Sommaire

BONJOUR
Musique "Invention BWV784" J.S. BACH 1685-1750

Tutoriel et Help

OpenGL Library

Nous veillons aux respects des droits d'auteur : si par erreur un contenu protégé est disponible sans indication sur ce site, merci de contacter l'administrateur, il sera retiré ou mis à jour dans les plus brefs délais.


OpenGL — Wikipédia

OpenGL elle-même
OpenGL — Wikipédia

OpenGL

Un article de Wikipédia, l'encyclopédie libre.
OpenGL
Image illustrative de l'article OpenGL
Développeur | Silicon Graphics (SGI)
Dernière version | 4.5 (11 août 2014) Voir et modifier les données sur Wikidata
Écrit en | C Voir et modifier les données sur Wikidata
Environnements | Multiplate-forme
Type | API
Site web | OpenGL.org
Consultez la documentation du modèle
Un traitement de pipeline 3D (Graphics Pipeline Process).

OpenGL (Open Graphics Library) est un ensemble normalisé de fonctions de calcul d'images 2D ou 3D lancé par Silicon Graphics en 1992[1]. Cette interface de programmation est disponible sur de nombreuses plateformes où elle est utilisée pour des applications qui vont du jeu vidéo jusqu'à la CAO en passant par la modélisation[1].

OpenGL permet à un programme de déclarer la géométrie d'objets sous forme de points, de vecteurs, de polygones, de bitmaps et de textures. OpenGL effectue ensuite des calculs de projection en vue de déterminer l'image à l'écran, en tenant compte de la distance, de l'orientation, des ombres, de la transparence et du cadrage[2],[3].

L'interface regroupe environ 250 fonctions différentes qui peuvent être utilisées pour afficher des scènes tridimensionnelles complexes à partir de simples primitives géométriques. Du fait de son ouverture, de sa souplesse d'utilisation et de sa disponibilité sur toutes les plates-formes, elle est utilisée par la majorité des applications scientifiques, industrielles ou artistiques 3D et certaines applications 2D vectorielles. Cette bibliothèque est également utilisée dans l'industrie du jeu vidéo où elle est souvent en rivalité avec la bibliothèque de Microsoft : Direct3D. Une version nommée OpenGL ES a été conçue spécifiquement pour les applications embarquées (téléphones portables, agenda de poche, consoles de jeux…).

Sommaire

[masquer]
  • 1 Histoire
    • 1.1 OpenGL 1.5
    • 1.2 OpenGL 2.0
    • 1.3 OpenGL ES
    • 1.4 OpenGL 3.0
    • 1.5 OpenGL 4.0
    • 1.6 Vulkan
  • 2 Extensions
  • 3 Bibliothèques
  • 4 Exemple simple de programme OpenGL
  • 5 Implémentations
  • 6 Intérêt d'OpenGL pour Silicon Graphics
  • 7 Arguments pour et contre OpenGL
  • 8 Utilisations
  • 9 Critiques
  • 10 Voir aussi
    • 10.1 Bibliographie
    • 10.2 Spécifications
    • 10.3 Notes et références
    • 10.4 Articles connexes
    • 10.5 Liens externes

Histoire

OpenGL est une évolution d'IrisGL, API 3D développée par Silicon Graphics (SGI). Cette dernière étant difficile à faire évoluer et à étendre, il a été décidé chez Silicon Graphics qu'OpenGL pouvait être assimilé à un sur-ensemble d'IrisGL. Les spécifications et développements de base ont été réalisés par une équipe de Silicon Graphics. Le projet Fahrenheit, initiative de Microsoft et de Silicon Graphics, tenta d'unifier les interfaces OpenGL et Direct3D. Celui-ci apporta au début l'espoir de mettre de l'ordre dans le monde des API 3D, mais pour des contraintes financières de la part de Silicon Graphics, le projet dut être abandonné.

La spécification OpenGL est actuellement surveillée par l'Architecture Review Board (ARB), formé en 1992. L'ARB se compose d'entreprises ayant un profond intérêt pour la création d'une API cohérente et largement disponible. Selon le site officiel d'OpenGL, AMD/ATI, Apple, Dell, Evans & Sutherland, Hewlett-Packard, IBM, Intel, Matrox, Nvidia, SGI et Sun font partie des membres votants (juin 2002). Microsoft, l'un des membres fondateurs, s'est retiré en mars 2003.

OpenGL 1.5

Le 29 juillet 2003 est annoncé OpenGL 1.5. Au même moment, ARB annonce les spécifications d'une première ébauche de l'OpenGL Shading Language ainsi que les trois extensions ARB_shader_objects, ARB_vertex_shader et ARB_fragment_shader.

OpenGL 2.0

Le 7 septembre 2004 est annoncé OpengGL 2.0. l'OpenGL Shading Language (GLSL, également surnommé GLslang) à la syntaxe proche du langage C y est intégré dans une version définitive.

OpenGL ES

Le 31 juillet 2006 lors de la conférence de la Siggraph, l'ARB a annoncé sa décision de transférer le contrôle de la spécification OpenGL au Khronos Group, qui s'occupait déjà de différentes spécifications OpenGL pour les systèmes embarqués et les consoles de jeux vidéo dont OpenGL ES. L'Architecture Review Board a été dissoute le 21 septembre 2006, mais pour des raisons historiques, l'acronyme « ARB » a été conservé[4].

OpenGL 3.0

Les spécifications officielles de OpenGL 3.0 ont été présentées le 11 août 2008. À l'origine, OpenGL 3 devait constituer un changement majeur, avec une refonte totale de l'API pour la rendre plus concurrentielle face à Direct3D. Il était notamment question d'abandonner les fonctionnalités obsolètes qui n'étaient, jusque là, conservées que par souci de compatibilité avec les anciennes versions, l'utilisation d'un nouveau modèle objet plus cohérent qui encapsule notamment les états, etc[5]. Cependant, après un an de retard et une absence totale de communication de la part du groupe Khronos, cette refonte totale a été abandonnée au profit d'une mise à jour incrémentale permettant simplement de supporter les dernières technologies graphiques [6]. Cette décision serait due à la volonté de rassurer certains marchés, par exemple en CAO, qui ne souhaitaient pas voir disparaître ces anciennes fonctionnalités[7]. Malgré tout, les fonctions obsolètes seront marquées « deprecated » dans les nouvelles spécifications, et pourraient être supprimées dans les versions futures.

OpenGL 4.0

Le 11 mars 2010, OpenGL 4.0 est annoncé, il a pour but d'exploiter pleinement le matériel compatible avec DirectX 11.

Au même moment OpenGL 3.3 est également annoncé.

Vulkan

Au début annoncé sous l’appellation OpenGL Next, Vulkan revoit d'importants concept d'OpenGL afin de s'adapter aux 25 ans d'évolution des processeurs graphiques 3D depuis la première version. La compatibilité avec les anciennes versions n'est plus assurée.

Les principaux changements les plus notables sont :

  • OpenGL et OpenGL ES sont unifiés ;
  • Les pilotes sont simplifiés, le processeur contrôle lui même directement le GPU et la mémoire utilisée par celui-ci.

Extensions

La norme OpenGL permet à différents fabricants d'ajouter de nouvelles fonctionnalités sous forme d'extensions. Une extension est distribuée en 2 parties : un fichier d'en-têtes qui contient les fonctions prototypes de l'extension et les drivers du fabricant. Chacun d'eux possède une abréviation qui est utilisée pour nommer leurs nouvelles fonctions et constantes. Par exemple, l'abréviation de nVidia (« NV ») est utilisée pour définir leur fonction propriétaire « glCombinerParameterfvNV() » et leur constante « GL_NORMAL_MAP_NV ». Il peut arriver que plus d'un fabricant implémente la même fonctionnalité. Dans ce cas, l'abréviation « EXT » est utilisée. Il peut également arriver que l'ARB officialise une extension. Celle-ci devient alors un standard et l'abréviation « ARB » est utilisée. La première extension ARB fut GL_ARB_multitexture.

Bibliothèques

Plusieurs bibliothèques sont développées à partir d'OpenGL afin d'apporter des fonctionnalités qui ne sont pas disponibles dans la bibliothèque OpenGL elle-même :

  • GLU ;
  • GLUT ;
  • GLUI ;
  • GLEW ;
  • GLEE ;
  • GLFW ;
  • GLM.

On notera en particulier la bibliothèque OpenGL Performer, développée par Silicon Graphics et disponible pour IRIX, Linux et quelques versions de Windows, qui permet la création d'applications de rendus temps réel.

Exemple simple de programme OpenGL

Voici un programme simple, en 2D, dessinant un triangle et permettant de modifier instantanément sa couleur en pressant l'une des touches r, g, b et celle du fond avec R, G, B. La touche q quitte le programme[8]. Celui-ci, qui utilise GLUT pour simplifier la programmation, est compilé et exécuté, sous Linux, parcc colorswap.c -lglut -lGL -lGLU && ./a.out. Les bibliothèques OpenGL sont juste à installer depuis les dépôts de votre distribution particulière, ce qui est très simple. L'objectif du code est ici la lisibilité et non la performance.

Sortie initiale du programme OpenGL "colorswap.c". Il s'agit d'une simple démonstration montrant les premiers concepts. A ce stade, OpenGL n'est utilisé que comme le GDDM (graphical data display manager) d'IBM : en 2D.
Une pression sur la touche g (pour green) change instantanément la couleur du triangle, montrant la vélocité d'OpenGL et son intérêt même en 2D pour des graphiques interactifs simples.
La pression de R (pour red, majuscule ici pour indiquer que cela concerne le fond) bascule tout aussi instantanément le fond en rouge.
/*
 * colorswap.c - Options -lglut -lGL -lGLU :
 * cc colorswap.c -lglut -lGL -lGLU && ./a.out
 *
 * Donnez le focus à la fenêtre, puis pressez :
 * R G B ou Y pour couleur du fond
 * r g b ou y pour celle de l'encre
 * q pour quitter.
 *
 * Programme écrit sous OpenSuSE avec Geany sous
 * Sony Satellite G50 bootant Linux d'un SSD USB3
 * en inhibant le secure boot et en donnant la
 * priorité de boot à l'USB.
 */

#include <GL/glut.h>

int firstPass = 1; // simule le « once » d'Eiffel

// Fonction associée (« callback ») à la fenêtre.
// Elle redessine tout quand on redimensionne.
void Rafraichir(void)
{
  glClear(GL_COLOR_BUFFER_BIT);     // Effacer la surface graphique

  if (firstPass) {
    glColor3f(0, 0, 1.0);  // Encre bleue au départ
    firstPass = 0;
  }

  // Dessiner un triangle
  glBegin(GL_POLYGON);      // Commencer un polygone
  glVertex2i(100, 100); // Coordonnées des trois points
  glVertex2i(400, 100);
  glVertex2i(250, 400);
  glEnd();                                // Fermer le polygone
  glFlush();                      // Dessiner le polygone
}

// Action sur frappe d'une touche au clavier
void Clavier(unsigned char key, int x, int y)
{
  switch(key) {
    case 'q' : exit(0); // q => « quitter »

    case 'R' : glClearColor(1, 0, 0, 0); break;   // R G B : couleur fond
    case 'G' : glClearColor(0, 1, 0, 0); break;
    case 'B' : glClearColor(0, 0, 1, 0); break;
    case 'Y' : glClearColor(1, 1, 0, 0); break;   // Y : jaune

    case 'r' : glColor3f(1, 0, 0); break;        // r g b : couleur encre
    case 'g' : glColor3f(0, 1, 0); break;
    case 'b' : glColor3f(0, 0, 1); break;
    case 'y' : glColor3f(1, 1, 0); break;        // y ; jaune
  }

  Rafraichir();   // Affichage
}

int main(int argc, char *argv[])
{
  int win;                                        // Identificateur de fenêtre (une seule)
  glutInit(&argc, argv);                   // initialise GLUT
  glutInitDisplayMode(GLUT_RGB);    // On travaille en RGB
  glutInitWindowSize(500,400);      // 500px large, 400px haut
  win = glutCreateWindow("Pressez r/g/b/y/R/G/B/Y/q");

  // Ordres maintenant exécutés dans la fenêtre « win »
  glClearColor(0.9, 0.9, 0.2, 0);     // Jaune
  gluOrtho2D(0,600,0,600);                    // On garde ces coordonnées

  glutDisplayFunc(Rafraichir);              // Callback de la fenêtre
  glutKeyboardFunc(Clavier);                // Callback du clavier

  glutMainLoop();                                 // Boucle infinie

  /* Le programme n'atteint jamais ce point */

  return 0;
}

Implémentations

Plusieurs implémentations d'OpenGL (exploitant l'accélération 3D fournie par le matériel) existent pour Windows, de nombreuses stations Unix et Mac OS. Ces implémentations sont généralement fournies par les constructeurs de matériels graphiques et y sont étroitement liées. Il existe une implémentation libre de cette bibliothèque, nommée Mesa, créée en 1993 par Brian Paul et utilisant la même API, ce qui permettait :

  • de se passer de la licence OpenGL dans la plupart des cas[9], l'implémentation de SGI étant sous licence, mais l'API elle-même ne pouvant l'être ;
  • de faire tourner des applications OpenGL sur des terminaux X de base qui y étaient inaptes (les performances sont alors quelconques faute d'accélération matérielle, mais cela vaut souvent mieux que pas d'exécution du tout et reste acceptable si l'on n'a pas besoin d'animation en temps réel). Ces stations ne disposant pas en général d'accélération 3D matérielle, on ne peut les utiliser que pour des modèles simples comportant relativement peu de polygones : de quelques dizaines à quelques milliers.

Intérêt d'OpenGL pour Silicon Graphics

Ce modèle est-il pertinent ? Cliquez pour en voir d'autres.
[ afficher ]Cette section ne cite pas suffisamment ses sources (indiquez la date de pose grâce au paramètre date).
Pour l'améliorer, ajoutez des références vérifiables [Comment faire ?] ou le modèle {{Référence nécessaire}} sur les passages nécessitant une source.

Silicon Graphics a eu pour politique de mettre à chaque fois dans le domaine public l'avant-dernière version de GL, bibliothèque graphique de GL. Cette approche :

  • évitait la concurrence (OpenGL étant gratuit et ouvert, pourquoi le réinventer ?) ;
  • dissuadait de modifier OpenGL, car tout ajout serait à recopier dès la version suivante ;
  • donnait aux stations Silicon Graphics un avantage notable, GL ayant toujours une version d'avance sur OpenGL ;
  • constituait une forme de mécénat bon pour le "karma" de la société dans les milieux Open Source

Arguments pour et contre OpenGL

Dans la pratique, tout en continuant à assurer un certain support d'OpenGL, les constructeurs et éditeurs de logiciel ont mis d'autres cordes à leur arc, comme Direct3D pour Microsoft (Google Earth permet par exemple un choix entre OpenGL et Direct3D). L'une des raisons était de mieux tirer parti d'innovation matérielles introduites dans les cartes graphiques, comme le nombre de processeurs, leur spécialisation ou non, etc.

En sens inverse a été cependant développé WebGL, qui donne des potentialités 3D à n'importe quel navigateur qui le supporte. OpenGL garde donc l'atout de la généralité, tandis que des solutions plus spécialisées restent préférées pour les applications demandant des calculs graphiques intensifs, comme les jeux. Voir aussi SDL.

Utilisations

Certains logiciels utilisent OpenGL pour gérer l'ensemble de leur interface, même 2D, comme Blender, Google Earth (version OpenGL), GLtron, XBMC, ou la version SGI de X11.

Critiques

Rich Geldreich et Joshua Barczak, sur leur blogs personnels, critiquent OpenGL[10],[11]. Leurs doléances suggérant d'un coté le besoin d'une remise à plat totale à la lumière de l'expérience acquise, de l'autre le besoin de mieux maîtriser la dispersion des portages. Il s'agit cependant d'utilisateurs à haute technicité devant utiliser les fonctions les plus avancées des cartes graphiques de 2014.

Voir aussi

Bibliographie[ modifier | modifier le code ]

  • Open GL 2.0 : Guide officiel, Jackie Neider, Mason Woo, Tom Davis, Dave Shreiner, Véronique Campillo, 2006, CampusPress (ISBN 978-2-7440-2086-5)
  • (en) OpenGL Programming Guide: The Official Guide To Learning OpenGL, Version 2, Dave Shreiner, Mason Woo, Jackie Neider, 2005, Addison-Wesley (ISBN 978-0-321-33573-9)
  • (en) OpenGL Reference Manual: The Official Reference Document to Open Gl, Version 1.4, Dave Shreiner, 2004, Addison-Wesley (ISBN 978-0-321-17383-6)
  • (en) OpenGL Shading Language, Randi J. Rost, 2005, Addison-Wesley (ISBN 978-0-321-33489-3)
  • OpenGL Superbible, Richard S., Jr. Wright, Benjamin Lipchak, 2004, Sams (ISBN 978-0-672-32601-1)
  • (en) Beginning Opengl Game Programming, Dave Astle, Kevin Hawkins, 2004, Course Technology (ISBN 1-59200-369-9)

Spécifications

  • (en) OpenGL & OpenGL Utility Specifications

Notes et références

  1. ? a et b (en)Dave Astle et Kevin H. Hawkins, Beginning OpenGL: Game Programming, Cengage Learning - 2004 (ISBN 9781592003693).
  2. ? (en)Paul Martz,OpenGL Distilled,Addison-Wesley Professional - 2006, (ISBN 9780132701785).
  3. ? (en)PJ Cabrera, Peter Bakhirev, Ian Marsh, Ben Smith, Eric Wing, Scott Penberthy, Beginning iPhone Games Development, Apress - 2010 (ISBN 9781430226000).
  4. ? (en) « The OpenGL Pipeline Newsletter - Volume 002 » [archive], Transitions, novembre 2006.
  5. ? Long Peaks : présentation sur le site d'Icare3D [archive].
  6. ? http://www.presence-pc.com/tests/Opengl-DirectX-guerre-22813/ [archive].
  7. ? (en) http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=243307&#Post243307 [archive].
  8. ? Un dérivé de ce programme est également partagé par son auteur dans une bibliothèque publique de Github : https://github.com/fdarmingaud/colorswap/blob/master/colorswap.c [archive]
  9. ? http://en.swpat.org/wiki/OpenGL [archive]
  10. ? (en) Rich Geldreich, « Things that drive me nuts about OpenGL » [archive], Rich Geldreich's Tech Blog, 11 mai 2014.
  11. ? (en) Michael Larabel, « Former AMD Developer: OpenGL Is Broken » [archive], Phoronix, 31 mai 2014.

Articles connexes

  • API
  • Direct3D
  • OpenGL ES
  • OpenAL
  • Mesa 3D
  • OpenCL de la même fondation
  • Liste de bibliothèques 3D

Liens externes[ modifier | modifier le code ]

  • (en) Site officiel
  • (en) Catégorie OpenGL de l’annuaire DMOZ
  • (en) Mesa 3D, implémentation libre d'OpenGL
[masquer]
v · m
Standards du Khronos Group
COLLADA · EGL · OpenCL · OpenGL · OpenGL ES · OpenGL SC (en) · OpenKODE · OpenMAX · OpenML · OpenSL ES (en) · OpenVG · OpenWF (en) · Vulkan · WebCL · WebGL
  • Portail de l’imagerie numérique Portail de l’imagerie numérique
Ce document provient de « https://fr.wikipedia.org/w/index.php?title=OpenGL&oldid=127482257 ».
Catégories :
  • Bibliothèque logicielle graphique
  • Interface de programmation
  • 3D
  • Norme ou standard informatique
| [+]
Catégories cachées :
  • Article à illustrer Logiciel
  • Page utilisant P348
  • Page utilisant P277
  • Bandeau de maintenance sans paramètre date/Liste complète
  • Article manquant de référence depuis date inconnue
  • Article manquant de référence/Liste complète
  • Article contenant un appel à traduction en anglais
  • Portail:Imagerie numérique/Articles liés
  • Portail:Informatique/Articles liés
  • Bon article en letton

s


OpenGL utility library — Wikipédia

OpenGL utility library

Un article de Wikipédia, l'encyclopédie libre.

GLU (OpenGL Utility Library) est une bibliothèque associée à OpenGL. Elle vient compléter cette dernière en apportant quelques routines pour des opérations de plus haut niveau. Ces dernières, suivant les conventions et la syntaxe d'OpenGL, font appel à des commandes OpenGL de bas niveau. GLU apporte les fonctionnalités suivantes :

  • Gestion de la matrice de projection pour mettre en place une vue orthogonale ou en perspective ;
  • Gestion de la matrice de visualisation avec une routine de type « caméra » ;
  • Redimensionnement d'images ;
  • Construction automatique de Mipmaps ;
  • Fragmentation de polygones ;
  • Gestion d'objets quadriques (sphère, cylindre, disque) ;
  • Gestion des courbes et des surfaces de Bézier ;
  • Gestion des surfaces NURBS (Non Uniform Rational B-Spline) ;
  • Vérification de la présence d'extensions OpenGL.

Cette bibliothèque fait partie de toutes les implémentations OpenGL.

Versions

La dernière version de GLU est la 1.3. Le tableau suivant donne la correspondance entre les versions de GLU et les versions d'OpenGL associées :

Correspondance des versions
Version de GLU | Version d’OpenGL
1.0 | 1.0
1.1 | 1.0
1.2 | 1.1
1.3 | 1.2

Voir aussi

Liens internes

  • GLUT
  • GLUI
  • GLEW

Lien externe

  • (en) Spécifications de GLU
  • Portail de la programmation informatique Portail de la programmation informatique
  • Portail de l’imagerie numérique Portail de l’imagerie numérique
Ce document provient de « https://fr.wikipedia.org/w/index.php?title=OpenGL_utility_library&oldid=95982984 ».
Catégorie :
  • Bibliothèque logicielle graphique
| [+]
Catégories cachées :
  • Portail:Programmation informatique/Articles liés
  • Portail:Informatique/Articles liés
  • Portail:Imagerie numérique/Articles liés


OpenGL utility toolkit — Wikipédia

OpenGL utility toolkit

Un article de Wikipédia, l'encyclopédie libre.

GLUT ( OpenGL Utility Toolkit ) est une bibliothèque utilitaire dont les droits d'auteur sont détenus par Mark J. Kilgard, offrant un jeu de routines pour la gestion des fenêtres OpenGL et les interactions avec le système d'exploitation (gestion clavier, souris, etc.) indépendamment de celui-ci et du gestionnaire de fenêtres. Elle est essentiellement utilisée pour la création de programmes de démonstrations.

Sommaire

  • 1 Présentation
  • 2 Exemple
  • 3 Notes et références
  • 4 Voir aussi
    • 4.1 Liens internes
    • 4.2 Liens externes

Présentation

La bibliothèque GLUT a été écrite par Mark J. Kilgard, auteur de OpenGL Programming for the X Window System et de The CG Tutorial: The Definitive Guide to Programmable Real-Time Graphics, lorsqu'il travaillait pour Silicon Graphics.

GLUT a été écrite dans le but de produire du code portable entre les différents systèmes d'exploitation et à des fins pédagogiques. Sa simplicité permet de ne pas se préoccuper de la partie système, relativement complexe, et de se concentrer sur le fond du programme OpenGL lui-même. La création d'un programme avec GLUT ne prend que quelques lignes de code. Par ailleurs, elle respecte les conventions et la syntaxe d'OpenGL ; les noms de fonction commencent par le préfixe « glut » et les macros par « GLUT » (OpenGL utilise les préfixes « gl » et « GL »).

Outre l'encapsulation des fonctions de base d'un gestionnaire de fenêtre, GLUT propose des routines pour le dessin de quelques objets simples à trois dimensions comme la sphère, le cube, le cône, le tore ou encore la fameuse théière de Newell.

La bibliothèque GLUT n'est désormais plus en développement et s'est arrêtée à la version 3.7 en mai 1998. Les bibliothèques Open Source freeglut et OpenGLUT (qui dérive de freeglut) remplacent aujourd'hui GLUT. Elles réimplémentent l'API GLUT et y apportent quelques évolutions. La création de ces bibliothèques alternatives est due au flou concernant la licence de GLUT, qui d'après certains ne permettrait pas la redistribution de code modifié [ 1 ] .

Exemple

Voici un exemple de programme utilisant GLUT et OpenGL (compatible avec freeglut et OpenGLUT). Le résultat est une simple fenêtre avec un triangle multicolore au centre.

#include <
    GL/glut.h>
voidreshape (int w, int h) {
      if (h == 0)    h = 1;
      glViewport (0, 0, (GLsizei)w,  (GLsizei)h);
      glMatrixMode (GL_PROJECTION);
      glLoadIdentity ();
      gluPerspective (45.0, (GLfloat)w/(GLfloat)h,  0.1,  1000.0);
      glMatrixMode (GL_MODELVIEW);
      glLoadIdentity ();
      glutPostRedisplay ();

}
voiddisplay () {

    /* efface les tampons de couleur et de profondeur;
réinitialise     la matrice de modélisation-visualisation */
      glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      glClearColor(0, 0, 0, 0);
      glLoadIdentity ();

/* dessine un triangle dont les couleurs rouge, vert et bleu sont     attribuées aux trois sommets */
      glTranslatef (0.0, 0.0, -5.0);
      glBegin (GL_TRIANGLES);
        glColor3f (1.0f, 0.0f, 0.0f);
        glVertex3f (-1.0f, -1.0f, 0.0f);
        glColor3f (0.0f, 1.0f, 0.0f);
        glVertex3f (1.0f, -1.0f, 0.0f);
        glColor3f (0.0f, 0.0f, 1.0f);
        glVertex3f (0.0f, 1.0f, 0.0f);
      glEnd ();
      glFlush();

}
intmain (int argc, char *argv[]) {

/* initialisation de GLUT */
      glutInit (&
    argc, argv);

/* création d'une fenêtre OpenGL RVBA avec en simple mémoire tampon     avec un tampon de profondeur */
      glutInitDisplayMode (GLUT_RGBA | GLUT_SINGLE | GLUT_DEPTH);
      glutInitWindowSize (350, 300);
      glutCreateWindow ("Une fenêtre OpenGL avec GLUT");

/* initialisation des fonctions callback appelées par glut      pour respectivement le redimensionnement de la fenêtre     et le rendu de la scène */
      glutReshapeFunc (reshape);
      glutDisplayFunc (display);

/* entre dans la boucle d'évènements */
      glutMainLoop ();
      return 0;

}

On peut ensuite faire évoluer ce programme en définissant des callbacks pour les actions au clavier (glutKeyboardFunc) ou à la souris (glutMouseFunc), voire au simple mouvement de la souris (glutMotionFunc), afin de changer par exemple interactivement la position des points du triangle ou le choix des couleurs. Voir aussi l' exemple interactif simple donné dans l'article OpenGL.

Notes et références

  1. ↑ Voir page de discussion de l'article concernant les problèmes de licence de GLUT.


OpenGL & OpenGL Utility Specifications

The Industry's Foundation for High Performance Graphics

from games to virtual reality, mobile phones to supercomputers

  • Home
  • Documentation
    • About OpenGL 4.5
    • OpenGL Shading Language
    • About OpenGL Extensions
    • OpenGL Registry
    • Spec Feedback
    • OpenGL 4 Reference Pages
    • OpenGL 3.3 Reference Pages
    • OpenGL 2.1 Reference Pages
    • OS/Platform Implementations
    • OpenGL Books
  • Coding Resources
    • OpenGL SDK
    • Getting Started
    • OpenGL Registry
    • FAQ
    • GLUT & Utility Libraries
    • Programming Language Bindings
    • Sample Code & Tutorials
    • Benchmarks
    • Mailing Lists & News Groups
    • OpenGL StackOverflow
    • Archived Resources
  • Wiki
  • Forums
  • About OpenGL
    • Contact Us
    • OpenGL logo
    • Advertise on OpenGL
    • Jobs

OpenGL & OpenGL Utility Specifications

OpenGL Specifications and GLX & GLU Libraries can now be found in the official OpenGL Registry.
OpenGL Reference Cards
  • OpenGL 4.2 GLSL 4.20 Reference Card
  • OpenGL 4.0 GLSL 4.0 Reference Card
  • OpenGL 3.2 GLSL 1.5 Reference Card
GLUT Specifications
  • FreeGlut - Sourceforge
  • GLUT 3 Specification (HTML), (pdf), (PostScript)
  • The GLUT 3.5 Manual Pages (Postscript as gzip)
    • GLUT is the OpenGL Utility Toolkit, a window system independent toolkit for writing OpenGL programs. It implements a simple windowing application programming interface for OpenGL. GLUT makes it considerably easier to learn about and explore OpenGL programming and provides a portable API so you can write a single OpenGL program that works on both Win32 PCs, Mac OS, and Linux/UNIX workstations
Man Pages

OpenGL, GLX, and GLU man pages are online in the OpenGL SDK.

Column Header

Documentation

  • About OpenGL 4.5
  • OpenGL Shading Language
  • About OpenGL Extensions
  • OpenGL Registry
  • Spec Feedback Form
  • Newest Reference Pages
  • OpenGL 4 Reference Pages
  • OpenGL 3.3 Reference Pages
  • OpenGL 2.1 Reference Pages
  • OS/Platform Implementations
  • OpenGL Books
  • OpenGL 4.5 & GLSL 4.5 Quick Reference Card
  • OpenGL 4.4 & GLSL 4.4 Quick Reference Card
  • OpenGL 4.3 & GLSL 4.3 Quick Reference Card
  • OpenGL 4.2 & GLSL 4.2 Quick Reference Card
  • OpenGL 3.2 & GLSL 1.5 Quick Reference Card
Column Footer
  • About OpenGL
  • Privacy Policy

OpenGL is a registered trademark of SGI

Website Copyright 1997 - 2016 The Khronos Group. All rights reserved.

OpenGL.org organization managed by Gold Standard Group and website maintained by Out of Control®


The Industry's Foundation for High Performance Graphics

from games to virtual reality, mobile phones to supercomputers

· Home

· Documentation

o                                            About OpenGL 4.5

o                                            OpenGL Shading Language

o                                            About OpenGL Extensions

o                                            OpenGL Registry

o                                            Spec Feedback

o                                            OpenGL 4 Reference Pages

o                                            OpenGL 3.3 Reference Pages

o                                            OpenGL 2.1 Reference Pages

o                                            OS/Platform Implementations

o                                            OpenGL Books

· Coding Resources

o                                            OpenGL SDK

o                                            Getting Started

o                                            OpenGL Registry

o                                            FAQ

o                                            GLUT & Utility Libraries

o                                            Programming Language Bindings

o                                            Sample Code & Tutorials

o                                            Benchmarks

o                                            Mailing Lists & News Groups

o                                            OpenGL StackOverflow

o                                            Archived Resources

· Wiki

· Forums

· About OpenGL

o                                            Contact Us

o                                            OpenGL logo

o                                            Advertise on OpenGL

o                                            Jobs

OpenGL API Documentation Overview

OpenGL is the industry's most widely used, supported and best documented 2D/3D graphics API making it inexpensive & easy to obtain information on implementing OpenGL in hardware and software. There are numerous books, tutorials, online coding examples, coding seminars, and classes that document the API, Extensions, Utility Libraries, and Platform Specific Implementations.

The two essential documentation resources that every developer should have are the latest releases of:

  • The OpenGL SuperBible
  • The OpenGL Programming Guide
The documentation section of OpenGL.org contains:
  • What's New in OpenGL
    • A review of the new features and extensions in the most recent version of the OpenGL specification
  • Download the OpenGL Specification and Utility Library Specifications
    • Download current specification and man pages for OpenGL, GLX, GLU , and GLUT, as well as older versions of these APIs.
  • OpenGL Reference Pages
    • The reference pages (also known as "man pages") include all of the important usage information for each command. This information includes a description of all parameters, return values, error conditions, and related commands.
      • Latest
      • OpenGL 4
      • OpenGL 3.3
      • OpenGL 2.1
  • The OpenGL Shading Language
    • The recent trend in graphics hardware has been to replace fixed functionality with programmability in areas that have grown exceedingly complex (e.g., vertex processing and fragment processing). The OpenGL Shading Language allows application programmers to express the processing that occurs at those programmable points of the OpenGL pipeline. This document specifies the programming language.
      • GLSL Reference Pages
  • OpenGL Extensions & OpenGL Extension Registry
    • Using the OpenGL extension mechanism, hardware developers can differentiate their products and incorporate new features by developing extensions that allow software developers to access additional performance and technological innovations. These extensions provide OpenGL application developers with new rendering features above and beyond the features specified in the official OpenGL standard. OpenGL extensions keep the OpenGL API current with the latest innovations in graphics hardware and rendering algorithms.
  • OS/Platform Implementations
    • Supported on all UNIX workstations, and shipped standard with every Windows and MacOS PC, no other graphics API operates on a wider range of hardware platforms and software environments. OpenGL runs on every major operating system including Mac OS, OS/2, UNIX, Win32/64, Linux, OPENStep and BeOS as well as embedded Real Time OSs and game consoles. OpenGL is callable from Ada, C, C++, Fortran, Python, Perl and Java and offers complete independence from network protocols and topologies
  • OpenGL Books
    • Books which range from covering core functionality to advanced rendering tips and techniques, to game programming, to 3D terrain rendering, to niche applications.

Column Header

Documentation

  • About OpenGL 4.5
  • OpenGL Shading Language
  • About OpenGL Extensions
  • OpenGL Registry
  • Spec Feedback Form
  • Newest Reference Pages
  • OpenGL 4 Reference Pages
  • OpenGL 3.3 Reference Pages
  • OpenGL 2.1 Reference Pages
  • OS/Platform Implementations
  • OpenGL Books
  • OpenGL 4.5 & GLSL 4.5 Quick Reference Card
  • OpenGL 4.4 & GLSL 4.4 Quick Reference Card
  • OpenGL 4.3 & GLSL 4.3 Quick Reference Card
  • OpenGL 4.2 & GLSL 4.2 Quick Reference Card
  • OpenGL 3.2 & GLSL 1.5 Quick Reference Card

Column Footer

  • About OpenGL
  • Privacy Policy

OpenGL is a registered trademark of SGI

Website Copyright 1997 - 2016 The Khronos Group. All rights reserved.

OpenGL.org organization managed by Gold Standard Group and website maintained by Out of Control®


La géométrie en OpenGL : vers les VertexBufferObject

  • Introduction
  • II. Les préices d'OpenGL : la g?m?rie imm?iate
    • II-A. Rappel sur le dessin imm?iat
    • II-B. Utilisation dans notre exemple
    • II-C. Avantages / Inconv?ients de cette technique
  • III. Quelques avancés : les "vertex array" (VA)
    • III-A. Utilisation des Vertex Array
    • III-B. Compléent par les "Display List"
    • III-C. Pour notre exemple
    • III-D. Avantages / Inconvéients de cette technique
  • IV. Nouvelle technologie : les Vertex Buffer Object (VBO)
    • IV-A. Par oû commencer ?
    • IV-B. Différents cas d'utilisations
    • IV-C. Utiliser un buffer
    • IV-D. Pour notre exemple
  • Conclusion
  • Remerciements

Pourquoi faire un tutoriel sur la géométrie en OpenGL ? Pourquoi faut-il s'y intéresser ? La réponse est simple, les technologies 3D sont sans cesse en cours d'évolution et d'améliorations. OpenGL ne fait pas exception, il y a réguliérement de nouvelles extensions pour améliorer ses performances et son efficacité. L'une d'elle se concentre sur l'amélioration de la géométrie : les Vertex Buffer Object. Comment en est on arrivé.

Introduction

Vous trouvez que votre application est lente ? Peut-?re avez vous choisi un mauvais syst?e pour rendre tous vos mod?es 3D dans votre application ou votre jeu. Ce tutoriel a pour objectif de vous pr?enter toutes les techniques pour rendre des objets en OpenGL avec leurs avantages et leurs inconv?ients. Nous commencerons par une pr?entation de la premi?e m?hode utilis? par OpenGL pour rendre des objets, le mode imm?iat. Nous verrons en quoi ce syst?e n'est pas forc?ent adapt?au rendu et pourquoi les "VertexArray" ont fait leur apparition. Enfin, nous verrons en d?ail l'utilisation de la derni?e, la plus r?ente et nouvelle technologie que sont les "Vertex Buffer Object".

Au travers de ce tutoriel, nous verrons le fonctionnement g??al de chacune des technologies au travers de l'exemple simple qu'est le dessin d'un cube color? Nous ferons une petite ?ude du co? de chaque m?hode pour pouvoir les comparer et savoir dans quels cas utiliser l'une plut? que l'autre.

Rendu de notre cube
Rendu de notre cube

II. Les pr?ices d'OpenGL : la g?m?rie imm?iate

II-A. Rappel sur le dessin imm?iat

La premi?e m?hode utilise, pour dessiner des formes 3D dans notre application, les fonctions "imm?iates". Ce type de dessin est le premier que l'on apprend lorsque que l'on d?ouvre l'API OpenGL. Cette m?hode est dite "imm?iate" car elle consiste en une succession d'appels de fonctions qui dessineront directement ?l'?ran. Pour d?rire la g?m?rie, on utilise un ensemble de fonctions qui d?rira toutes les composantes de notre objet 3D dessin?

Chaque description de forme est encadr? par les fonctions glBegin() et glEnd() qui permettent de d?imiter notre dessin. La premi?e prend comme param?re le type de forme ?dessiner. Voici une description de ces fonctions :

Prototype de la fonction | Description
void glBegin(GLenum mode); | Marque le d?ut du dessin d'un groupe de primitives. mode pr?ise le type des primitives parmi GL_POINTS, GL_LINES, GL_LINE_STRIP, GL_LINE_LOOP, GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_QUADS, GL_QUAD_STRIP et GL_POLYGON.
void glEnd(void); | Marque la fin du dessin d'un groupe de primitives.

Chaque primitive est d?oup? en vertex qui correspondent ?un sommet de notre forme 3D. Nous pouvons d?inir plusieurs types d'informations par vertex comme sa position, sa couleur, ... Chaque information est d?inie par un appel de fonction correspondant. Par exemple, nous utiliserons glVertex*() pour d?inir la position du vertex et glColor*() pour en d?inir la couleur. Ces fonctions sont d?rites ici :

Prototype de la fonction | Description
void glVertex*(TYPE param?res); | Sp?ifie la position du vertex ?dessiner
void glNormal*(TYPE param?res); | Sp?ifie la normale du vertex
void glColor*(TYPE param?res); | Sp?ifie la couleur du vertex
void glSecondaryColor*(TYPE param?res); | Sp?ifie la seconde couleur du vertex
void glIndex*(TYPE param?res); | Sp?ifie l'index de la couleur du vertex
void glTexCoord*(TYPE param?res); | Sp?ifie les coordonn?s de la texture principale du vertex
void glMultiTexCoord*(TYPE param?res); | Sp?ifie les coordonn?s de texture en cas de "multi-texturing" du vertex
void glFogCoord*(TYPE param?res); | Sp?ifie les coordonn?s de fog du vertex

* doit ?re remplac?par la description du TYPE de donn?s pass?en param?re.

II-B. Utilisation dans notre exemple

Pour l'exemple du cube, que nous allons suivre tout au long du tutoriel, nous allons cr?r un cube dont chacun des vertex a une couleur diff?ente. Nous allons constituer notre cube de 6 faces. Chacune des faces est constitu? de 2 triangles dont chaque vertex ?une couleur diff?ente. Nous obtenons donc un cube de 12 triangles.

Dans le cas que nous ?udions actuellement, nous allons choisir le mode de primitives GL_TRIANGLES pour rendre notre objet. Ainsi, pour un cube, nous devons appeler 36 fois la fonction glVertex*() ( 12 triangles * 3 vertex ). Il faut multiplier ce nombre par la quantit?d'information par vertex pour obtenir le nombre total d'appels de fonction. Dans un cas extr?e qui param?rerait une couleur par vertex, la normale au vertex, des coordonn?s de fog et les coordonn?s pour deux textures, nous obtiendrions 180 appels de fonctions pour un simple cube avec 12 triangles. Pour notre exemple, nous nous contenterons d'une seule propri??de couleur par vertex donc nous obtiendrons 72 appels de fonctions plus les 2 appels pour glBegin et glEnd. Enfin, trois valeurs par appel de fonction nous donnent un total de 216 valeurs envoy?s au processeur graphique.

Rendu d'un cube en mode imm?iat
S?ectionnez

glBegin( GL_TRIANGLES );
    // gauche (x=-1)
    glColor3f( 1.0f, 0.0f, 0.0f ); glVertex3f( -1.0f, 1.0f, -1.0f );
    glColor3f( 1.0f, 0.0f, 1.0f ); glVertex3f( -1.0f, -1.0f, -1.0f );
    glColor3f( 1.0f, 1.0f, 1.0f ); glVertex3f( -1.0f, 1.0f, 1.0f );
    glColor3f( 1.0f, 1.0f, 1.0f ); glVertex3f( -1.0f, 1.0f, 1.0f );
    glColor3f( 1.0f, 0.0f, 1.0f ); glVertex3f( -1.0f, -1.0f, -1.0f );
    glColor3f( 0.0f, 0.0f, 1.0f ); glVertex3f( -1.0f, -1.0f, 1.0f );

    // droite (x=1)
    glColor3f( 0.0f, 1.0f, 0.0f ); glVertex3f( 1.0f, 1.0f, 1.0f );
    glColor3f( 0.0f, 1.0f, 1.0f ); glVertex3f( 1.0f, -1.0f, 1.0f );
    glColor3f( 1.0f, 1.0f, 0.0f ); glVertex3f( 1.0f, 1.0f, -1.0f );
    glColor3f( 1.0f, 1.0f, 0.0f ); glVertex3f( 1.0f, 1.0f, -1.0f );
    glColor3f( 0.0f, 1.0f, 1.0f ); glVertex3f( 1.0f, -1.0f, 1.0f );
    glColor3f( 1.0f, 1.0f, 1.0f ); glVertex3f( 1.0f, -1.0f, -1.0f );
    // bas (y=-1)
    glColor3f( 0.0f, 0.0f, 1.0f ); glVertex3f( -1.0f, -1.0f, 1.0f );
    glColor3f( 1.0f, 0.0f, 1.0f ); glVertex3f( -1.0f, -1.0f, -1.0f );
    glColor3f( 0.0f, 1.0f, 1.0f ); glVertex3f( 1.0f, -1.0f, 1.0f );
    glColor3f( 0.0f, 1.0f, 1.0f ); glVertex3f( 1.0f, -1.0f, 1.0f );
    glColor3f( 1.0f, 0.0f, 1.0f ); glVertex3f( -1.0f, -1.0f, -1.0f );
    glColor3f( 1.0f, 1.0f, 1.0f ); glVertex3f( 1.0f, -1.0f, -1.0f );
    // haut (y=1)
    glColor3f( 1.0f, 0.0f, 0.0f ); glVertex3f( -1.0f, 1.0f, -1.0f );
    glColor3f( 1.0f, 1.0f, 1.0f ); glVertex3f( -1.0f, 1.0f, 1.0f );
    glColor3f( 1.0f, 1.0f, 0.0f ); glVertex3f( 1.0f, 1.0f, -1.0f );
    glColor3f( 1.0f, 1.0f, 0.0f ); glVertex3f( 1.0f, 1.0f, -1.0f );
    glColor3f( 1.0f, 1.0f, 1.0f ); glVertex3f( -1.0f, 1.0f, 1.0f );
    glColor3f( 0.0f, 1.0f, 0.0f ); glVertex3f( 1.0f, 1.0f, 1.0f );
    // arriere (z=-1)
    glColor3f( 1.0f, 1.0f, 0.0f ); glVertex3f( 1.0f, 1.0f, -1.0f );
    glColor3f( 1.0f, 1.0f, 1.0f ); glVertex3f( 1.0f, -1.0f, -1.0f );
    glColor3f( 1.0f, 0.0f, 0.0f ); glVertex3f( -1.0f, 1.0f, -1.0f );
    glColor3f( 1.0f, 0.0f, 0.0f ); glVertex3f( -1.0f, 1.0f, -1.0f );
    glColor3f( 1.0f, 1.0f, 1.0f ); glVertex3f( 1.0f, -1.0f, -1.0f );
    glColor3f( 1.0f, 0.0f, 1.0f ); glVertex3f( -1.0f, -1.0f, -1.0f );
    // avant (z=1)
    glColor3f( 1.0f, 1.0f, 1.0f ); glVertex3f( -1.0f, 1.0f, 1.0f );
    glColor3f( 0.0f, 0.0f, 1.0f ); glVertex3f( -1.0f, -1.0f, 1.0f );
    glColor3f( 0.0f, 1.0f, 0.0f ); glVertex3f( 1.0f, 1.0f, 1.0f );
    glColor3f( 0.0f, 1.0f, 0.0f ); glVertex3f( 1.0f, 1.0f, 1.0f );
    glColor3f( 0.0f, 0.0f, 1.0f ); glVertex3f( -1.0f, -1.0f, 1.0f );
    glColor3f( 0.0f, 1.0f, 1.0f ); glVertex3f( 1.0f, -1.0f, 1.0f );
glEnd();

II-C. Avantages / Inconv?ients de cette technique

Notre premier exemple permet de mettre en ?idence certains avantages et certains inconv?ients de la technique de dessin "Imm?iat". Nous allons donc commencer par ?oquer les avantages d'une telle technique :

  • Permet de d?inir ind?endamment chacune des informations des vertex
  • Permet d'augmenter la lisibilit?du code
  • Regroupe clairement chaque rendu par type de primitives

Malgr?ceux-ci, on remarque rapidement un grand nombre d'inconv?ients tr? g?ants pour des applications "temps-r?l". En voici une liste non-exhaustive :

  • Un grand nombre d'appels de fonction pour des g?m?ries simples
  • Grande r??itivit?de commandes identiques
  • Complexifie les structures de donn?s utilisables
  • Obligation de s?arer chacune des composantes de nos informations (X, Y et Z pour les positions de nos vertex, par exemple)

La technique de dessin "Imm?iat" est tr? contraignante du point de vue performance pour une application "temps-r?l". Les deux probl?es principaux sont que le transfert des donn?s est extr?ement lent au vu de la quantit?d'appel de fonctions et du peu d'informations envoy?s au GPU par chaque appel. Le second probl?e vient du nombre d'appels identiques ?chaque frame. OpenGL nous propose une nouvelle technique permettant de r?oudre notamment le nombre d'appels de fonctions, de limiter l'envoi d'information en double. Cette technique utilise le principe des "vertex array". De plus, cette technique pourra ?re compl?? par la m?hode des "Display List" qui ?itera un grand nombre d'appels de fonction r??itifs ?chaque frame dans le cas de g?m?rie "statique".

III. Quelques avanc?s : les "vertex array" (VA)

III-A. Utilisation des Vertex Array

La seconde technique utilisable en OpenGL est donc celle des "Vertex Array". Le principe de cette technique est de rassembler l'ensemble des vertex d'une g?m?rie dans un tableau unique. Il peut y avoir un tableau par type d'information ou un seul tableau "entrelac? contenant toutes les informations. L'utilisation des Vertex Array se fait en trois ?apes principales. La premi?e consiste en l'activation / d?activation des types de tableaux ?utiliser. Ensuite, il faut remplir les tableaux activ? avec les informations de notre g?m?rie. Enfin, on peut lancer l'appel du rendu de notre g?m?rie.

La premi?e chose ?faire pour utiliser les VertexArray est d'en activer l'utilisation. Il faut ?alement les d?activer quand on n'en a plus besoin. Pour cela, on utilise deux fonctions :

Prototype de la fonction | Description
void glEnableClientState(GLenum type); | Sp?ifie le type de tableau ?activer
void glDisableClientState(GLenum type); | Sp?ifie le type de tableau ?d?activer

Le type de tableau est ?choisir parmi GL_COLOR_ARRAY, GL_EDGE_FLAG_ARRAY, GL_FOG_COORD_ARRAY, GL_INDEX_ARRAY, GL_NORMAL_ARRAY, GL_SECONDARY_COLOR_ARRAY, GL_TEXTURE_COORD_ARRAY et GL_VERTEX_ARRAY.

La seconde ?ape consiste ?indiquer ?OpenGL o?se trouvent les donn?s ?utiliser pour dessiner notre g?m?rie. On va donc cr?r un tableau local qui sera directement utilis?par OpenGL. Il existe ainsi une fonction par type d'information pour ex?uter cette ?ape.

Prototype de la fonction | Description
void glVertexPointer(GLint taille, GLenum type, GLsizei stride, const GLvoid * pointeur); | Sp?ifie l'emplacement du tableau de coordonn?s de vertex. Les donn?s sont au nombre de taille par vertex, de type type et espac?s de stride octets entre deux vertex.
void glColorPointer(GLint taille, GLenum type, GLsizei stride, const GLvoid * pointeur); | Sp?ifie l'emplacement du tableau de couleurs principales par vertex.
void glSecondaryColorPointer(GLint taille, GLenum type, GLsizei stride, const GLvoid * pointeur); | Sp?ifie l'emplacement du tableau de couleurs secondaires par vertex.
void glIndexPointer(GLenum type, GLsizei stride, const GLvoid * pointeur); | Sp?ifie l'emplacement du tableau de couleurs index?s par vertex.
void glNormalPointer(GLenum type, GLsizei stride, const GLvoid * pointeur); | Sp?ifie l'emplacement du tableau des normales par vertex.
void glFogCoordPointer(GLenum type, GLsizei stride, const GLvoid * pointeur); | Sp?ifie l'emplacement du tableau des coordonn?s de brouillard par vertex.
void glTexCoordPointer(GLint taille, GLenum type, GLsizei stride, const GLvoid * pointeur); | Sp?ifie l'emplacement du tableau des coordonn?s de texture par vertex. Dans le cas de Multitexturage, les coordonn?s correspondent ?la texture active
void glEdgeFlagPointer(GLsizei stride, const GLvoid * pointeur); | Sp?ifie l'emplacement du tableau d'indicateur de contours par vertex.

On peut remarquer que certaines fonctions ne prennent pas de param?re taille car certaines informations ont un nombre de composantes fixes. Le param?re stride peut ?re diff?ent de z?o, par exemple, dans le cas d'un tableau unique pour les couleurs primaires et secondaires.

Une fois que nous avons indiqu?o?se situaient toutes les informations de notre g?m?rie, il existe donc plusieurs fa?ns de d?rire notre g?m?rie gr?e ?diff?entes fonctions.

Prototype de la fonction | Description
void glArrayElement(GLint index); | Doit ?re plac?entre glBegin et glEnd. R?up?e les informations du vertex d'index index dans tous les tableaux activ?
void glDrawElements(GLenum mode, GLsizei taille, GLenum type, void * indices); | Cr? automatiquement une g?m?rie avec le type de primitive mode, compos? des vertex des taille index contenu dans le tableau indices de type type
void glMultiDrawElements(GLenum mode, GLsizei taille, GLenum type, void ** indices, GLsizei taille_primitive); | Correspond ?l'appel de taille_primitive fois la fonction glDrawElements o?indice est un tableau ?2 dimensions. Chaque sous-tableau est pass?en param?re ?chaque appel.
void glDrawArrays(GLenum mode, GLint premier, GLsizei taille); | Utilise l'ensemble des vertex des diff?ents tableaux pour dessiner la g?m?rie utilisant le type de primitive mode et les taille indices en commen?nt par le premier
void glMultiDrawArrays(GLenum mode, GLint * premier, GLsizei * taille, GLsizei taille_primitive); | Correspond ?l'appel de taille_primitive fois la fonction glDrawArrays o?premier et taille contiennent les param?res de chaque appel.

glArrayElement correspond au mode "Imm?iat" donc donne des performances identiques.

On peut remarquer deux types de fonctions : gl*Elements qui r?up?ent chaque vertex ind?endamment dans les tableaux d'informations, gl*Arrays qui utilisent l'ensemble des vertex des tableaux. Pour utiliser un tableau unique entrelac?/i>, on utilise la fonction glInterleavedArrays.

III-B. Compl?ent par les "Display List"

Nous avons vu que les commandes OpenGL pouvaient ?re nombreuses et surtout r??itives entre deux frames. OpenGL offre la possibilit?de pallier ?ce probl?e par l'enregistrement de commandes successives pr?ompil?s. Cette technologie, appel? "Display List", est tout ?fait utilisable pour le dessin imm?iat ainsi que pour les Vertex Array. Le principe d'utilisation des listes est d'abord de cr?r un identifiant par liste, de cr?r la liste et de l'appeler ?chaque fois que l'on en a besoin.

Prototype de la fonction | Description
GLuint glGenLists(GLsizei nombre); | Cr? nombre identifiants s?uentiels de listes d'affichages disponibles.
void glNewList(GLuint index, GLenum mode); | Sp?ifie le d?ut de la cr?tion de la liste d'identifiant index o?mode est d?ini parmi GL_COMPILE, qui compile seulement la liste et GL_COMPILE_AND_EXECUTE, qui compile et ex?ute la liste.
void glEndList(void); | Sp?ifie la fin de la liste courante.
void glCallList(GLuint index); | Appelle les commandes de la liste index pr?ompil?.
void glCallLists(GLsizei nombre, GLenum type, const GLvoid * liste); | Appelle les commandes successives des lites dont les nombre identifiants sont de type type dans le tableau liste.
void glDeleteLists(GLuint premiere, GLsizei nombre); | Supprime les nombre commandes successives en commen?nt par l'identifiant premiere.

III-C. Pour notre exemple

L'exemple du cube est tr? simple pour appliquer tout ce que nous avons vu pr??emment. Nous allons donc d?inir nos 8 vertex dans des tableaux de vertex et de couleurs. Nous allons ensuite d?inir un tableau des index successifs des vertex ?utiliser pour des primitives de type GL_TRIANGLES. Nous choisissons ici le cas simple sans "Display List" et avec la fonction glDrawElements pour optimiser la quantit?d'information envoy? ?OpenGL.

Nous remarquons tout de suite que le nombre d'appels de fonction a bien ??r?uit puisqu'il passe de 74 pour le mode imm?iat ?7 pour les vertex array. De plus, la quantit?d'information a diminu?car elle est pass? de 216 pour le mode imm?iat ?84 ( 8 vertex * 3 composantes * 2 informations + 36 indices ) valeurs pour les vertex array. Par contre, reste le probl?e que toutes les informations sont envoy?s ?chaque frame de notre application alors que ce ne serait pas n?essaire pour une g?m?rie statique (sauf par l'utilisation des Display List).

Rendu d'un cube avec VertexArray
S?ectionnez

GLfloat VertexArray[24] = {
    -1.0f, 1.0f, -1.0f,
    -1.0f, -1.0f, -1.0f,
    -1.0f, 1.0f, 1.0f,
    -1.0f, -1.0f, 1.0f,
    1.0f, 1.0f, 1.0f,
    1.0f, -1.0f, 1.0f,
    1.0f, 1.0f, -1.0f,
    1.0f, -1.0f, -1.0f
};
GLfloat ColorArray[24] = {
    1.0f, 0.0f, 0.0f,
    1.0f, 0.0f, 1.0f,
    1.0f, 1.0f, 1.0f,
    0.0f, 0.0f, 1.0f,
    0.0f, 1.0f, 0.0f,
    0.0f, 1.0f, 1.0f,
    1.0f, 1.0f, 0.0f,
    1.0f, 1.0f, 1.0f
};
GLuint IndiceArray[36] = {
    0,1,2,2,1,3,
    4,5,6,6,5,7,
    3,1,5,5,1,7,
    0,2,6,6,2,4,
    6,7,0,0,7,1,
    2,3,4,4,3,5
};
glEnableClientState( GL_VERTEX_ARRAY );
glEnableClientState( GL_COLOR_ARRAY );
glVertexPointer( 3, GL_FLOAT, 0, VertexArray );
glColorPointer( 3, GL_FLOAT, 0, ColorArray );
glDrawElements( GL_TRIANGLES, 36, GL_UNSIGNED_INT, IndiceArray );
glDisableClientState( GL_COLOR_ARRAY );
glDisableClientState( GL_VERTEX_ARRAY );

III-D. Avantages / Inconv?ients de cette technique

Nous avons pu voir que les techniques des "Vertex Array" et des "Display List" pouvaient grandement am?iorer le rendu de g?m?rie avec OpenGL. Pour les "Vertex Array", nous avons vu qu'il en existait deux types : sans ou avec partage de vertex (glDrawArrays vs glDrawElements). La version sans partage de vertex (glDrawArrays) a ??d?onseill? par Nvidia ainsi que par ATI. Il est pr??able d'utiliser la version glDrawElements qui permet de cr?r un cache de vertex uniques et, gr?e ?un tableau d'indices, de partager le vertex pour une m?e g?m?rie. Nous pouvons en d?uire un certain nombre d'avantages :

  • R?uction du nombre d'appels de fonction
  • R?uction de la quantit?d'information envoy? ?la carte graphique
  • Pr?ompilation d'une s?ie de commandes OpenGL

A cela, nous pouvons ajouter quelques optimisations possibles qu'offrent les "Vertex Array" :

  • Utilisation du type GL_UNSIGNED_SHORT (16bit) ?la place du type GL_UNSIGNED_INT (32bit) (optimisation m?oire et temps de transfert)
  • Utilisation de GL_TRIANGLE_STRIP (1/3 d'indices en moins en moyenne)
  • Dans le cas d'information couleur, il est conseill?d'utiliser GL_UNSIGNED_BYTE ou GL_FLOAT (optimisation de conversion des donn?s)
  • Dans le cas d'information de normale, il est conseill?d'utiliser GL_FLOAT, mais d?onseill?d'utiliser GL_BYTE.

Malgr?la r?olution de certains probl?es par rapport au dessin "imm?iat", on conserve certains inconv?ients avec cette technique. Voici une liste des probl?es les plus remarquables :

  • Espace m?oire occup?par les "Display List" compil?s
  • Temps de compilation des "Display List"
  • Applicable uniquement pour une g?m?rie statique

Malgr?tous les gains apport? par ces nouvelles technologies, il reste quelques probl?es assez importants. Tout d'abord, les "Display List" permettent de pr?ompiler des commandes OpenGL mais cette pr?ompilation a un surco? m?oire ainsi qu'un surco? en temps. Le surco? m?oire provient de l'enregistrement des commandes elles m?es, l'id?l serait de stocker uniquement les informations de notre g?m?rie. De plus, ces deux techniques associ?s obligent ?dessiner uniquement une g?m?rie statique non-modifiable. Si on utilise uniquement les "VertexArray" sans les "DisplayList", le probl?e de l'envoi r??itif des informations (comme dans notre exemple) r?ppara?. OpenGL propose donc une solution ?tout cela et cette solution s'appelle les "Vertex Buffer Object".

IV. Nouvelle technologie : les Vertex Buffer Object (VBO)

IV-A. Par o?commencer ?

Nous avons donc vu que les "VertexArray", accompagn? des "DisplayList", ?aient une grande avanc? dans le rendu de g?m?rie. Mais il persiste quelques probl?es assez importants notamment dans les surco?s m?oire et temps de ces technologies. Pour cela, apr? l'apparition des extensions NV_vertex_array_range et ATI_vertex_array_object par, respectivement, Nvidia et ATI et l'approbation par l'ARB (Architecture Review Board) de l'extension ARB_vertex_buffer_object, a ??cr? la technologie des VertexBufferObject (VBO). C'est ainsi que depuis les sp?ifications de la version 1.5 d'OpenGL est int?r? cette technologie qui permet de stocker directement en m?oire graphique toute notre g?m?rie ?dessiner. Grace ?un proc??proche des "VertexArray", les VBO permettent d'envoyer et de stocker toutes les informations de notre g?m?rie dans notre carte graphique. Ce principe, utilis?pour les textures, est maintenant utilis?pour la g?m?rie.

Sp?ification de l'extension GL_ARB_vertex_buffer_object Sp?ification de l'extension GL_ARB_vertex_buffer_object

Pour utiliser ces buffers, il faut d'abord commencer par demander ?OpenGL un ou plusieurs identifiants pour ces buffers. Cet identifiant sera utilis?pour signaler ?OpenGL que nous voulons utiliser le buffer de la g?m?rie correspondante. Nous pouvons, bien ?idemment, utiliser et enregistrer plusieurs buffers pour notre application, tant qu'il y a suffisamment de m?oire dans la carte graphique.

Prototype de la fonction | Description
void glGenBuffers(GLsizei nombre, Guint * buffers); | Cr? nombre identifiants de buffer et les place dans le bloc m?oire commen?nt par buffers (??ent simple ou tableau)
void glBindBuffer(GLenum type, GLuint buffer); | Sp?ifie que l'on utilise le buffer d'identifiant buffer comme buffer courant et qu'il est de type type. type est ?choisir parmi GL_ARRAY_BUFFER et GL_ELEMENT_ARRAY_BUFFER.
void glDeleteBuffers(GLsizei nombre, const GLuint * buffers); | Supprime les nombre buffers de la liste buffers.

GL_ARRAY_BUFFER correspond aux tableaux des informations sur les vertex et GL_ELEMENT_ARRAY_BUFFER correspond aux tableaux des indices de vertex.

L'?ape suivante est de remplir notre buffer avec les informations sur nos vertex. Nous allons donc envoyer des tableaux de donn?s ?notre carte graphique qui va remplir sa m?oire comme elle le ferait pour une texture. C'est dans cette partie que nous allons d?inir quel type d'utilisation nous allons faire de notre buffer par l'interm?iaire du param?re utilisation.

Prototype de la fonction | Description
void glBufferData(GLenum type, GLsizeiptr taille, const GLvoid * donnees, GLenum utilisation); | Cr? et initialise le buffer actif de type type avec les taille informations contenues dans le tableau donnees. L'utilisation de ce buffer sera d?inie par utilisation
void glBufferSubData(GLenum type, GLintptr decalage, GLsizeiptr taille, const GLvoid * donnees); | Met ?jour le buffer de type type ?partir de la zone commen?nt par decalage avec les taille informations contenues dans donnees.

IV-B. Diff?ents cas d'utilisations

Les VBO supporte diff?ents types d'utilisations qui peuvent ?re diff?ents pour chaque buffer. Les cas d'utilisations se d?inissent par deux crit?es qui sont la fr?uence avec laquelle nous allons mettre ?jour les informations et la fa?n dont nous allons acc?er aux donn?s. Pour chacun des param?res, il existe trois cat?ories diff?entes. Dans le cas de la fr?uence d'utilisation, elle peut ?re :

  • STATIC : les donn?s seront modifi?s une fois pour de nombreuses utilisations
  • STREAM : les donn?s seront modifi?s une fois pour quelques utilisations
  • DYNAMIC : les donn?s seront modifi?s et utilis?s r?uli?ement de nombreuses fois

Une fois que nous avons d?ermin?cette fr?uence, il faut choisir un type d'utilisation de notre buffer. Il existe trois types diff?ents ?choisir parmi :

  • DRAW : les donn?s sont modifi?s par l'application et utilis?s par OpenGL pour le rendu d'images (le plus utilis?
  • READ : les donn?s sont modifi?s par une lecture de donn?s depuis OpenGL et utilis?s en lecture par l'application.
  • COPY : les donn?s sont modifi?s par une lecture de donn?s depuis OpenGL et utilis?s par OpenGL pour le rendu d'images

A partir de ces deux param?res, nous pouvons d?erminer quel type de buffer nous allons utiliser et quel param?re passer ?la fonction glBufferData. Nous avons donc neuf possibilit?, donc neuf valeurs diff?entes :

 | STATIC | STREAM | DYNAMIC
DRAW | GL_STATIC_DRAW | GL_STREAM_DRAW | GL_DYNAMIC_DRAW
READ | GL_STATIC_READ | GL_STREAM_READ | GL_DYNAMIC_READ
COPY | GL_STATIC_COPY | GL_STREAM_COPY | GL_DYNAMIC_COPY

Ce param?re est tr? important car OpenGL va se charger seul d'organiser sa m?oire en fonction du type d'utilisation. Les utilisations du type GL_*_DRAW sont les plus utilis?s pour dessiner une g?m?rie car les informations proviennent de l'application et non d'OpenGL. Ensuite, les utilisations de type GL_STATIC_* vont plut? s'appliquer ?une g?m?rie qui ne va jamais ?re modifi? au cours de l'application comme des b?iments, des statues, ... Les utilisations GL_STREAM_* vont s'appliquer g??alement ?une g?m?rie qui est modifi? une seule fois juste avant un rendu. Ceci est tr? proche du type GL_DYNAMIC_* qui est pr?u pour de nombreuses modifications de donn?s avant une seule utilisation. Ces deux derni?es utilisations peuvent s'appliquer, par exemple, ?des applications d'animations de personnages (skinning), ?des objets d?ormables par des simulations physiques, ...

IV-C. Utiliser un buffer

Maintenant que nous avons cr? notre buffer, il faudrait pouvoir le modifier et l'utiliser tout au long de notre application. OpenGL offre la possibilit?d'acc?er directement aux donn?s du buffer actif. Cet acc? se fait par les deux fonctions :

Prototype de la fonction | Description
void * glMapBuffer(GLenum type, GLenum acces); | Retourne le pointeur vers les donn?s du buffer courant de type type autorisant un acc? de type acces parmi GL_READ_ONLY (lecture seule), GL_WRITE_ONLY (?riture seule), et GL_READ_WRITE (lecture et ?riture).
void glUnmapBuffer(GLenum type); | Signifie ?OpenGL que l'acc? au buffer courant de type type est termin?

Pour utiliser les donn?s contenues dans le buffer, nous allons utiliser les m?es fonctions que pour les "Vertex Array". Il faut donc commencer par activer le buffer correspondant ?notre g?m?rie gr?e ?la fonction glBindBuffer. Ainsi les fonctions glVertexPointer, glColorPointer, glSecondaryColorPointer, glNormalPointer, glFogCoordPointer, glTexCoordPointer et glEdgeFlagPointer vont-elles ?re utilis?s pour les buffers de type GL_ARRAY_BUFFER. La seule diff?ence sera qu'aucun tableau de donn?s ne sera utilis?mais sera remplac?par un d?alage m?oire dans notre buffer par rapport ?son d?ut. Nous pouvons donc cr?r un VBO par type d'information et par g?m?rie mais ?alement un seul VBO pour toutes les informations de notre g?m?rie. Voici quelques exemples d'utilisation :

Quelques exemples
S?ectionnez

glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), 0);
glColorPointer(3, GL_FLOAT, 3 * sizeof(float), ((void*)NULL + (3)));

IV-D. Pour notre exemple

Nous allons donc ?udier notre exemple. Nous devons dessiner un cube color?qui ne changera jamais. Aucune information ne sera modifi? ainsi nous pourrons utiliser un buffer de type GL_STATIC_DRAW. Nous allons donc stocker toutes nos informations dans un tableau unique qui sera stock?une seule fois ?l'initialisation :

Initialisation de notre VBO
S?ectionnez

GLfloat CubeArray[48] = {
    1.0f, 0.0f, 0.0f, -1.0f, 1.0f, -1.0f,
    1.0f, 0.0f, 1.0f, -1.0f, -1.0f, -1.0f,
    1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f,
    0.0f, 0.0f, 1.0f, -1.0f, -1.0f, 1.0f,
    0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f,
    0.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f,
    1.0f, 1.0f, 0.0f, 1.0f, 1.0f, -1.0f,
    1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f
};
GLuint IndiceArray[36] = {
    0,1,2,2,1,3,
    4,5,6,6,5,7,
    3,1,5,5,1,7,
    0,2,6,6,2,4,
    6,7,0,0,7,1,
    2,3,4,4,3,5
};
// G??ation des buffers
glGenBuffers( 2, CubeBuffers );
// Buffer d'informations de vertex
glBindBuffer(GL_ARRAY_BUFFER, CubeBuffers[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(CubeArray), CubeArray, GL_STATIC_DRAW);
// Buffer d'indices
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, CubeBuffers[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(IndiceArray), IndiceArray, GL_STATIC_DRAW);

Une fois cr?, nous pouvons l'utiliser ?chaque frame. Nous devons sp?ifier les buffers ?utiliser ainsi que l'emplacement de chaque information.

Rendu du cube avec notre VBO
S?ectionnez

// Utilisation des donn?s des buffers
glBindBuffer(GL_ARRAY_BUFFER, CubeBuffers[0]);
glVertexPointer( 3, GL_FLOAT, 6 * sizeof(float), ((float*)NULL + (3)) );
glColorPointer( 3, GL_FLOAT, 6 * sizeof(float), 0 );
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, CubeBuffers[1]);
// Activation d'utilisation des tableaux
glEnableClientState( GL_VERTEX_ARRAY );
glEnableClientState( GL_COLOR_ARRAY );
// Rendu de notre g?m?rie
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
glDisableClientState( GL_COLOR_ARRAY );
glDisableClientState( GL_VERTEX_ARRAY );

Nous pouvons remarquer que d'un point de vue m?oire, seules les informations de notre g?m?rie seront stock?s contrairement aux "Display List" qui enregistrent ?alement toute la succession des commandes OpenGL. De plus, le temps demand?par la compilation des "Display List" n'est plus n?essaire car on se contente d'envoyer directement notre g?m?rie. Enfin, gr?e au mapping de buffer, on peut uniquement modifier les donn?s des g?m?ries dynamiques et nous ne sommes plus oblig? de nous contenter des formes statiques.

Conclusion

Nous avons donc vu les diff?entes optimisations qu'a apport?s OpenGL au rendu de g?m?rie. Chacune des techniques a ses avantages propres mais les "Vertex Array" et les "Vertex Buffer Object" ont pour objectif de combler les lacunes des pr??entes. A titre d'exemple, sur une carte Nvidia GeForce 7600 GS, les dessins de notre cube nous donnent les performances suivantes :

Imm?iat | Vertex Array | Vertex Buffer Object
1445 FPS | 1485 FPS | 1515 FPS

Cet exemple n'est pas vraiment significatif car il se contente de dessiner 12 triangles mais l'on remarque d??une l??e diff?ence en faveurs de VBO. Pour augmenter les performances de votre application OpenGL, vous pouvez trouver quelques informations dans notre FAQ :

FAQ Optimisation OpenGL FAQ Optimisation OpenGL

Une des derni?es extensions d'OpenGL ( GL_EXT_draw_instanced) permet le dessin de type "instancing". Cette extension permet de dessiner plusieurs fois le m?e objet avec un seul appel de fonction.

Pour toutes questions, n'h?itez pas ?demander directement sur le forum OpenGL Pour toutes questions, n'h?itez pas ?demander directement sur le forum OpenGL


Le guide du routard dans OpenGL 1.2

Orion (F. Gasser)

Gritche (J-F. Herouard)

Sommaire

1. Introduction______________________________________________________________________ 4

1.1. Qu’est ce que OpenGL__________________________________________________________ 4

1.2. Les bonnes adresses____________________________________________________________ 5

1.3. Les bons documents____________________________________________________________ 5

2. GLUT, l’OpenGL Utility Toolkit_______________________________________________________ 5

2.1. Un exemple__________________________________________________________________ 6

2.2. L’initialisation________________________________________________________________ 6

2.3. Les callbacks_________________________________________________________________ 7

2.4. Travail en background_________________________________________________________ 7

2.5. Les fonctions de dessin 3D évoluées_______________________________________________ 7

2.6. Exécuter le programme________________________________________________________ 8

3. Initialisation de la vue______________________________________________________________ 8

3.1. Exemple d’initialisation 2D______________________________________________________ 8

3.2. Présentation des matrices d’OpenGL______________________________________________ 8

4. Dessiner__________________________________________________________________________ 9

4.1. Un classique : le triangle________________________________________________________ 9

4.2. Les Buffers__________________________________________________________________ 9

4.3. Bouger le triangle, utilisation de la matrice de vue__________________________________ 12

5. Dessiner en 3D____________________________________________________________________ 13

5.1. Initialisation________________________________________________________________ 13

5.2. Exemple : un Cube____________________________________________________________ 14

5.3. Depth-Buffer________________________________________________________________ 15

6. Paramètres du rendering____________________________________________________________ 15

6.1. Antialiasing_________________________________________________________________ 15

6.2. Culling_____________________________________________________________________ 15

7. Eclairage_______________________________________________________________________ 16

7.1. Activation__________________________________________________________________ 16

7.2. Paramètres_________________________________________________________________ 17

7.3. Fonctions OpenGL___________________________________________________________ 18

7.4. Les normales________________________________________________________________ 20

7.5. Exemple____________________________________________________________________ 21

8. Fog (brouillard)__________________________________________________________________ 22

8.1. Le fonctionnement du brouillard________________________________________________ 22

8.2. L’utilisation du brouillard_____________________________________________________ 22

8.3. Les équations________________________________________________________________ 22

9. Transparence et alpha-blending______________________________________________________ 23

9.1. Activation__________________________________________________________________ 23

9.2. Exemple____________________________________________________________________ 23

9.3. Paramètres_________________________________________________________________ 23

9.4. Ordre des primitives et depth-buffer______________________________________________ 24

10. Listes________________________________________________________________________ 25

10.1. Commandes OpenGL_______________________________________________________ 25

10.2. Exemple_________________________________________________________________ 25

11. Textures______________________________________________________________________ 26

11.1. Principe général___________________________________________________________ 26

11.2. En OpenGL_______________________________________________________________ 26

11.3. Exemple_________________________________________________________________ 28

11.4. MipMapping______________________________________________________________ 29

11.5. Chargement dans la carte vidéo_______________________________________________ 29

11.6. Exemple_________________________________________________________________ 30

11.7. Priorités des textures_______________________________________________________ 30

12. Les opérations sur les pixels_______________________________________________________ 31

12.1. Introduction______________________________________________________________ 31

12.2. La Raster Position_________________________________________________________ 31

12.3. Dessiner le bitmap__________________________________________________________ 31

12.4. Lire, écrire et copier des images_______________________________________________ 32

12.5. Agrandissement, réduction et réflexion_________________________________________ 33

13. Tests de validation et stencil buffer__________________________________________________ 33

13.1. Chaîne de tests_____________________________________________________________ 33

13.2. Scissor test________________________________________________________________ 33

13.3. Alpha test________________________________________________________________ 33

13.4. Stencil test________________________________________________________________ 33

14. Ombres et reflets________________________________________________________________ 35

14.1. Reflets___________________________________________________________________ 35

14.2. Ombres__________________________________________________________________ 38

15. Courbes et Surfaces calculées______________________________________________________ 41

15.1. Présentation______________________________________________________________ 41

15.2. Quadrics_________________________________________________________________ 41

15.3. Bézier___________________________________________________________________ 42

16. Ce qui n’a pas de place ailleurs____________________________________________________ 45

16.1. Dessiner deux fois au même endroit____________________________________________ 45

16.2. Clipping_________________________________________________________________ 45

16.3. Multitexturing_____________________________________________________________ 46

1. Introduction

Qu’est ce que OpenGL

OpenGL est une interface de programmation vers le matériel graphique. Cette interface compte environ 150 commandes qui sont utilisées dans les programmes interactifs en 3D. Etant donné que cette API est à l’origine professionnelle, il y a beaucoup de fonctions qui ne sont toujours pas câblées sur les cartes accélératrices 3D pour le grand public, et qui seront donc émulées en software.

La première particularité d’OpenGL est que la programmation est indépendante du matériel graphique utilisé, et que son domaine d’utilisation n’est pas confiné aux PC/Windows ou Mac. A l’origine développé par Silicon Graphics, OpenGL est aujourd’hui disponible sur les plates-formes x86 et 68000 entre autres, que ce soit sous Windows, Linux ou Mac. La conséquence immédiate est qu’aucune commande de fenêtrage, ou d’une manière générale toute commande dépendant de l’OS, n’est incluse et que seuls des commandes graphiques de bas niveaux existent (dessiner un point, un segment mais pas de sphère par exemple). Tout objet complexe est composé à partir d’un nombre limité de primitives graphiques.

Deuxièmement, OpenGL se base toujours sur d’autres librairies graphiques qui sont en général spécifiques au système d’exploitation ou qui apportent des fonctions complexes par dessus la librairie OpenGL. L’OpenGL Utility Library (GLU) ainsi que l’OpenGL Utility Toolkit (GLUT) en sont des exemples.

Une autre particularité est que certaines implémentations d’OpenGL sont conçues pour que la station sur laquelle on affiche ne soit pas celle sur laquelle tourne le programme. Cette fonctionnalité n’est utilisé que sur les systèmes basés sur l’X, et ne sera donc pas détaillée ici (en gros, cela est possible car OpenGL utilise une architecture client (un programme) – serveur (la carte vidéo et son driver OpenGL)).

Silicon Graphics n’a pas implémenté de version pour Linux de OpenGL. Il convient donc d’utiliser Mesa, qui est une librairie compatible OpenGL (attention, la compatibilité n’est ni garantie ni totale, .... Gritche tire une sale gueule la parce qu’il est sûr que Mesa 3.1 est compatible OpenGL 1.2, il dit aussi que ça s’appelle Mesa parce que l’emploi du nom OpenGL™ n’est pas possible en GNU).

Les bonnes adresses

Voici quelques bonnes adresse à visiter pour avoir accès à des informations complémentaires et aux manuels de référence :

· OpenGL :

http://www.opengl.org : news, documentation, liens…

http://trant.sgi.com/opengl : des exemples, des astuces

http://reality.sgi.com/opengl : idem

· GLU :

http://www.opengl.org/Documentation/GLX.html

· GLUT :

http://reality.sgi.com/mjk/spec3/spec3.html

· Mesa :

http://www.mesa3d.org

· AGL (Extension OpenGL pour Apple) :

http://www.sd.tgs.com/Products/opengl.htm

· PGL (Extension OpenGL pour IBM OS/2 Warp

http://www.austin.ibm.com/software/OpenGL/

Sur la machine de Game, il y a déjà dans les 250 Mo de download provenant de ces sites, alors avant de dans lancer un wget sauvage à l’école, vérifiez :-). En plus les exemples sont déjà compilés.

Si vous trouvez quelque chose d’intéressant qui n’est pas sur la machine, n’hésitez pas à en faire part à tout le monde.

Les bons documents

Il y a quelques documents de références en ligne qui sont bons à avoir : opengl1.2.pdf, glu1.3.pdf, glut.spec.ps et les man pages d’OpenGL en Html (une page html par fonction).

2. GLUT, l’OpenGL Utility Toolkit

OpenGL ne s’occupant pas des opérations relatives au système ou au fenêtrage, il laisse la place à d’autres API, dont GLUT fait parti. Il existe des implémentations de GLUT pour Windows NT, X-Window et OS/2, ce qui permet de compiler le même code source sur toutes les plates-formes.

GLUT est une interface entre le programme et l’humanoïde qui se trouve derrière l’écran, la souris, ou le joystick. Sa programmation est de type événementielle et chaque agitation de l’humain est considéré comme un événement qui sera géré par des fonctions personnalisées (callback).

Un exemple

Voici un premier exemple de programme utilisant OpenGL :

#include <GL/glut.h>

int main(int argc, char** argv)

{

Glint Xscreen = 800;

Glint Yscreen = 600;

glutInit(&argc, argv);

glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);

glutInitWindowSize (Xscreen, Yscreen);

glutCreateWindow (argv[0]);

glutDisplayFunc (display);

glutReshapeFunc (reshape);

glutIgnoreKeyRepeat(1);

glutKeyboardFunc(keyboard);

glutKeyboardUpFunc(keyboardup);

glutSpecialFunc(special);

glutSpecialUpFunc(specialup) ;

glutMouseFunc(mouse);

glutMotionFunc(motion);

glutPassiveMotionFunc(motion);

glutIdleFunc (IntelligenceArtificielle);

glutMainLoop ();

return(0);

}

L’initialisation

On remarque que l’on fait plusieurs appels à des fonctions GLUT pour créer une fenêtre. Dans l’ordre, on a :

· glutInit(int *argc, char **argv) : A être appelé avant tout autre fonction GLUT. Cette fonction gere sous X les paramètres tels que –display et –geometry.

· glutInitDisplayMode(unsigned int mode) : différencie les mode RGBA et couleur indexé, ainsi que le double-buffering et les buffers à utiliser (depth, stencil, accumulation), que nous étudierons par la suite

· glutInitWindowPostion(int x, int y) : no comment

· glutInitWindowSize(int width, int size) : no comment

· int glutCreatWindow(char *string) : crée la fenêtre (et un contexte OpenGL) avec tous les paramètres spécifiés avant. Elle renvoie un identifiant unique pour la nouvelle fenêtre. Attention : la fenêtre ne sera pas affichée avant l’appel à glutMainLoop()

Les callbacks

Viennent ensuite les « callback ». Il s’agit de spécifier quels sont les fonctions à appeler suivant les événements qui se produisent. On peut changer les fonctions appelées à n’importe quel moment. Si on spécifie la valeur NULL comme adresse de fonction à utiliser, cela a pour effet de désactiver le callback correspondant. Elles sont par défaut à NULL. Les plus utilisés des callback sont :

· glutDisplayFunc(void(*func)(void)) : indique la fonction à utiliser lors de l’affichage graphique.

· glutReshapeFunc(void(*func)(int w, int h)) : si la fenêtre est redimentionnée

· glutKeyboardFunc(void(*func)(unsigned char key, int x, int y)) et glutMouseFunc(void(*func)(int button, int state, int x, int y)) : événement clavier et souris

· glutMotionFunc(void (*func)(int x, int y))

Travail en background

Lorsque le programme n’a plus rien a faire, ie l’affichage a été traité, et les callback aussi, on peut spécifier de rentrer dans un fonction qui peut se charger par exemple de faire évoluer le monde virtuel. On utilise alors la fonction :

· GlutIdleFunc(void (*func)(void)) : comme d’hab, NULL passé en paramètre désactive cette fonction.

Les fonctions de dessin 3D évoluées

GLUT peut se charger de dessiner des objets 3D centrés sur l’origine en mode fil de fer (wire) ou solide (solid). Les objets sont :

cone icosahedron teapot

cube octahedron tetrahedron

dodecahedron sphere torus

Pour dessiner un cube ou une sphère, on utilise par exemple les fonctions :

void glutWireCube(Gldouble size) ;

void glutSolidCube(Gldouble size) ;

void glutWireSphere(Gldouble radius, Glint slices, Glint stacks) ;

void glutSolidSphere(Gldouble radius, Glint slices, Glint stacks) ;

Exécuter le programme

Pour exécuter le programme et voir réellement quelque chose s’afficher après l’initialisation, il faut appeler la fonction :

· glutMainLoop(void) Ici, on rentre dans la boucle infinie d’exécution. Ca doit être à peut de choses prêt la dernière ligne du programme. On ne pourra sortir de cette boucle que par un exit().

3. Initialisation de la vue

Exemple d’initialisation 2D

OpenGL est plus connu pour son utilisation 3D, mais il est aussi prévu pour fonctionner en 2D. Comme c’est un peu plus facile à appréhender, on commence par là.

Une fois que le « contexte » OpenGL est crée, il reste à créer la vue. Cela se fait ainsi :

GLsizei width = cx, height = cy; // width, height : taille de la fenêtre

GLdouble aspect;

if (cy == 0)

aspect = (GLdouble)width;

else

aspect = (GLdouble)width/(GLdouble)height;

glMatrixMode(GL_PROJECTION); // initialisation de la matrice de projection

glLoadIdentity();

gluOrtho2D(-1.0f * aspect, 1.0f * aspect, -1.0f, 1.0f);

glMatrixMode(GL_MODELVIEW); // initialisation de la matrice de vue

glLoadIdentity();

Il s’agit ici d’une initialisation d’une vue 2D.

Présentation des matrices d’OpenGL

En OpenGL, toutes les transformations géométriques sont entrées sous forme de matrice, la vue est définie par la position de la caméra (matrice de changement de repère) et par la matrice de projection.

La matrice de projection s’appelle GL_PROJECTION. Il n’est possible d’accéder qu’à une matrice à la fois, donc pour modifier une matrice, il faut d’abord l’activer avec glMatrixMode. La matrice choisie devient la « matrice courante ». Il reste à définir la matrice de projection, mais inutile de se revoir le cours de prépa, car heureusement des fonctions de glu permettent de spécifier les matrices les plus courantes. La fonction

void gluOrtho2D( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top )

permet dans l’exemple de mettre une matrice de projection 2D de la dimension voulue.

GlLoadIdentity initialise la matrice à l’identité.

La matrice GL_MODELVIEW est en quelque sorte la matrice de positionnement de la vue. C’est le plus souvent une matrice de changement de repère. Il faut la considérer comme une matrice qui positionne les objets dans le repère du monde, et éviter de la considérer comme la position de la caméra, même si c’est aussi son rôle.

Il existe une troisième matrice, GL_TEXTURE, mais celle-ci n’est jamais utilisée directement.

Toutes sont des matrices 4x4 de float.

4. Dessiner

Un classique : le triangle

Les fonctions suivantes permettent de dessiner un triangle à l’écran :

glClear(GL_COLOR_BUFFER_BIT); // efface l’écran

glBegin(GL_POLYGON); // début d’un dessin d’un polygone

glColor3f(1.0f, 0.0f, 0.0f); // changement de couleur

glVertex2f(-0.5f, -0.5f); // entrée d’une coordonnée

glColor3f(0.0f, 1.0f, 0.0f);

glVertex2f( 0.5f, -0.5f);

glColor3f(0.0f, 0.0f, 1.0f);

glVertex2f( 0.5f, 0.5f);

glEnd(); // fin du polygone

glFlush(); // affichage

Ce qui donne (imprimé en noir & blanc c’est moche, mais tant pis) :

Les Buffers

Il existe dans OpenGL des « buffers », qui d’une manière générale ne sont que des espaces mémoires contenant des données propres à l’OpenGL.

Le principal est le « COLOR_BUFFER », qui correspond à ce que l’on voit sur l’écran.

La fonction glClear permet de remplir tout un buffer avec la même valeur. La valeur par défaut est 0. Un appel à glClear sur le COLOR_BUFFER rempli l’écran de noir. GlClearColor permet de changer la couleur de remplissage.

Un autre buffer, le back-buffer, fonctionne différemment : pour ne pas voir se dessiner la scène à l’écran, on utilise la technique du « double-buffering ». Le COLOR_BUFFER est en fait dédoublé : l’un est affiché (front-buffer), l’autre est celui sur lequel on travaille (back-buffer). A chaque fois qu’une image est calculée, on inverse (swap) les buffers. Pour le programmeur, tout marche comme s’il dessinait toujours sur le même buffer. L’avantage est que l’utilisateur ne vois pas se dessiner l’image.

Les autres buffers standards sont le Depth-buffer, le Stencil-buffer, l’Accumulation-buffer. Chaque buffer joue un rôle précis, mais ils ne seront présentés que lorsque nous en aurons besoin. D’autres sont des extensions propriétaires (T-Buffer, W-Buffer, …).

Le début de l’entrée d’une primitive se fait avec glBegin : ici un polygone. Les possibilités sont (tirées de la man page de glBegin) :

GL_POINTS Treats each vertex as a single point. Vertex n defines point n.

N points are drawn.

GL_LINES Treates each pair of vertices as an independent line segment.

Vertices 2n-1 and 2n define line n. N/2 lines are drawn.

GL_LINE_STRIP

Draws a connected group of line segments from the first vertex to

the last. Vertices n and n+1 define line n. N-1 lines are

drawn.

GL_LINE_LOOP

Draws a connected group of line segments from the first vertex to

the last, then back to the first. Vertices n and n+1 define line

n. The last line, however, is defined by vertices N and 1. N

lines are drawn.

GL_TRIANGLES

Treates each triplet of vertices as an independent triangle.

Vertices 3n-2, 3n-1, and 3n define triangle n. N/3 triangles are

drawn.

GL_TRIANGLE_STRIP

Draws a connected group of triangles. One triangle is defined

for each vertex presented after the first two vertices. For odd

n, vertices n, n+1, and n+2 define triangle n. For even n,

vertices n+1, n, and n+2 define triangle n. N-2 triangles are

drawn.

GL_TRIANGLE_FAN

Draws a connected group of triangles. One triangle is defined

for each vertex presented after the first two vertices. Vertices

1, n+1, and n+2 define triangle n. N-2 triangles are drawn.

GL_QUADS Treats each group of four vertices as an independent

quadrilateral. Vertices 4n-3, 4n-2, 4n-1, and 4n define

quadrilateral n. N/4 quadrilaterals are drawn.

GL_QUAD_STRIP

Draws a connected group of quadrilaterals. One quadrilateral is

defined for each pair of vertices presented after the first pair.

Vertices 2n-1, 2n, 2n+2, and 2n+1 define quadrilateral n. N/2-1

quadrilaterals are drawn. Note that the order in which vertices

are used to construct a quadrilateral from strip data is

different from that used with independent data.

GL_POLYGON

Draws a single, convex polygon. Vertices 1 through N define this

polygon.

glVertex permet d’entrer un point.

Le format général des fonctions d’OpenGL est : {nom}{1234}{b s i d ub us ui}{v}, respectivement le nom de la fonction, le nombre de paramètres, leur type, passé directement ou par pointeur (vecteur). Tous les caractéristiques de la fonction sont optionnelles sauf le nom.

glVertex existe par exemple sous les formes glVertex2d, glVertex2f, glVertex2i, glVertex2s, glVertex3d, glVertex3f, glVertex3i, glVertex3s, glVertex4d, glVertex4f, glVertex4i, glVertex4s, glVertex2dv, glVertex2fv, glVertex2iv, glVertex2sv, glVertex3dv, glVertex3fv, glVertex3iv…

glColor fixe la couleur courante. De même elle existe sous de nombreuses formes.

glEnd indique la fin de l’entrée de coordonnées.

Cependant jusqu’ici rien n’est affiché : OpenGL a mis tout ce qu’il y a à faire dans un buffer, mais ne l’affiche que lorsqu’on lui demande, avec glFlush, qui rend la main immédiatement, ou avec glFinish, qui attend que tout soit affiché (peu d’intêret).

Bouger le triangle, utilisation de la matrice de vue

Rotation et translation

Pour bouger la « caméra » ou un objet (c’est la même chose de bouger tout les objets ou la caméra), il faut utiliser la matrice de vue (GL_MODELVIEW).

Comme calculer directement une matrice de changement de repère n’est pas vraiment évidente, il existe des fonctions prédéfinies : glRotate et glTranslate.

void glRotated( GLdouble angle, GLdouble x, GLdouble y, GLdouble z )

void glRotatef( GLfloat angle, GLfloat x, GLfloat y, GLfloat z )

void glTranslated( GLdouble x, GLdouble y, GLdouble z )

void glTranslatef( GLfloat x, GLfloat y, GLfloat z )

Ces fonctions multiplient à droite la matrice de vue. Il faut donc faire attention à l’ordre dans lequel les transformations sont entrées : c’est la dernière transformation entrée qui sera appliquée en premier.

Rappels :

· soient f de matrice L, g de matrice M et h de matrice N. h°g°f a pour matrice NML donc il faut entrer les matrices dans l'ordre N,M,L

· les rotations et les translations ne sont pas commutatives

Par exemple, pour faire tourner un objet sur lui-même, il faut entrer en dernier la matrice de rotation. Pour simplifier, l’origine du repère local d’un objet est par convention son centre de rotation (d’inertie) pour permettre de le faire tourner sur lui-même sans calcul complexe.

Tout cela peut paraître compliqué, mais un ordinateur ne sait comprendre que les matrices, et un énorme avantage est le temps de calcul : même si de nombreuses transformations sont entrées, il ne reste qu’une seule matrice à appliquer à tout les points entrés.

Pile de matrice

Mais comment inverser les transformations entrées ? Calculer les inverses est une perte de temps. Une pile est en effet associée à chaque matrice. Les fonctions sont :

void glPushMatrix( void )

void glPopMatrix( void )

qui respectivement empile et dépile la matrice courante. Même s’il n’y a que deux fonctions, il existe bien trois piles différentes (une par matrice). Comme pour les transformations, seule la matrice active peut-être empilée / dépilée (ce qui est logique, puisque que une matrice est empilée pour la sauvegarder avant une transformation).

Le plus souvent, un objet est caractérisé par ses angles de rotation (ax, ay, az) et sa position (x, y, z). Son affichage est alors du type :

glPushMatrix() ;

glTranslate3f(x,y,z) ;

glRotate3f(ax,ay,az) ;

glBegin(…)

…

glEnd() ;

glPopMatrix() ;

Exemple d’utilisation : un soleil avec ses satellites, et les lunes des satellites. Le principe est le même :

· Dessin du soleil

· PushMatrix

§ Translation dans le repère local d’une planète

§ Dessin de la planète

§ PushMatrix

· Translation dans le repère local d’une lune

· Dessin de la lune

§ PopMatrix

§ PushMatrix

· Translation dans le repère local d’une autre lune

· Dessin de la lune

§ PopMatrix

· PopMatrix

· PushMatrix

§ Translation dans le repère local d’une autre planète

§ Dessin de la planète

§ PushMatrix

· Translation dans le repère local d’une lune

· Dessin de la lune

§ PopMatrix

· PopMatrix

· etc.

5. Dessiner en 3D

Initialisation

Quelques changements dans l’initialisation :

glMatrixMode(GL_PROJECTION);

glLoadIdentity();

gluPerspective(45, aspect, 1.0f, 10.0f);

gluLookAt(0.0f, 0.0f, 0.0f,

0.0f, 1.0f, 0.0f,

0.0f, 0.0f, 1.0f);

glEnable(GL_DEPTH_TEST);

gluPerspective construit une matrice de projection de champ de vue de 45°, de rapport largeur/hauteur aspect, de profondeur de champ (1.0, 10.0), ie seuls les objets situés à une distance comprise entre 1.0 et 10. de la caméra sont affichés.

gluLookAt construit une matrice de changement de repère à partir de trois vecteurs : la position de la caméra, la direction où elle regarde, un vecteur indiquant le « haut » (juste la direction, il sera modifié pour faire un repère orthonormé).

Exemple : un Cube

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

total %= 360*500; // temps écoulé en ms

glPushMatrix();

glTranslatef(0.0f, 5.0f+(GLfloat)sin(GLfloat(total)/500.0f)*3.0f, 0.0f);

glRotatef(total/50.0f, 0.3f, 0.5f, 0.8f);

glBegin(GL_QUADS);

glColor3f(1.0f, 0.0f, 0.0f);

glVertex3f(-0.5f, -0.5f, -0.5f);

glVertex3f( 0.5f, -0.5f, -0.5f);

glVertex3f( 0.5f, 0.5f, -0.5f);

glVertex3f(-0.5f, 0.5f, -0.5f);

glColor3f(1.0f, 1.0f, 0.0f);

glVertex3f(-0.5f, -0.5f, 0.5f);

glVertex3f( 0.5f, -0.5f, 0.5f);

glVertex3f( 0.5f, 0.5f, 0.5f);

glVertex3f(-0.5f, 0.5f, 0.5f);

glColor3f(0.0f, 0.0f, 1.0f);

glVertex3f( 0.5f, -0.5f, 0.5f);

glVertex3f( 0.5f, -0.5f, -0.5f);

glVertex3f( 0.5f, 0.5f, -0.5f);

glVertex3f( 0.5f, 0.5f, 0.5f);

glColor3f(0.0f, 1.0f, 1.0f);

glVertex3f(-0.5f, -0.5f, 0.5f);

glVertex3f(-0.5f, -0.5f, -0.5f);

glVertex3f(-0.5f, 0.5f, -0.5f);

glVertex3f(-0.5f, 0.5f, 0.5f);

glColor3f(1.0f, 0.0f, 1.0f);

glVertex3f(-0.5f, 0.5f, 0.5f);

glVertex3f(-0.5f, 0.5f, -0.5f);

glVertex3f( 0.5f, 0.5f, -0.5f);

glVertex3f( 0.5f, 0.5f, 0.5f);

glColor3f(0.0f, 1.0f, 0.0f);

glVertex3f(-0.5f, -0.5f, 0.5f);

glVertex3f(-0.5f, -0.5f, -0.5f);

glVertex3f( 0.5f, -0.5f, -0.5f);

glVertex3f( 0.5f, -0.5f, 0.5f);

glEnd();

glPopMatrix();

Depth-Buffer

En 3D, tout n’est pas affiché : il y a des faces cachées. Comment déterminer ce qui est affiché ? En affichant ce qui est la plus proche de la caméra.

C’est la rôle du depth-buffer : il sauvegarde la distance du point actuellement affiché. Si un point plus proche arrive ensuite, il remplace le point et la valeur dans la depth-buffer est changée. Le depth-buffer à les même dimensions que le color-buffer.

Le depth-buffer doit être effacé avec glClear.

6. Paramètres du rendering

Antialiasing

L’OpenGL est une « state machine » : les paramètres de dessin consistent en une série de variables (très nombreuses, voir les annexes de la spécification OpenGL pour une liste complète).

La plupart sont modifiables par :

void glEnable( GLenum cap )

void glDisable( GLenum cap )

Pour l’antialiasing, les paramètres sont : GL_POINT_SMOOTH, GL_LINE_SMOOTH, et GL_POLYGON_SMOOTH. Le but de l’antialiasing est de rendre apparemment plus lisse les lignes en faisant un dégradé sur les contours des points.

Culling

Le culling consiste à ne dessiner que les triangles dont les points sont dans le sens des aiguilles d’une montre (« clockwise ») ou dans l’autre sens (« counter-clockwise »). L’intérêt est l’optimisation : l’objectif est de toujours dessiner les faces extérieures d’un objet dans le sens contraire des aiguilles d’une montre, et de filtrer les faces cachées avec le culling.

Prenons l’exemple du cube : les faces sont toujours affichées dans le même ordre, peut importe si elles sont visibles ou pas. C’est le depth-buffer qui permet d’afficher les bonnes faces. Cependant le test du depth-buffer est long : il faut calculer la distance de chaque point de chaque face par rapport à la caméra, puis tester le depth pour chaque point (et mettre à jour le depth si le point est plus près). C’est un test beaucoup trop important pour les faces dont on est sûr qu’elles ne seront pas affichées. Il est plus rapide d’éliminer une face entière d’un coup avec le culling.

L’astuce utilisée par le culling pour savoir de quel coté est vue une face consiste à regarder dans quel sens les points des triangles sont dessinés (en OpenGL tout est décomposé en triangles) : dans le sens des aiguilles d’une montre ou dans l’autre sens.

Cette technique est très rapide : en gros il suffit de regarder le signe du produit scalaire de deux cotés d’un triangle. La seule contrainte pour le programmeur est de dessiner dans le bon sens les points.

Le standard est le CCW (counter-clockwise), que ce soit pour les primitives de glu ou glut, ou encore pour 3DS ou Lightwave.

En OpenGL, les fonctions correspondantes sont :

void glCullFace( GLenum mode )

avec mode à GL_FRONT, GL_BACK ou GL_FRONT_AND_BACK, pour indiquer quel coté doit être éliminé au culling.

void glFrontFace( GLenum mode )

avec mode à GL_CW ou GL_CCW pour indiquer quelles sont les faces de devant

Un appel à glEnable / glDisable avec en paramètre GL_CULL_FACE pour activer / désactiver le culling.

7. Eclairage

Activation

C’est pour ce genre de choses que l’on apprécie la simplicité d’OpenGL, car activer l’éclairage se fait par :

glEnable(GL_LIGHTING) ; // active le calcul de l’éclairage

glEnable(GL_LIGHT0) ; // allume la lampe 0

C’est tout.

Pour allumer une autre lampe, mettre GL_LIGHTi au lieu de GL_LIGHT0. Il y a au minimum 8 lampes disponibles. On a toujours GL_LIGHTi = GL_LIGHT0 + i. Pour éteindre, utiliser glDisable.

Le nombre maximal de lumières est obtenu en appelant :

glGetIntegerv(GL_MAX_LIGHTS, &nb_lights)

Paramètres

Il faut quand même donner un minimum d’information sur la lampe, comme la couleur et la position de la lampe.

Un peu de théorie

Dans la réalité, les sources de lumières sont des volumes (par exemple un filament), et la lumière se réfléchit à l’infini sur tous les objets.

Il est impossible de simuler un tel comportement, surtout quand il faut calculer une image en quelques millisecondes. Des simplifications ont été apportées :

· les sources de lumière sont des points

· la lumière ne se réfléchit pas sur les objets

Pour plus de réalisme, les sources disposent de plusieurs paramètres qui n’ont pas forcément d’équivalent réel (voir le paragraphe suivant).

Une chose par contre ne change pas : la couleur visible d’un objet dépend à la fois de sa couleur propre et celle de la lampe, et l’intensité de la lumière qu’il reçoit dépend de l’angle de la surface avec la direction vers la source lumineuse.

Tout sur la lampe

Tiré tout droit de la spécification OpenGL :

Paramètre | Type | Par défaut | Description
acm | Couleur | (0,0,0,0) | Intensité ambiante : la lumière ambiante est présente partout dans la scène, elle n’a pas de direction
dcli | Couleur | (0,0,0,0) sauf lampe 0 : (1,1,1,1) | Intensité de diffusion : c’est l’intensité « réelle » de la source, elle part dans tous les sens depuis la source
scli | Couleur | Idem | Intensité du « spot » : comme la lumière diffuse, sauf qu’elle n’est émise qu’à l’intérieur d’un cône
Ppli | Position | (0,0,1,0) | Position
sdli | Direction | (0,0,-1) | Direction du spot (vecteur)
srli | Réel | 0 | Exposant du spot (entre 0 et 128)
crli | Réel | 180 | Angle du cône du spot (180 ou entre 0 et 90)
k0i | Réel | 1.0 | Atténuation constante (entre 0 et ¥)
k1i | Réel | 0 | Atténuation linéaire
k2i | Réel | 0 | Atténuation quadrique

Et sur les matériaux

Les mêmes paramètres existent pour spécifier la « réponse » de la surface d’un objet :

Paramètre | Type | Par défaut | Description
acm | Couleur | (0.2,0.2,0.2,1.0) | Couleur ambiante
dcm | Couleur | (0.8,0.8,0.8,1.0) | Couleur de diffusion
scm | Couleur | (0,0,0,1) | Couleur au « spot »
ecm | Couleur | (0,0,0,1) | Couleur émise
srm | Réel | 0 | Exposant au « spot »

Fonctions OpenGL

Les fonctions suivantes permettent de spécifier tous les paramètres précédents :

· Pour une source lumineuse :

void glLightf( Glenum light, Glenum pname, GLfloat param )

void glLighti( Glenum light, Glenum pname, GLint param )

light = GL_LIGHTi

pname = GL_SPOT_EXPONENT, GL_SPOT_CUTOFF, GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION, ou GL_QUADRATIC_ATTENUATION

et param = la nouvelle valeur

void glLightfv( GLenum light, GLenum pname, const Glfloat *params)

void glLightiv( GLenum light, GLenum pname, const GLint *params)

light = GL_LIGHTi

pname = GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, GL_POSITION, GL_SPOT_DIRECTION, GL_SPOT_EXPONENT, GL_SPOT_CUTOFF, GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION, ou GL_QUADRATIC_ATTENUATION

param = la nouvelle valeur

Attention à la position de la lampe : comme toutes les coordonnées entrées, elles dont immédiatement multipliées par la matrice ModelView. Si celle-ci change, il faut repositionner les lampes.

void glLightModelf( GLenum pname, GLfloat param )

void glLightModeli( GLenum pname, GLint param )

void glLightModelfv( GLenum pname, const GLfloat *params )

void glLightModeliv( GLenum pname, const GLint *params )

Pname | param
GL_LIGHT_MODEL_AMBIENT (fv ou iv seulement) | Intensité ambiante de la scène, par défaut (0.2, 0.2, 0.2, 1.0)
GL_LIGHT_MODEL_LOCAL_VIEWER | 0 : les reflets des spots sont calculés sur l’axe –z ¹ 0 : par rapport à la position de l’œil
GL_LIGHT_MODEL_TWO_SIDE | 0 : seule la face « front » est calculée ¹ 0 : les deux faces d’une surface sont calculées

· Pour un matériau :

Les propriétés de surface d’un polygone peuvent être différentes suivant la face que la face est « front » ou « back » (voir glFrontFace, ou le paragraphe sur le culling).

void glMaterialfv( GLenum face, GLenum pname, const GLfloat *params )

void glMaterialiv( GLenum face, GLenum pname, const GLint *params )

La page man est tellement bien faite que je ne vois pas l’intérêt de la refaire :

Face | Specifies which face or faces are being updated. Must be one of GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK.
Pname | Specifies the material parameter of the face or faces that is being updated. Must be one of GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, GL_EMISSION, GL_SHININESS, GL_AMBIENT_AND_DIFFUSE, or GL_COLOR_INDEXES.
Params | Specifies a pointer to the value or values that pname will be set to.

glMaterial assigns values to material parameters. There are two matched sets of material parameters. One, the front-facing set, is used to shade points, lines, bitmaps, and all polygons (when two-sided lighting is disabled), or just front-facing polygons (when two-sided lighting is enabled). The other set, back-facing, is used to shade back-facing polygons only when two-sided lighting is enabled. Refer to the glLightModel reference page for details concerning one- and two-sided lighting calculations.

[…]

GL_AMBIENT | params contains four integer or floating-point values that specify the ambient RGBA reflectance of the material. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to -1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default ambient reflectance for both front- and back-facing materials is (0.2, 0.2, 0.2, 1.0).
GL_DIFFUSE | params contains four integer or floating-point values that specify the diffuse RGBA reflectance of the material. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to -1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default diffuse reflectance for both front- and back-facing materials is (0.8, 0.8, 0.8, 1.0).
GL_SPECULAR | params contains four integer or floating-point values that specify the specular RGBA reflectance of the material. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to -1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default specular reflectance for both front- and back-facing materials is (0.0, 0.0, 0.0, 1.0).
GL_EMISSION | params contains four integer or floating-point values that specify the RGBA emitted light intensity of the material. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to -1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default emission intensity for both front- and back-facing materials is (0.0, 0.0, 0.0, 1.0).
GL_SHININESS | params is a single integer or floating-point value that specifies the RGBA specular exponent of the material. Integer and floating-point values are mapped directly. Only values in the range [0,128] are accepted. The default specular exponent for both front- and back-facing materials is 0.
GL_AMBIENT_AND_DIFFUSE | Equivalent to calling glMaterial twice with the same parameter values, once with GL_AMBIENT and once with GL_DIFFUSE.

Les normales

Une fois que la source lumineuse est définie, ainsi que les caractéristiques du matériau, il reste encore une chose : l’angle d’incidence de la lumière. Cela ne change pas la couleur d’une surface, mais l’intensité de l’éclairage qu’elle reçoit.

L’angle d’incidence est l’angle entre la normale à une surface et la droite partant de la source lumineuse et passant par le point d’incidence. Il faut impérativement préciser pour chaque point la « normale » de ce point (plus rigoureusement, la normale à la surface dont ce point fait partie).

Le principe est le même que pour glColor, la normale est une des variables d’état d’OpenGL, qui est modifiée par l’une des fonctions suivantes :

void glNormal3b( GLbyte nx, GLbyte ny, GLbyte nz )

void glNormal3d( GLdouble nx, GLdouble ny, GLdouble nz )

void glNormal3f( GLfloat nx, GLfloat ny, GLfloat nz )

void glNormal3i( GLint nx, GLint ny, GLint nz )

void glNormal3s( GLshort nx, GLshort ny, GLshort nz )

void glNormal3bv( const GLbyte *v )

[...]

Les paramètres correspondent au coordonnées du vecteur normal. ATTENTION : il doit être UNITAIRE (de norme 1).

Il est possible de rendre unitaire automatiquement les vecteurs entrés, mais cela entraîne un calcul supplémentaire :

glEnable(GL_NORMALIZE);

Exemple

L’exemple le plus simple :

void init ()

{

glClearColor (0.0f, 0.0f, 0.0f, 0.0f);

GLfloat ambientProperties[] = {0.9f, 0.9f, 0.9f, 1.0f};

GLfloat positionProperties[] = {0.0f, -5.0f, 0.0f, 0.0f};

glLightfv( GL_LIGHT0, GL_DIFFUSE, ambientProperties);

glLightfv( GL_LIGHT0, GL_POSITION, positionProperties);

glEnable(GL_LIGHT0);

glEnable(GL_LIGHTING);

}

void display (void)

{

static timeb tt ,t;

static int total;

GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 1.0f};

GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 1.0f};

GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 1.0f};

glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

ftime(&t);

total += (t.time - tt.time)*1000 + (t.millitm - tt.millitm);

total %= 360*300;

tt = t;

glPushMatrix();

glRotatef((total/30.0f), 0.5f, 0.3f, 0.8f);

glRotatef((total/50.0f), 0.5f, -0.3f, 0.8f);

glBegin(GL_QUADS);

glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, Rouge);

glNormal3d(0.0,0.0,1.0);

glVertex3d(-1, 1, 1);

glVertex3d(-1, -1, 1);

glVertex3d( 1, -1, 1);

glVertex3d( 1, 1, 1);

glNormal3d(0.0,0.0,-1.0);

glVertex3d( 1, 1, -1);

glVertex3d( 1, -1, -1);

glVertex3d( -1, -1, -1);

glVertex3d( -1, 1, -1);

glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, Vert);

glNormal3d(1.0,0.0,0.0);

glVertex3d( 1, 1, 1);

glVertex3d( 1, -1, 1);

glVertex3d( 1, -1, -1);

glVertex3d( 1, 1, -1);

glNormal3d(-1.0,0.0,0.0);

glVertex3d( -1, 1, 1);

glVertex3d( -1, 1, -1);

glVertex3d( -1, -1, -1);

glVertex3d( -1, -1, 1);

glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, Bleu);

glNormal3d(0.0,-1.0,0.0);

glVertex3d( -1, -1, 1);

glVertex3d( -1, -1, -1);

glVertex3d( 1, -1, -1);

glVertex3d( 1, -1, 1);

glNormal3d(0.0,1.0,0.0);

glVertex3d( -1, 1, 1);

glVertex3d( 1, 1, 1);

glVertex3d( 1, 1, -1);

glVertex3d( -1, 1, -1);

glEnd();

glPopMatrix();

glutSwapBuffers();

}

void reshape (int width, int height)

{

glViewport (0, 0, width, height);

glMatrixMode (GL_PROJECTION);

glLoadIdentity ();

gluPerspective (45.0, ((GLfloat) width) / ((GLfloat) height),1.0,10.0);

glMatrixMode (GL_MODELVIEW);

glLoadIdentity ();

gluLookAt (0.0, -5.0, 0.0,

0.0, 0.0, 0.0,

0.0, 0.0, 1.0);

init();

glEnable(GL_DEPTH_TEST);

glEnable(GL_POLYGON_SMOOTH);

glutPostRedisplay();

}

Pour voir l’effet des différents paramètres, voir les très bons exemples de www.cs.utah.edu/~narobins/tutors/tutor, et le lightlab de www.student.nada.kth.se/~nv91-gta/OpenGL/projects/lightlab/.

8. Fog (brouillard)

C’est ce qui simule les effets atmosphériques (je ne vous apprends rien là) comme la fumée, la pollution etc. On s’en sert aussi pour rendre les effets de vision limitée (en même temps, cela permet de virer de la scène une partie des objets 3D trop loin et d’accélérer le rendu).

Le fonctionnement du brouillard

Lorsque le brouillard est activé, les objets qui sont de plus en plus loin commencent à se fondre dans la couleur du brouillard. On peut contrôler aussi la densité du brouillard pour déterminer le rythme avec lequel les objets sont estompé lorsque la distance augmente.

Le brouillard est appliqué à la scène après les transformations matricielles, la lumière, et les textures, donc il affecte les objets transformés (ie avec texture…).

L’utilisation du brouillard

C’est très simple. On l’active en passant GL_FOG comme attribut de glEnable() et on la couleur et l’équation de contrôle de la densité du brouillard grâce à :

void glFogf( GLenum pname, GLfloat param )

void glFogi( GLenum pname, GLint param )

void glFogfv( GLenum pname, const GLfloat *params )

void glFogiv( GLenum pname, const GLint *params )

avec pname = GL_FOG_MODE, GL_FOG_DENSITY, GL_FOG_START, GL_FOG_END, ou GL_FOG_COLOR, et param la nouvelle valeur.

Exemple :

GlEnable(GL_FOG) ;

GLfloat fogcolor[4] = {0.5, 0.5, 0.5, 1} ;

GLint fogmode = GL_EXP ;

glFogi (GL_FOG_MODE, fogmode) ;

glFogfv(GL_FOG_COLOR, fogcolor) ;

glFogf(GL_FOG_DENSITY, 0.35) ;

glFogf(GL_FOG_START, 1.0) ;

glFogf(GL_FOG_END, 5.0) ;

Les équations

Le brouillard se sert d’une variable f variant de 0 (brouillard complet) à 1 (transparent comme de l’eau de roche). Les 3 équations sont :

· GL_LINEAR : f = (end – z) / (end – start)

· GL_EXP : f = e-density * z

· GL_EXP2 : f = e-(density * z)²

9. Transparence et alpha-blending

Plusieurs fois déjà des couleurs ont été présentée sous forme RGBA : rouge, vert, bleu et alpha. Si les trois premières coordonnées sont évidentes, la quatrième quant à elle sert à indiquer la « transparence ».

Activation

Si pour l’instant rien n’a été dit sur les valeurs de l’alpha, c’est simplement parce que par défaut OpenGL s’en fiche.

Pour activer le canal alpha, il faut appeler comme d’habitude glEnable :

glEnable(GL_BLEND) ;

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) ; // expliqué après

Ensuite il suffit de changer la valeur de A suivant la transparence.

Exemple

void init ()

{

glClearColor (0.0f, 0.0f, 0.0f, 0.0f);

glEnable(GL_BLEND) ;

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

}

void display (void)

{

static timeb tt ,t;

static int total;

GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 0.5f};

GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 0.5f};

GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 0.5f};

// […]

glBegin(GL_QUADS);

glColor4fv(Rouge);

// […] (dessin cube)

glColor4fv(Vert);

// […]

glColor4fv(Bleu);

// […]

glEnd();

glPopMatrix();

glutSwapBuffers();

}

Paramètres

La paramétrage principal est effectué par la fonction :

void glBlendFunc( GLenum sfactor, GLenum dfactor )

Si la fonction s’appelle « blend » (« mélanger » en anglais, ici en parlant des couleurs) et pas « transparency », c’est que la transparence n’est qu’une application de l’alpha-blending. C’est de loin la principale.

Néanmoins ce que fait réellement l’alpha-blending, c’est mélanger la couleur déjà affichée avec une nouvelle. Et comme dans OpenGL, la rapidité du rendu est prioritaire sur la spécialisation des fonctions, il est bon de savoir comment cela fonctionne.

Le mélange est précisé par un appel à glBlendFunc, avec le premier paramètre indiquant par quelle valeur multiplier (canal par canal) la nouvelle couleur, le second la couleur déjà affichée. La nouvelle couleur affichée est la somme de ces deux multiplications.

L’indice « s » indique que la nouvelle couleur est concernée, « d » qu’il s’agit de la couleur déjà affichée.

La couleur affichée est : C = CsS + CdD. S et D étant paramétrés respectivement par sfactor et dfactor.

Principales valeurs de S en fonction de sfactor :

Valeur de sfactor | Valeur de S
ZERO | (0,0,0,0)
ONE | (1,1,1,1)
DST_COLOR | (Rd,Gd,Bd,Ad)
ONE_MINUS_DST_COLOR | (1,1,1,1) - (Rd,Gd,Bd,Ad)
SRC_ALPHA | (As, As, As, As)
ONE_MINUS_SRC_ALPHA | (1,1,1,1) - (As,As,As,As)
DST_ALPHA | (Ad,Ad,Ad,Ad)
ONE_MINUS_DST_ALPHA | (1,1,1,1) - (Ad,Ad,Ad,Ad)
SRC_ALPHA_SATURATE | (f,f,f,1) avec f = min (As, 1 - Ad)

Principales valeurs de D en fonction de dfactor :

Valeur de sfactor | Valeur de D
ZERO | (0,0,0,0)
ONE | (1,1,1,1)
SRC_COLOR | (Rs,Gs,Bs,As)
ONE_MINUS_SRC_COLOR | (1,1,1,1) - (Rs,Gs,Bs,As)
SRC_ALPHA | (As, As, As, As)
ONE_MINUS_SRC_ALPHA | (1,1,1,1) - (As,As,As,As)
DST_ALPHA | (Ad, Ad, Ad, Ad)
ONE_MINUS_DST_ALPHA | (1,1,1,1) - (Ad,Ad,Ad,Ad)
SRC_ALPHA_SATURATE | (f,f,f,1) avec f = min (As, 1 - Ad)

Les paramètres les plus courants sont (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA).

Cela représente bien la transparence codée dans le canal Alpha : la couleur affichée est multipliée par la valeur de transparence (si 0, complètement transparent, si 1 opaque), et la couleur en dessous est multipliée par le complément.

Ordre des primitives et depth-buffer

Le problème avec cette manière de rendre les objets transparents, c’est qu’il faut dessiner les surfaces dans l’ordre d’apparition pour avoir un meilleur rendu. En plus, si le depth-buffer est activé, les surfaces recouvertes sont directement éliminées au lieu d’être transparentes. Le plus simple, c’est de désactiver avec glDisable(GL_DEPTH_TEST) le depth-buffer lorsque des surfaces transparentes sont affichées.

10. Listes

Commandes OpenGL

Certains objets sont toujours les mêmes. Comme OpenGL traduit toutes les séquences entrées, il est intéressant de créer des listes pour regrouper les instructions. Cela accélère légèrement l’affichage et réduit le code. Une liste est alors codée par un entier (un seul entier est alors envoyé pour un objet quelconque).

En pratique :

· d’abord réserver des numéros de listes avec :
GLuint glGenLists( GLsizei range )
où range est le nombre de numéros de listes à réserver. Les numéros réservés vont de la valeur renvoyée à cette valeur + range.

· ensuite appeler :
void glNewList( GLuint list, GLenum mode )
avec pour list le numéro de la liste à créer, et mode = GL_COMPILE ou GL_COMPILE_AND_EXECUTE. Les deux modes créés la liste, mais le second en plus l’éxécute (l’affiche).

· mettre toutes les routines souhaitées (glBegin, glColor, glVertex…), elles sont enregistrées dans la liste. Certaines commandes ne peuvent pas être enregistrées dans une liste, mais il y en a peu et c’est plutôt logique (par exemple glGenList, glFlush, glNewlist…).

· terminer par :
void glEndList( void )

Pour afficher une liste, il faut appeler :

void glCallList( GLuint list )

ou

void glCallLists( GLsizei n, GLenum type, const GLvoid *lists)

pour afficher plusieurs listes d’un coup (type indique le type du tableau passé en paramètre : GL_BYTE, GL_INT…).

Reste à effacer une liste lorsqu’elle n’est plus utile :

void glDeleteLists( GLuint list, GLsizei range )

dont les paramètres sont les même que glGenList.

Exemple

Par exemple le cube peut être mis dans une liste :

void init()

{

GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 0.5f};

GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 0.5f};

GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 0.5f};

cube = glGenLists(1);

glNewList(cube, GL_COMPILE);

glBegin(GL_QUADS);

glColor4fv(Rouge);

glNormal3d(0.0,0.0,1.0);

glVertex3d(-1, 1, 1);

glVertex3d(-1, -1, 1);

glVertex3d( 1, -1, 1);

glVertex3d( 1, 1, 1);

glNormal3d(0.0,0.0,-1.0);

glVertex3d( 1, 1, -1);

// […]

glEnd();

glEndList();

}

void display (void)

{

// […]

glPushMatrix();

glRotatef((total/30.0f), 0.5f, 0.3f, 0.8f);

glRotatef((total/50.0f), 0.5f, -0.3f, 0.8f);

glCallList(cube);

glPopMatrix();

glutSwapBuffers();

}

11. Textures

Jusqu’ici, les surfaces étaient simplement coloriées, ce qui ne permet pas d’avoir des rendus très réalistes. Les textures améliore considérablement le rendu. Le principe est de remplir une surface avec une image bitmap correspondant à une photo de la surface.

Principe général

Un texture est appliquée à une surface de la manière suivante :

· d’abord il faut indiquer quelle image il va falloir appliquer (ce qui revient à donner un pointeur vers l’image).

· ensuite, avant d’entrer chaque point (vertex) de la surface, il faut préciser où se situe ce point dans la texture, c’est le même principe qu’avant avec glColor. Cela revient en quelque sorte à mettre des épingles sur la texture

OpenGL se charge ensuite d’appliquer la texture, en faisant correspondre les vertex avec les « épingles ».

En OpenGL

Dans ce cours, les routines de chargement d’images en mémoire ne seront pas expliquées. Ce dont à besoin OpenGL, c’est d’une image non compressée chargée sous la forme d’un tableau en mémoire. Il existe de nombreuses routines toutes faites pour charger les principaux formats d’images (BMP, PCX, GIF, PNG…).

Spécifier l’image

Pour spécifier la texture à utiliser, appeler :

void glTexImage2D( GLenum target, GLint level, GLint components, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels )

Avec :

Target | GL_TEXTUTE_2D
Level | Indique le niveau de mipmapping, expliqué plus loin dans ce cours
Components | Nombre de composantes dans la texture (1, 2, 3 ou 4). Généralement 3 (pour RGB) ou 4 (pour RGBA).
Width | Largeur de l’image. Doit être 2n + 2 (dépend de bordure).
Height | Hauteur de l’image. Doit être 2m + 2 (dépend de bordure).
Bordure | Indique la largeur de la bordure : 0 ou 1
Format | Codage des couleurs : GL_COLOR_INDEX, GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA, GL_LUMINANCE, ou GL_LUMINANCE_ALPHA
Type | Type du tableau : GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, ou GL_FLOAT
Pixel | Pointeur vers l’image.

Les « épingles »

La première chose à faire, c’est d’activer les textures :

glEnable(GL_TEXTURE_2D) ;

Pour poser une épingle, avant un vertex il faut appeler :

void glTexCoord2f( GLfloat s, GLfloat t )

ou l’une de ses variantes.

Les coordonnées (s, t) indiquent la correspondance entre le prochain vertex entré et la texture. En OpenGL, le repère d’une texture fait toujours [0..1] sur [0..1], mais s et t peuvent sortir des ces intervalles, l’effet dépend alors du paramètrage choisi.

Paramétrage

Application de la texture

Les paramètres d’application d’une texture sont :

Nom | Type | Valeur possibles | Signification
GL_TEXTURE_WRAP_S | Entier | GL_CLAMP, GL_CLAMP_TO_EDGE, GL_REPEAT | Indique ce qui se passe si une coordonnée s sort de l’intervalle [0..1]. Soit la texture est répètée (mosaïque), soit seulement le bord est répété.
GL_TEXTURE_WRAP_T | Entier | Idem | Idem pout t
GL_TEXTURE_MIN_FILTER | Entier | NEAREST, LINEAR, NEAREST_MIPMAP_NEAREST, NEAREST_MIPMAP_LINEAR, LINEAR_MIPMAP_NEAREST, LINEAR_MIPMAP_LINEAR | Indique la qualité de l’agrandissement d’une texture. LINEAR est meilleur que NEAREST, mais plus lent.
GL_TEXTURE_MAG_FILTER | Entier | NEAREST, LINEAR | Idem en réduction

Il existe d’autres paramètres, mais ceux-ci sont de loin les plus importants.

Pour changer un paramètre, il faut appeler :

void glTexParameteri(GLenum target, GLenum pname, GLint param )

avec target = GL_TEXTURE_2D, pname l’un des noms de paramètres, et param la nouvelle valeur.

glColor

La couleur courante (spécifiée par glColor), joue un rôle différent suivant les paramètres passés à :

void glTexEnvi( GLenum target, GLenum pname, Glint param )

avec target = GL_TEXTURE_ENV, pname = GL_TEXTURE_ENV_MODE, et param = GL_REPLACE, GL_MODULATE, GL_DECAL, ou GL_BLEND.

Il y a beaucoup de cas suivant le nombre de composantes de la texture.

Dans le cas de 3 ou 4 composantes (RGB ou RGBA), les modes les plus courants sont :

· GL_REPLACE, la couleur courante n’intervient pas

· GL_MODULATE, la couleur courante multiplie la texture. Il est alors possible de changer très facilement l’intensité d’une texture.

Exemple

Pour simplifier (et garantir la portabilité), le format d’image est ici le « Raw », c’est-à-dire l’image seule. Paint Shop Pro gère très bien ce format. Le problème est que la taille de l’image n’est pas inclus dans ce fichier.

void init()

{

char buffer[32*32*3];

FILE *f = fopen("../Data/Marbre.raw", "rb");

if (f)

{

fread(buffer, 3072, 1, f);

fclose(f);

glTexImage2D(GL_TEXTURE_2D,0,3, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer);

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

glEnable(GL_TEXTURE_2D);

}

cube = glGenLists(1);

glNewList(cube, GL_COMPILE);

glBegin(GL_QUADS);

glNormal3d(0.0,0.0,1.0);

glTexCoord2f(0.0, 1.0); glVertex3d(-1, 1, 1);

glTexCoord2f(1.0, 1.0); glVertex3d(-1, -1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( 1, -1, 1);

glTexCoord2f(0.0, 0.0); glVertex3d( 1, 1, 1);

glNormal3d(0.0,0.0,-1.0);

glTexCoord2f(0.0, 1.0); glVertex3d( 1, 1, -1);

glTexCoord2f(0.0, 0.0); glVertex3d( 1, -1, -1);

glTexCoord2f(1.0, 0.0); glVertex3d( -1, -1, -1);

glTexCoord2f(1.0, 1.0); glVertex3d( -1, 1, -1);

glNormal3d(1.0,0.0,0.0);

glTexCoord2f(0.0, 0.0); glVertex3d( 1, 1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( 1, -1, 1);

glTexCoord2f(1.0, 1.0); glVertex3d( 1, -1, -1);

glTexCoord2f(0.0, 1.0); glVertex3d( 1, 1, -1);

glNormal3d(-1.0,0.0,0.0);

glTexCoord2f(0.0, 0.0); glVertex3d( -1, 1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( -1, 1, -1);

glTexCoord2f(1.0, 1.0); glVertex3d( -1, -1, -1);

glTexCoord2f(0.0, 1.0); glVertex3d( -1, -1, 1);

glNormal3d(0.0,-1.0,0.0);

glTexCoord2f(0.0, 0.0); glVertex3d( -1, -1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( -1, -1, -1);

glTexCoord2f(1.0, 1.0); glVertex3d( 1, -1, -1);

glTexCoord2f(0.0, 1.0); glVertex3d( 1, -1, 1);

glNormal3d(0.0,1.0,0.0);

glTexCoord2f(0.0, 0.0); glVertex3d( -1, 1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( 1, 1, 1);

glTexCoord2f(1.0, 1.0); glVertex3d( 1, 1, -1);

glTexCoord2f(0.0, 1.0); glVertex3d( -1, 1, -1);

glEnd();

glEndList();

}

MipMapping

Le MipMapping consiste à avoir la même texture à plusieurs échelles, de façon à limiter les transferts mémoires lors du plaquage de texture (inutile de transférer une texture 256x256 si l’objet dessiné est très loin dans le décor et ne fait plus que du 5x5).

En théorie, le programmeur devrait avoir 3 ou 4 fois la même texture, mais avec une résolution différente. Il faut alors entrer toutes les textures à chaque fois, en changeant le paramètre level de glTextImage2D (sachant que 0 est l’image la plus grande, et les suivantes sont ses réductions). Lorsque la texture est appliqué, OpenGL soit choisi l’image de taille la plus proche de la surface à afficher (NEAREST_MIPMAP), soit fait une sorte de moyenne entre l’image de taille juste au dessus et celle juste en dessous (LINEAR_MIPMAP).

En pratique, il y a une fonction glu :

int gluBuild2DMipmaps(GLenum target, GLint components, GLint width, GLint height, GLenum format, GLenum type, const void *data )

Les paramètres sont les mêmes que pour glTexImage2D, sauf qu’il est cette fois possible d’utiliser des images de dimensions qui ne sont pas des puissances de 2. L’image passée est celle de meilleure qualité (level = 0).

Chargement dans la carte vidéo

L’intérêt du MipMapping est très limité si les textures ne sont pas stockées dans la carte vidéo.

Le principe est similaire à celui des listes :

void glGenTextures( GLsizei n, GLuint * textures)

permet de réserver n textures, les numéros sont alors placés dans le tableau pointé par textures.

Ensuite le numéro de la texture courante est changé par :

void glBindTexture( GLenum target, GLuint texture)

avec target = GL_TEXTURE_2D et texture le numéro de la texture à activer. Ensuite toutes les fonctions faisant intervenir une texture utilisent la texture active.

Les textures sont libérées par :

void glDeleteTextures(GLsizei n, const GLuint * textures )

Pour résumé, une texture est le plus souvent chargée par :

glGenTextures(1, &Texture_num);

glBindTexture(GL_TEXTURE_2D, Texture_num);

gluBuild2DMipmaps(GL_TEXTURE_2D, 3, width, height(), GL_RGB, GL_UNSIGNED_BYTE, data);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

Ensuite pour activer une texture, avant un glBegin :

glBindTexture(GL_TEXTURE_2D, Texture_num);

Exemple

void init()

{

char buffer[32*32*3];

FILE *f = fopen("../Data/Marbre.raw", "rb");

if (f)

{

fread(buffer, 3072, 1, f);

fclose(f);

glGenTextures(1, &Texture1);

glBindTexture(GL_TEXTURE_2D, Texture1);

glTexImage2D(GL_TEXTURE_2D,0,3, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer);

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

glEnable(GL_TEXTURE_2D);

}

f = fopen("../Data/Marbre2.raw", "rb");

if (f)

{

fread(buffer, 3072, 1, f);

fclose(f);

glGenTextures(1, &Texture2);

glBindTexture(GL_TEXTURE_2D, Texture2);

glTexImage2D(GL_TEXTURE_2D,0,3, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer);

}

cube = glGenLists(1);

glNewList(cube, GL_COMPILE);

glBindTexture(GL_TEXTURE_2D, Texture2);

glBegin(GL_QUADS);

glNormal3d(0.0,0.0,1.0);

glTexCoord2f(0.0, 1.0); glVertex3d(-1, 1, 1);

glTexCoord2f(1.0, 1.0); glVertex3d(-1, -1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( 1, -1, 1);

glTexCoord2f(0.0, 0.0); glVertex3d( 1, 1, 1);

glNormal3d(0.0,0.0,-1.0);

glTexCoord2f(1.0, 0.0); glVertex3d( 1, 1, -1);

glTexCoord2f(1.0, 1.0); glVertex3d( 1, -1, -1);

glTexCoord2f(0.0, 1.0); glVertex3d( -1, -1, -1);

glTexCoord2f(0.0, 0.0); glVertex3d( -1, 1, -1);

glNormal3d(1.0,0.0,0.0);

glTexCoord2f(1.0, 1.0); glVertex3d( 1, 1, 1);

glTexCoord2f(0.0, 1.0); glVertex3d( 1, -1, 1);

glTexCoord2f(0.0, 0.0); glVertex3d( 1, -1, -1);

glTexCoord2f(1.0, 0.0); glVertex3d( 1, 1, -1);

glNormal3d(-1.0,0.0,0.0);

glTexCoord2f(0.0, 0.0); glVertex3d( -1, 1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( -1, 1, -1);

glTexCoord2f(1.0, 1.0); glVertex3d( -1, -1, -1);

glTexCoord2f(0.0, 1.0); glVertex3d( -1, -1, 1);

glEnd();

glBindTexture(GL_TEXTURE_2D, Texture1);

glBegin(GL_QUADS);

glNormal3d(0.0,-1.0,0.0);

glTexCoord2f(0.0, 0.0); glVertex3d( -1, -1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( -1, -1, -1);

glTexCoord2f(1.0, 1.0); glVertex3d( 1, -1, -1);

glTexCoord2f(0.0, 1.0); glVertex3d( 1, -1, 1);

glNormal3d(0.0,1.0,0.0);

glTexCoord2f(0.0, 0.0); glVertex3d( -1, 1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( 1, 1, 1);

glTexCoord2f(1.0, 1.0); glVertex3d( 1, 1, -1);

glTexCoord2f(0.0, 1.0); glVertex3d( -1, 1, -1);

glEnd();

glEndList();

}

Priorités des textures

Comme il se peut que toutes les textures ne rentrent pas dans la carte vidéo, il est possible de donner des priorités au chargement des textures en mémoire vidéo.

void glPrioritizeTextures( GLsizei n, GLuint * textures, GLclampf * priorities)

avec priorities un tableau de n flottants appartenant à [0..1], 0 étant la priorité la plus faible et 1 la plus forte.

12. Les opérations sur les pixels

Introduction

Tout ce qu’on va voir ici concerne l’affichage de bitmaps, la copie et l’affichage de blocs de pixels et la façon dont les pixels sont stockés en mémoire.

L’utilisation la plus courante en OpenGL du transfert de pixels est l’ajout d’images qui recouvrent la scène (photo d’un cockpit, nom du joueur, score…). Il est plus facile des les afficher en utilisant les coordonnées écrans (par exemple 800x600), il faut alors repasser en mode 2D (ce qui ne fait que changer la matrice de projection, tout ce qui est 3D ne bouge pas) :

glMatrixMode(GL_PROJECTION) ;

glLoadIdentity() ;

gluOrtho2D(0.0, (Glfloat) width, 0.0, (Glfloat) height) ;

glMatrixMode(GL_MODEL_VIEW) ;

glLoadIdentity() ;

La Raster Position

La position actuelle de rastérisation est l’origine du prochain bitmap affiché. Le bitmap est affiché au dessus et à droite de cette position. Exemple :

glRasterPos2i(20, 20) ;

La façon de trouver la position est la même que celle de glVertex dans le cas ou l’environnement est en 3D. Si la position est hors du clipping (zone d’affichage), la position est dite invalide. Pour obtenir la Raster Position actuelle et sa validité , on utilise :

GLfloat vector[4] ;

glGetFloatv(GL_CURRENT_RASTER_POSITION, vector) ;

glGetBooleanv(GL_CURRENT_RASTER_POSITION_VALID) ;

Dessiner le bitmap

Après avoir choisi la Raster Position, on peut dessiner le bitmap grâce à l’instruction glBitmap dont le prototype est :

void glBitmap (GLsizei width, GLsizei height, GLfloat Xbo Ybo Xbi Ybi, const GLubyte *bitmap) ;

Si la Raster Position est invalide, rien n’est affiché. On utilise Xbo et Ybo pour définir l’origine du bitmap par rapport à la Raster Position. Xbi et Ybi sont les incréments à la Raster Position après avoir affiché le bitmap. Pour choisir la couleur d’affichage du bitmap, on utilise glColor juste avant.

Le gros problème de cette fonction est qu’elle ne gère pas le format RGB ou RGBA (seulement le mode 8 bits). Les fonctions des paragraphes suivants sont un peu plus lourdes à manipuler, mais plus générales.

Lire, écrire et copier des images

Pour lire une image, la fonction glReadPixels doit être utilisée :

void glReadPixels (Glint x, y, Glsizei width, height, Glenum format, type, Glvoid *pixels) ;

x et y représentent le coin bas-gauche de l’image à lire. Width et height sont ses dimensions. Le format et le type servent à définir le type de structure qui sera utilisé pour le stockage. Les différents types de format sont :

Format

GL_COLOR_INDEX

GL_RGB Dans l’ordre : Rouge, Vert, Bleu

GL_RGBA RGB et après Alpha

GL_RED

GL_GREEN

GL_BLUE

GL_ALPHA

GL_LUMINANCE

GL_LUMINANCE_ALPHA luminance suivi de alpha

GL_STENCIL_INDEX

GL_DEPTH_COMPONENT

Type

GL_UNSIGNED_BYTE (8bits)

GL_BYTE (8 bits)

GL_BITMAP (même format que pour glBitmap)

GL_UNSIGNED_SHORT (16 bits)

GL_SHORT (16 bits)

GL_UNSIGNED_INT (32 bits)

GL_INT (32 bits)

GL_FLOAT (flottant en simple précision)

! Il faut bien faire attention de faire correspondre le type de *pixel avec celui indiqué par le format et le type.

Pour écrire une image, on utilise la fonction glDrawPixel :

void glDrawPixels(Glsizei widht, height, Glenum format, type, const Glvoid *pixels) ;

Cela dessine un rectangle contenant l’image pointée par *pixels à la Raster Position et de la taille widht * height. Les valeurs de format et de type sont les mêmes que celles de glReadPixels.

La fonction glCopyPixels permet de copier un rectangle d’un buffer spécifié par le cinquième paramètre (GL_COLOR, GL_STENCIL ou GL_DEPTH) vers la raster position.

void glCopyPixels(Glint x, y, Glsizei width, height, Glenum buffer) ;

Agrandissement, réduction et réflexion

Normalement, chaque pixel d’une image est écrit dans un pixel sur l’écran. La fonction

void glPixelZoom(Glfloat, zoomx, zoomy) ;

permet de modifier la taille que chaque pixel prend sur l’écran. Si on donne 2 pour chaque argument, chaque pixel est représenté à l’écran par 4 pixels. Des valeurs non entières et négatives sont permises. Les facteurs négatifs de zoom font une réflexion par rapport à la raster position.

13. Tests de validation et stencil buffer

Chaîne de tests

Il est tant de voir la liste complète des tests de validation d’un point :

à Scissor Test à Alpha Test à Stencil Test à Depth Test à […] à framebuffer

Scissor test

Le scissor test est paramétré par :

void glScissor( GLint x, GLint y, GLsizei width, GLsizei height )

Cela permet de limiter l’affichage à un rectangle dont le coin haut – gauche à pour coordonnées (x, y) dans la fenêtre. Par défaut (x, y) = (0, 0) et width et height ont la même taille que la fenêtre.

Le test est activé par : glEnable(GL_SCISSOR_TEST)

Alpha test

Un point peut être éliminé par sa valeur alpha avec :

void glAlphaFunc( GLenum func, GLclampf ref )

avec func = GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, ou GL_ALWAYS

Ne pas oublier, comme toujours : glEnable(GL_ALPHA_TEST)

Stencil test

Ce test est plus complexe, mais plus utile que les deux précédents.

Tout comme le depth-buffer, le stencil-buffer est un espace mémoire de la taille du framebuffer. Mais cette fois son utilisation est libre.

Sous glut, ne pas oublier de le rajouter à l’initialisation :

glutInitDisplayMode (GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH | GLUT_STENCIL)

Effacement

D’abord, le stencil étant comme le depth un buffer, il faut (le plus souvent) l’effacer à chaque image avec :

glClear(… | GL_STENCIL_BUFFER_BIT) ;

La valeur de remplissage est paramétrable avec :

void glClearStencil( GLint s )

Initialisation

Comme pour tous les tests, il faut appeler :

glEnable(GL_STENCIL_TEST) ;

Le test est paramétré par deux fonctions :

void glStencilFunc( GLenum func, GLint ref, GLuint mask)

qui sert à indiquer le test à effectuer, les paramètres sont :

· func = GL_NEVER, GL_LESS, GL_LEQUAL, GL_GREATER, GL_GEQUAL, GL_EQUAL, GL_NOTEQUAL, ou GL_ALWAYS

· ref = valeur de référence du test

· mask = masque binaire à appliquer à la valeur du stencil et à ref pour le test

void glStencilOp( GLenum fail, GLenum zfail, GLenum zpass )

qui sert à indiquer ce qui se passe suivant le résultat du test du stencil et du depth, avec :

· fail indique l’opération à effectuer sur la valeur dans le stencil lorsque le stencil test a échoué. Peut être égal à GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, ou GL_INVERT

· zfail indique de même l’opération à effectuer si le stencil est passé mais pas le depth

· zpass indique l’opération à effectuer si le stencil et le depth est passent

14. Ombres et reflets

La principale application du stencil buffer, ce sont les ombres et les reflets.

Reflets

Principe

Les reflets sur un plan sont très simples : ce n’est qu’un double d’un objet, recouvert d’une surface légèrement transparente pour donner l’illusion qu’il s’agit d’un reflet.

En plus, ce qui simplifie bien les choses, il existe la fonction :

void glScalef( GLfloat x, GLfloat y, GLfloat z )

qui multiplie la matrice courante par une matrice multipliant toutes les coordonnées entrées par les valeurs spécifiées, c’est-à-dire une matrice diagonale (x, y, z, 1).

Dans notre cas, pour effectuer une symétrie par rapport au plan y = 0, il suffit d’entrer glScalef(1.0f, -1.0f, 1.0f).

Seul problème : le reflet peut sortir de la surface miroir ! C’est là que le stencil buffer intervient. La surface réfléchissante est d’abord « virtuellement » dessinée, en fait elle est dessinée normalement sauf que pour empêcher l’affichage on utilise :

void glColorMask( GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha )

avec tous les paramètres à GL_FALSE. Cela permet de changer la valeur du stencil là où la surface va être dessinée.

Les étapes du reflets sont finalement :

·
Limitation du reflet à la surface réfléchissante :
glDisable(GL_DEPTH_TEST);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
Dessin de la surface réfléchissante
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glEnable(GL_DEPTH_TEST);

· Dessin du reflet :
glStencilFunc(GL_EQUAL, 1, 0xffffffff); /* draw if ==1 */
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glPushMatrix();
glScalef(1.0, -1.0, 1.0);
respositionnement des lumières
dessin de l’objet reflété
glPopMatrix();
respositionnement des lumières
glDisable(GL_STENCIL_TEST);

· Dessin de la surface réfléchissante :
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Dessin de la surface réfléchissante
glDisable(GL_BLEND);

· Dessin de l’objet

Exemple

Le programme est cette fois complet (même le main y est).

#include <GL/glut.h>

#include <sys/timeb.h>

#include <math.h>

GLint cube = 0;

void idle (void)

{

glutPostRedisplay ();

}

void drawFloor()

{

glColor4f(0.5f, 0.5f, 0.5f, 0.75f);

glBegin(GL_QUADS);

glVertex3f(-4.0, 4.0, 0.0);

glVertex3f( 4.0, 4.0, 0.0);

glVertex3f( 4.0, -4.0, 0.0);

glVertex3f(-4.0, -4.0, 0.0);

glEnd();

}

void SetLight()

{

GLfloat ambientProperties[] = {0.7f, 0.7f, 0.7f, 1.0f};

GLfloat diffuseProperties[] = {1.0f, 1.0f, 1.0f, 1.0f};

GLfloat positionProperties[] = {5.0f, 0.0f, 0.0f, 0.0f};

glLightfv( GL_LIGHT0, GL_AMBIENT, ambientProperties);

glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuseProperties);

glLightfv( GL_LIGHT0, GL_POSITION, positionProperties);

glEnable(GL_LIGHT0);

glEnable(GL_LIGHTING);

}

void display (void)

{

static timeb tt ,t;

static int total;

GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 1.0f};

GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 1.0f};

GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 1.0f};

glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

ftime(&t);

total += (t.time - tt.time)*1000 + (t.millitm - tt.millitm);

total %= 360*300;

tt = t;

glDisable(GL_DEPTH_TEST);

glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

glEnable(GL_STENCIL_TEST);

glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);

glStencilFunc(GL_ALWAYS, 1, 0xffffffff);

drawFloor();

glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

glEnable(GL_DEPTH_TEST);

glStencilFunc(GL_EQUAL, 1, 0xffffffff); /* draw if ==1 */

glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

glPushMatrix();

glScalef(1.0, 1.0, -1.0);

SetLight();

glTranslatef(sin(total/30.0f/180.0f*3.1415f)*5.0f, 0.0f, 5.0f);

glRotatef((total/30.0f), 0.5f, 0.3f, 0.8f);

glRotatef((total/50.0f), 0.5f, -0.3f, 0.8f);

SetLight();

glCallList(cube);

glPopMatrix();

SetLight();

glDisable(GL_STENCIL_TEST);

glEnable(GL_BLEND);

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

glDisable(GL_LIGHTING);

drawFloor();

glEnable(GL_LIGHTING);

glDisable(GL_BLEND);

glPushMatrix();

glTranslatef(sin(total/30.0f/180.0f*3.1415f)*5.0f, 0.0f, 5.0f);

glRotatef((total/30.0f), 0.5f, 0.3f, 0.8f);

glRotatef((total/50.0f), 0.5f, -0.3f, 0.8f);

glCallList(cube);

glPopMatrix();

glutSwapBuffers();

}

void init ()

{

GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 1.0f};

GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 1.0f};

GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 1.0f};

cube = glGenLists(1);

glNewList(cube, GL_COMPILE);

glBegin(GL_QUADS);

glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, Rouge);

glNormal3d(0.0,0.0,1.0);

glVertex3d(-1, 1, 1);

glVertex3d(-1, -1, 1);

glVertex3d( 1, -1, 1);

glVertex3d( 1, 1, 1);

glNormal3d(0.0,0.0,-1.0);

glVertex3d( 1, 1, -1);

glVertex3d( 1, -1, -1);

glVertex3d( -1, -1, -1);

glVertex3d( -1, 1, -1);

glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, Vert);

glNormal3d(1.0,0.0,0.0);

glVertex3d( 1, 1, 1);

glVertex3d( 1, -1, 1);

glVertex3d( 1, -1, -1);

glVertex3d( 1, 1, -1);

glNormal3d(-1.0,0.0,0.0);

glVertex3d( -1, 1, 1);

glVertex3d( -1, 1, -1);

glVertex3d( -1, -1, -1);

glVertex3d( -1, -1, 1);

glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, Bleu);

glNormal3d(0.0,-1.0,0.0);

glVertex3d( -1, -1, 1);

glVertex3d( -1, -1, -1);

glVertex3d( 1, -1, -1);

glVertex3d( 1, -1, 1);

glNormal3d(0.0,1.0,0.0);

glVertex3d( -1, 1, 1);

glVertex3d( 1, 1, 1);

glVertex3d( 1, 1, -1);

glVertex3d( -1, 1, -1);

glEnd();

glEndList();

}

void reshape (int width, int height)

{

glViewport (0, 0, width, height);

glMatrixMode (GL_PROJECTION);

glLoadIdentity ();

gluPerspective (45.0, ((GLfloat) width) / ((GLfloat) height),1.0,100.0);

glMatrixMode (GL_MODELVIEW);

glLoadIdentity ();

gluLookAt (0.0,-7.0,15.0, 0.0, 0.0, 0.0,0.0,0.0, 1.0);

glEnable(GL_DEPTH_TEST);

glEnable(GL_POLYGON_SMOOTH);

SetLight();

glutPostRedisplay();

}

int main (int argc, char *argv[])

{

glutInit (&argc, argv);

glutInitDisplayMode (GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH | GLUT_STENCIL);

glutInitWindowSize (640, 480);

glutCreateWindow ("Cours09.c");

glutDisplayFunc (display);

glutReshapeFunc (reshape);

glutIdleFunc (idle);

init();

glutMainLoop ();

return 0;

}

Matrice « miroir »

L’utilisation de glScale est beaucoup moins pratique dès que la surface n’est pas un plan parallèle à un axe. Heureusment, M.J. Kilgard (de SGI / NVidia) à mis en ligne la fonction :

Ombres

Principe

Le stencil buffer joue toujours son rôle de délimitation, pour l’ombre cette fois. Reste à dessiner l’ombre elle-même, or qu’est-ce qu’une ombre ? C’est la projection de tout un objet sur un plan suivant la direction de la lumière. Cela fait (devrait faire ?) immédiatement penser à « matrice de projection ». Effectivement il suffit de multiplier la matrice ModelView par une matrice de projection pour avoir le résultat voulu.

Quelques détails tout de même :

· l’ombre n’est pas le dessin de l’objet lui-même mais une surface noire (à moitié transparente). Bien que dans la réalité, ce n’est pas exactement cela : c’est tout le reste de la scène qui est plus éclairée. Cependant il est bien plus rapide d’assombrir là où est l’ombre que d’éclaircir tout le reste. S’il n’y a aucune lumière ambiante, l’ombre n’est pas transparente.

· avec cette méthode, il faut faire attention à ne pas assombrir deux fois le même point : ce n’est pas parce que deux surfaces cachent la lumière que l’ombre est deux fois plus sombre ! L’astuce consiste ici à incrémenter le stencil lorsque le stencil test et le depth test passent (dès qu’un point est assombri, il ne fait plus « partie » de la surface).
Cependant, il existe un cas où il faut volontairement oublier ce fait : lorsque l’objet projeté est en partie transparent. Il est alors possible d’avoir un assombrissement proportionnel au nombre de surfaces qui cachent la lumière. Sauf qu’il ne faut pas trop en abuser, le temps de calcul s’allongeant encore.

· le problème de la matrice de projection : elle n’est pas dans glu ! Alors remercions une fois encore M.J. Kilgard pour sa documentation en ligne :

/* Create a matrix that will project the desired shadow. */

enum { X = 0, Y, Z, W };

void shadowMatrix(GLfloat shadowMat[4][4], GLfloat groundplane[4], GLfloat lightpos[4])

{

GLfloat dot;

dot = groundplane[X] * lightpos[X] +

groundplane[Y] * lightpos[Y] +

groundplane[Z] * lightpos[Z] +

groundplane[W] * lightpos[W];

shadowMat[0][0] = dot - lightpos[X] * groundplane[X];

shadowMat[1][0] = 0.f - lightpos[X] * groundplane[Y];

shadowMat[2][0] = 0.f - lightpos[X] * groundplane[Z];

shadowMat[3][0] = 0.f - lightpos[X] * groundplane[W];

shadowMat[X][1] = 0.f - lightpos[Y] * groundplane[X];

shadowMat[1][1] = dot - lightpos[Y] * groundplane[Y];

shadowMat[2][1] = 0.f - lightpos[Y] * groundplane[Z];

shadowMat[3][1] = 0.f - lightpos[Y] * groundplane[W];

shadowMat[X][2] = 0.f - lightpos[Z] * groundplane[X];

shadowMat[1][2] = 0.f - lightpos[Z] * groundplane[Y];

shadowMat[2][2] = dot - lightpos[Z] * groundplane[Z];

shadowMat[3][2] = 0.f - lightpos[Z] * groundplane[W];

shadowMat[X][3] = 0.f - lightpos[W] * groundplane[X];

shadowMat[1][3] = 0.f - lightpos[W] * groundplane[Y];

shadowMat[2][3] = 0.f - lightpos[W] * groundplane[Z];

shadowMat[3][3] = dot - lightpos[W] * groundplane[W];

}

avec shadowMat la matrice à construire, groundpane l’équation du plan où projeter l’ombre (de type ax + by + cz + d = 0), et lightpos comme son nom l’indique (lightpos[3] de préférence à 0).

Et pour les coefficients du plan :

/* Find the plane equation given 3 points. */

enum { A = 0, B, C, D };

void findPlane(GLfloat plane[4], GLfloat v0[3], GLfloat v1[3], GLfloat v2[3])

{

GLfloat vec0[3], vec1[3];

/* Need 2 vectors to find cross product. */

vec0[X] = v1[X] - v0[X];

vec0[Y] = v1[Y] - v0[Y];

vec0[Z] = v1[Z] - v0[Z];

vec1[X] = v2[X] - v0[X];

vec1[Y] = v2[Y] - v0[Y];

vec1[Z] = v2[Z] - v0[Z];

/* find cross product to get A, B, and C of plane equation */

plane[A] = vec0[Y] * vec1[Z] - vec0[Z] * vec1[Y];

plane[B] = -(vec0[X] * vec1[Z] - vec0[Z] * vec1[X]);

plane[C] = vec0[X] * vec1[Y] - vec0[Y] * vec1[X];

plane[D] = -(plane[A] * v0[X] + plane[B] * v0[Y] + plane[C] * v0[Z]);

}

Les étapes de l’ombre sont alors :

·
Limitation de l’ombre à la surface :
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
Dessin de la surface
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glEnable(GL_DEPTH_TEST);
glDisable(GL_STENCIL_TEST);

· Dessin de l’objet

· Calcul de la matrice de projection (avec vfloor des points de la surface) :
GLfloat floorPlane[4];
GLfloat floorShadow[4][4];
findPlane(floorPlane, vfloor[1], vfloor[2], vfloor[3]);
shadowMatrix(floorShadow, floorPlane, lightZeroPosition);

·
Changement des paramètres pour dessiner une surface noire
transparente :
glStencilFunc(GL_EQUAL, 1, 0xffffffff); /* draw if ==1 */
glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_LIGHTING);
glDisable(GL_TEXTURE_2D);
glDisable(GL_DEPTH_TEST);
glColor4f(0.0, 0.0, 0.0, 0.5);

· Dessin de l’ombre :
glPushMatrix();
glMultMatrixf((GLfloat *) floorShadow);
dessin de l’objet
glPopMatrix();

Exemple

GLfloat vfloor[4][3] = {-4, 4, 0, 4, 4, 0, 4,-4, 0,-4,-4, 0};

GLfloat positionProperties[] = {0.0f, -3.0f, 5.0f, 0.0f};

void drawFloor()

{

glColor4f(0.5f, 0.5f, 0.5f, 0.75f);

glBegin(GL_QUADS);

glVertex3fv(vfloor[0]);

glVertex3fv(vfloor[1]);

glVertex3fv(vfloor[2]);

glVertex3fv(vfloor[3]);

glEnd();

}

void SetLight()

{

GLfloat ambientProperties[] = {0.7f, 0.7f, 0.7f, 1.0f};

GLfloat diffuseProperties[] = {1.0f, 1.0f, 1.0f, 1.0f};

glLightfv( GL_LIGHT0, GL_AMBIENT, ambientProperties);

glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuseProperties);

glLightfv( GL_LIGHT0, GL_POSITION, positionProperties);

glEnable(GL_LIGHT0);

glEnable(GL_LIGHTING);

}

void display (void)

{

static timeb tt ,t;

static int total;

GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 1.0f};

GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 1.0f};

GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 1.0f};

glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

ftime(&t);

total += (t.time - tt.time)*1000 + (t.millitm - tt.millitm);

total %= 360*300;

tt = t;

glDisable(GL_DEPTH_TEST);

glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

glEnable(GL_STENCIL_TEST);

glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);

glStencilFunc(GL_ALWAYS, 1, 0xffffffff);

drawFloor();

glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

glEnable(GL_DEPTH_TEST);

glDisable(GL_STENCIL_TEST);

glDisable(GL_LIGHTING);

drawFloor();

glEnable(GL_LIGHTING);

glEnable(GL_STENCIL_TEST);

glStencilFunc(GL_ALWAYS, 2, 0xffffffff);

glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

glPushMatrix();

glTranslatef(sin(total/30.0f/180.0f*3.1415f)*5.0f, 0.0f, 3.0f);

glRotatef((total/30.0f), 0.5f, 0.3f, 0.8f);

glRotatef((total/50.0f), 0.5f, -0.3f, 0.8f);

glCallList(cube);

glPopMatrix();

glStencilFunc(GL_EQUAL, 1, 0xffffffff); /* draw if ==1 */

glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);

glEnable(GL_BLEND);

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

glDisable(GL_LIGHTING);

glDisable(GL_DEPTH_TEST);

glColor4f(0.0, 0.0, 0.0, 0.5);

static GLfloat floorPlane[4];

static GLfloat floorShadow[4][4];

findPlane(floorPlane, vfloor[1], vfloor[2], vfloor[3]);

shadowMatrix(floorShadow, floorPlane, positionProperties);

glPushMatrix();

glMultMatrixf((GLfloat *) floorShadow);

glTranslatef(sin(total/30.0f/180.0f*3.1415f)*5.0f, 0.0f, 3.0f);

glRotatef((total/30.0f), 0.5f, 0.3f, 0.8f);

glRotatef((total/50.0f), 0.5f, -0.3f, 0.8f);

glCallList(cube);

glPopMatrix();

glEnable(GL_DEPTH_TEST);

glDisable(GL_BLEND);

glEnable(GL_LIGHTING);

glDisable(GL_STENCIL_TEST);

glFlush();

glutSwapBuffers();

}

15. Courbes et Surfaces calculées

Présentation

Pour certaines surfaces, comme par exemple les sphères, il se pose un problème : elles ne peuvent être qu’approchées par des triangles. Il faut faire un compromis : soit avoir une sphère très belle, ce qui peut prendre plusieurs milliers de triangles, ou bien avoir peu de triangles mais une sphère pas très sphérique. De plus, pourquoi avoir des surfaces précises si elles sont éloignées ? De toute façon elles ne font que quelques pixels.

D’où l’intérêt d’avoir des surfaces calculées : la précision de la surface est ajustée suivant les besoins (distance par rapport à l’œil), et même peut être ajustée dynamiquement suivant la puissance de la machine (si le programme tourne à 60 fps, une amélioration visuelle est raisonnable).

Quadrics

Présentation

Glu intègre déjà un certain nombre de formes : sphère, cylindre et disque. Le principe est toujours le même :

· Déclaration d’un « quadric » :
GLUquadricObj obj = gluNewQuadric();

· Appel(s) à l’une des fonctions suivantes (dessin immédiat) :
void gluCylinder( GLUquadricObj *qobj, GLdouble baseRadius, GLdouble topRadius, GLdouble height, GLint slices, GLint stacks )
void gluDisk( GLUquadricObj *qobj, GLdouble innerRadius, GLdouble outerRadius, GLint slices, GLint loops )
void gluPartialDisk( GLUquadricObj *qobj, GLdouble innerRadius, GLdouble outerRadius, GLint slices, GLint loops, GLdouble startAngle, GLdouble sweepAngle )
void gluSphere( GLUquadricObj *qobj, GLdouble radius, GLint slices, GLint stacks )

· Destruction avec :
void gluDeleteQuadric( GLUquadricObj *state )

Suivant les paramètres du rendu, il est possible de générer :

· Les normales (pour l’éclairage) :
void gluQuadricNormals( GLUquadricObj *qobj, GLenum normals )
avec normals = GLU_NONE, GLU_FLAT, ou GLU_SMOOTH

· Les coordonnées de texture :
void gluQuadricTexture( GLUquadricObj *qobj, GLboolean textureCoords )

· Le mode de dessin :
void gluQuadricDrawStyle( GLUquadricObj *qobj, GLenum drawStyle )
avec drawStyle = GLU_FILL, GLU_LINE, GLU_SILHOUETTE, ou GLU_POINT

Exemple

GLfloat positionProperties[] = {5.0f, -5.0f, 5.0f, 0.0f};

void SetLight()

{

GLfloat ambientProperties[] = {0.7f, 0.7f, 0.7f, 1.0f};

GLfloat diffuseProperties[] = {1.0f, 1.0f, 1.0f, 1.0f};

glLightfv( GL_LIGHT0, GL_AMBIENT, ambientProperties);

glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuseProperties);

glLightfv( GL_LIGHT0, GL_POSITION, positionProperties);

glEnable(GL_LIGHT0);

glEnable(GL_LIGHTING);

glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, ambientProperties);

}

void display (void)

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

GLUquadricObj *obj = gluNewQuadric();

gluQuadricNormals(obj, GL_SMOOTH);

gluQuadricDrawStyle(obj, GLU_FILL);

gluSphere(obj, 1.0, 20, 20);

gluDeleteQuadric(obj);

glFlush();

glutSwapBuffers();

}

Bézier

OpenGL intègre (ce n’est pas glu cette fois) les courbes et les surfaces de Bézier. Cependant les cartes grand public n’accélèrent pas leur calcul. Pour l’instant…

Le principe d’une courbe de Bézier est le suivant :

- Soit n point de contrôle, la courbe part du premier et arrive au dernier

- La courbe est calculée par récurrence, en rajoutant entre deux points de contrôle leur milieu jusqu’à la résolution de l’affichage. Seul les milieux du dernier niveau de récurrence sont affichés (en fait dès qu’un point, hormis les extrémités la courbe, a servi pour calculer un milieu, il est éliminé). Un petit dessin pour essayer de mieux comprendre :

Le principe est le même pour les surfaces.

Courbe

Fonctions OpenGL

La première chose à faire est de rentrer les points de contrôle :

void glMap1f( GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points )

Target

Indique quel type de coordonnées il faut générer. Les choix sont : GL_MAP1_VERTEX_3, GL_MAP1_VERTEX_4, GL_MAP1_INDEX, GL_MAP1_COLOR_4, GL_MAP1_NORMAL, GL_MAP1_TEXTURE_COORD_1, GL_MAP1_TEXTURE_COORD_2, GL_MAP1_TEXTURE_COORD_3, ou GL_MAP1_TEXTURE_COORD_4

Pour une courbe de Bézier, il faut choisir GL_MAP1_VERTEX_3. Mais il est aussi possible d’utiliser GL_MAP1_COLOR_4 pour créer un dégradé de couleurs : quelle différence entre générer des coordonnées (x, y,z) et (R, G, B) ?
u1, u2

Une courbe est ensuite repérée par un « axe courbe ». Par la suite, la courbe débutera en (u1) et se terminera en (u2)
Stride

Indique le nombre de flottants qui sépare un point de contrôle dans points du suivant. Le seul but de cette valeur est de pouvoir utiliser des tableaux de structure avec d’autres données que les points.
Order

Nombre de points dans le tableau
Points

Pointeur vers les points de contrôle

Il faut appeler glEnable avec le même target pour activer la courbe.

Ensuite la fonction :

void glEvalCoord1f( GLfloat u)

fait l’équivalent d’un glVertex(coordonnées du point repéré par u). Par exemple glEvalCoord1f(u1) à le même effet que glVertex(premier point de contrôle), et de même glEvalCoord1f(u2) à le même effet que glVertex(dernier point de contrôle). La valeur de u n’est pas limitée à [u1..u2]. Si elle sort de cette intervalle, cela prolonge simplement la courbe.

La fonction doit être appelée à l’interieur d’un glBegin / glEnd.

Exemple

void display (void)

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glPushMatrix();

static GLfloat ctrlpoints[4][3] = {

{-4.0, -4.0, -15.0}, {-2.0, 4.0, -15.0},

{ 2.0, -4.0, -15.0}, { 4.0, 4.0, -15.0}};

glColor3f(1.0f,1.0f,1.0f);

glMap1f(GL_MAP1_VERTEX_3, 0.0, 1.0, 3, 4, &ctrlpoints[0][0]);

glEnable(GL_MAP1_VERTEX_3);

glBegin(GL_LINE_STRIP);

for (GLfloat i = 0.0; i <= 1.0; i+=0.05f)

glEvalCoord1f(i);

glEnd();

glDisable(GL_MAP1_VERTEX_3);

glPointSize(5.0);

glColor3f(1.0, 1.0, 0.0);

glBegin(GL_POINTS);

for (int j = 0; j < 4; j++)

glVertex3fv(&ctrlpoints[j][0]);

glEnd();

glPopMatrix();

glFlush();

glutSwapBuffers();

}

void reshape (int width, int height)

{

glViewport (0, 0, width, height);

glMatrixMode (GL_PROJECTION);

glLoadIdentity ();

gluPerspective (45.0, ((GLfloat) width) / ((GLfloat) height),1.0,100.0);

glMatrixMode (GL_MODELVIEW);

glLoadIdentity ();

gluLookAt(0, 0, 0, 0, 0, -1, 0, 1, 0);

glEnable(GL_DEPTH_TEST);

glEnable(GL_POLYGON_SMOOTH);

}

Surface

Fonctions OpenGL

Les fonctions sont les mêmes avec des « 2 » au lieu de « 1 », et le tableau des points de contrôle est à 2 dimensions :

void glMap2f( GLenum target, Glfloat u1, Glfloat u2, GLint ustride, GLint uorder, Glfloat v1, Glfloat v2, GLint vstride, GLint vorder, const GLfloat *points )

void glEvalCoord2f( GLfloat u, GLfloat v)

Il y cependant une fonction supplémentaire bien utile :

void glEvalMesh2( GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2 )

qui est purement et simplement :

glBegin(mode);

for (j = j1; j <= j2; j += 1)

for (i = i1; i <= i2; i += 1)

glEvalCoord2(i·du + u1, j·dv + v1);

glEnd();

avec du = (u2 – u1) / i et dv = (v2 – v1) / j

(mode est au choix : GL_QUAD_STRIP, GL_LINE_STRIP, GL_POINTS).

Exemple

void display (void)

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glPushMatrix();

static GLfloat ctrlpoints[4][4][3] = {

{{-1.5, -1.5, 4.0}, {-0.5, -1.5, 2.0},

{ 0.5, -1.5, -1.0}, { 1.5, -1.5, 2.0}},

{{-1.5, -0.5, 1.0}, {-0.5, -0.5, 3.0},

{ 0.5, -0.5, 0.0}, { 1.5, -0.5, -1.0}},

{{-1.5, 0.5, 4.0}, {-0.5, 0.5, 0.0},

{ 0.5, 0.5, 3.0}, { 1.5, 0.5, 4.0}},

{{-1.5, 1.5, -2.0}, {-0.5, 1.5, -2.0},

{ 0.5, 1.5, 0.0}, { 1.5, 1.5, -1.0}}

};

glEnable(GL_LINE_SMOOTH);

glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, &ctrlpoints[0][0][0]);

glEnable(GL_MAP2_VERTEX_3);

static GLfloat tt= 0;

if ((tt += 0.05f) > 360) tt -= 360.0f;

glTranslatef(0.0f, 0.0f, -10.0f);

glRotatef(tt, 0.7f, 0.0f, 1.0f);

glColor3f(1.0, 1.0, 1.0);

glMapGrid2f(9, 0.0, 1.0, 9, 0.0, 1.0);

glEvalMesh2(GL_LINE, 0, 9, 0, 9);

glDisable(GL_MAP2_VERTEX_3);

glPopMatrix();

glFlush();

glutSwapBuffers();

}

void reshape (int width, int height)

{

glViewport (0, 0, width, height);

glMatrixMode (GL_PROJECTION);

glLoadIdentity ();

gluPerspective (45.0, ((GLfloat) width) / ((GLfloat) height),1.0,100.0);

glMatrixMode (GL_MODELVIEW);

glLoadIdentity ();

gluLookAt(0, 0, 0,

0, 0, -0.5f,

0, 1, 0);

glEnable(GL_DEPTH_TEST);

glEnable(GL_POLYGON_SMOOTH);

}

16. Ce qui n’a pas de place ailleurs

Dessiner deux fois au même endroit

Il se peut qu’il faille dessiner deux fois au même endroit, pour mettre un logo sur un rectangle par exemple. Le depth-buffer pose alors un problème : il n’est pas précis, ou en tout cas pas suffisament précis pour être sûr qu’ancun point du logo ne va être éliminé.

La solution la plus simple : désactiver le depth-test. Mais ce n’est à utiliser que lorsque l’on est sûr qu’il n’y a rien de plus proche de la caméra.

Une autre solution, parfaitement sûre mais plus compliquée, c’est d’utiliser le stencil buffer : dessiner d’abord le logo et changer la valeur du stencil là où le logo est dessiné. Dessiner ensuite le rectangle sur lequel est placé le logo, mais seulement là où le stencil n’a pas bougé.

Dernière solution, la fonction :

void glPolygonOffset(Glfloat factor, Glfloat units)

avec factor qui permet de « lisser » la profondeur (limite la dérivée du depth, sans intérêt ici), et units le nombre « d’unités » de décalage à ajouter au point. L’unité est dépendante de l’implémentation GL, et est spécifiée comme étant « le plus petit décalage tel que deux points ayant la même profonfeur soit quand même disctincts ». Dans le cas qui nous intéresse, cela donne :

glPolygonOffset(0, 1);

Il faut appeler glEnable avec GL_POLYGON_OFFSET_FILL pour activer le décalage des points.

Cette méthode fonctionne assez bien, mais la précision n’atteint pas celle du stencil : si le nombre de calculs (nombre de matrcies dans modelview) est trop différent pour le logo et la surface, il se peut très bien (pour cause de calcul en flottant et d’imprécision du depth) que cette technique ne marche pas. Elle est par contre très fiable si les matrices utilisées sont les mêmes pour le logo et la surface.

Clipping

Il est possible de rajouter des plans de clipping, pour exclure certaines parties du monde dessiné. Il en existe toujours au moins deux : ceux qui limitent l’intervalle de z (automatiquement générés lors de l’appel de gluPerspective).

Un plan quelconque peut être ajouté avec :

void glClipPlane( GLenum plane, const GLdouble *equation )

plane étant GL_CLIP_PLANEi (=GL_CLIP_PLANE0 + i), la valeur max étant GL_MAX_CLIP_PLANES. Equation est un tableau de quatre flottants donnant l’équation du plan (ax + by + cz + d = 0). Le nombre minimal de plans possible est 6 (pour définir un cube).

Pour l’activer : glEnable(GL_CLIP_PLANEi).

La règle pour savoir si un vertex est coupé ou pas est : « If the dot product of the eye coordinates of a vertex with the stored plane equation components is positive or zero, the vertex is in with respect to that clipping plane. Otherwise, it is out. »

Multitexturing

Le but de cette partie est de montrer les possibilités d’OpenGL en combinant plusieurs des fonctions vues jusqu’à présent.

Le « multitexturing » consiste à faire plusieurs rendus d’une même surface en changeant certaines options, pour obtenir des effets lumineux. L’exemple donné ici consiste à dessiner deux fois le même objet, une fois avec une texture non éclairée, puis une deuxième fois pour ajouter un rendu sans texture mais éclairé.

En OpenGL, cela donne à peu près :

glEnable(GL_BLEND);

glDepthFunc(GL_LEQUAL);

glDepthMask(GL_TRUE);

glDisable(GL_LIGHTING);

glDisable(GL_DITHER);

glShadeModel(GL_FLAT);

glEnable(GL_TEXTURE_2D);

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

Draw();

glEnable(GL_LIGHTING);

glDisable(GL_TEXTURE_2D);

glDepthFunc(GL_EQUAL);

glDepthMask(GL_FALSE);

glEnable(GL_DITHER);

glBlendFunc(GL_ONE, GL_ONE);

Draw();

²


Getting Started with OpenGL GLUT using Pelles C
By Christopher Hill

GLUT is the OpenGL Utility Toolkit. GLUT is good for learning OpenGL since it is platform independent, and you don't have to worry about Windows programming. The same program should compile under Windows, Linux, UNIX, etc. GLUT applications compile as console applications so you can jump right in and start trying things out. The only drawback of course is no system specific windows components such as menus, buttons, etc. you can still however use the keyboard, a mouse, a joystick, etc. for user input. GLUT also has it's own menu system.

Since GLUT is platform independent the natural language to choose is ANSI C, and for Windows of course, Pelles C. In this tutorial I will show you how to set up Pelles C for programming in GLUT and also show some sample code.

Before you begin you will need the GLUT header and library files, glut32.lib, glut.lib and glut.h. A download is available here that the code samples are tested with. These files are from the OpenGL SuperBible book 3rd Edition CD. The very latest Win32 GLUT files can be found here: http://www.xmission.com/~nate/glut.html The code however is not guaranteed to work with anything other than the files supplied.

Once you have downloaded the header and library files add them to the proper Pelles C directories as shown:
PellesC\Lib\Win\glut32.lib
PellesC\Lib\Win\glut.lib
PellesC\Include\Win\gl\glut.h

Now start Pelles C and start a new Win32 program (EXE) as a new workspace. Select File - New - Source code and paste the code below.

//---------------------------------------------------------------------
//
Getting Started with OpenGL GLUT
// A Very Simple OpenGL Example
//
//
Draws a simple window with a rectangle in it
//---------------------------------------------------------------------

#include <windows.h>
#include <gl\gl.h>
#include <gl\glut.h>

void init(void);
void display(void);

int main (int
argc, char **argv)
{
glutInit(&argc,
argv);
glutInitDisplayMode(GLUT_DOUBLE |
GLUT_RGB);
glutInitWindowSize(250, 250);
glutInitWindowPosition(100,
100);
glutCreateWindow("My First OpenGL
Application");
init();
glutDisplayFunc(display);
glutMainLoop();
return
0;
}

void
init(void)
{
glClearColor(0.0, 0.0, 0.0, 0.0);
glColor3f(0.0, 0.0,
1.0);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-10.0,
10.0, -10.0, 10.0, -10.0, 10.0);
}

void
display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glRectf(-5.0, 5.0, 5.0,
-5.0);
glutSwapBuffers();
}
//---------------------------------------------------------------------

Save the code as myfirst.c and add it to your project. Now we just need to set up our Project options. Under Project - Project Options - Linker - Library and object files add glut32.lib and glut.lib. Under Subsystem Type select Console. Under Compiler - Calling convention select __cdecl. Select File - Save all.

Under Project, Compile, Build then Execute. You should have a beautiful black screen with a blue square on it!

If you receive an error that you are missing glut32.dll you can download it here . Place it in your windows/system32 directory.


Getting Started with OpenGL GLUT using Pelles C - How it Works

Getting Started with OpenGL GLUT using Pelles C
By Christopher Hill
How it Works

Vu dans .... Getting Started with OpenGL GLUT using Pelles C - How it Works

Now we will take a look at exactly how our first OpenGL GLUT application works.

First the glutInit function is called, which initializes the GLUT library:

int main (int argc, char **argv)
{
glutInit(&argc, argv);

Next the initial display mode is set, using a double buffered window, with RGB color:

glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);

The next three lines create the window:

glutInitWindowSize(250, 250);
glutInitWindowPosition(100, 100);
glutCreateWindow("My First OpenGL Application");

Next OpenGL is initialized:

void init(void)
{
glClearColor(0.0, 0.0, 0.0, 0.0); /* set the background color to black */
glColor3f(0.0, 0.0, 1.0); /* set the foreground color to blue */
glMatrixMode(GL_PROJECTION); /* set the current matrix mode */
glLoadIdentity();
glOrtho(-10.0, 10.0, -10.0, 10.0, -10.0, 10.0);
}

In OpenGL, matrices are used to transform and project 3D data onto the 2D viewing plane of the screen. There are three matrix modes, GL_MODELVIEW, GL_PROJECTION, and GL_TEXTURE. GL_PROJECTION clips and sizes the viewing volume. GL_MODELVIEW describes the duality of viewing and modeling transformations. GL_TEXTURE applies matrix operations to the texture matrix.

Study these two lines:

glLoadIdentity();
glOrtho(-10.0, 10.0, -10.0, 10.0, -10.0, 10.0);

glLoadIdentity() replaces the current matrix with the identity matrix (in this case the Projection matrix). Next is the glOrtho call. The glOrtho function creates an orthographic view. An orthographic view is a parallel projection, it contains no perspective.

The parameters for glOrtho are:

left, right | Specify the coordinates for the left and right vertical clipping planes.
bottom, top | Specify the coordinates for the bottom and top horizontal clipping planes.
zNear, zFar | Specify the distances to the nearer and farther depth clipping planes. These values are negative if the plane is to be behind the viewer.

Now, examine these two lines:

glutDisplayFunc(display);
glutMainLoop();

The first function sets the function that GLUT will use whenever it needs to
update the view for the current window, in this case, display(). glutMainLoop() is then called which begins the GLUT event processing loop. Once called, this routine will never return. It will call as necessary any callbacks that have been registered.

Examine the display() function:

void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glRectf(-5.0, 5.0, 5.0, -5.0);
glutSwapBuffers();
}

First glClear is called with the GL_COLOR_BUFFER_BIT parameter.
This will clear the window with the color specified earlier using
glClearColor. Next, the rectangle is drawn, using the glRectf
function.

OpenGL Function-Naming Conventions
Most OpenGL functions follow a naming convention that tells you which library the function is from, how many arguments and what type of arguments the function takes.

For example, glColor, which sets the current foreground color. This function has several prototypes, for example:

glColor3f(float, float, float);
glColor4f(float, float, float, float);
glColor3d(double, double, double);
glColor3i(int, int, int);

OpenGL functions are usually formatted like this:

glColor3f()

gl | Color | 3 | f
gl library | Root command | Number of arguments | Type of argument

Even though most OpenGL functions can accept any type, it's usually best to pass floats. OpenGL uses floating point values for all its internal calculations anyways.

The next function, glutSwapBuffers(), swaps the back buffer to the screen buffer. In double buffered mode, drawing is not done on the screen, drawing is done on an offscreen buffer. Once drawing is done, the entire back buffer is copied to the screen, thus producing smooth animation. Of course, in this simple example there is no animation, but without a double buffer even moving the window around the screen will cause the rectangle to flicker.


Drawing in 3D with OpenGL GLUT
Points, Lines, and Polygons
By Christopher Hill

This tutorial covers drawing points, lines, and faces in 3D. Some basic user input functionality is also introduced. Use the same procedures for setting up Pelles C compiler for Win32 as outlined in the previous lesson, Getting Started with OpenGL GLUT.

The OpenGL cartesian coordinate system is right-handed, meaning +X is to the right, +Y is up, and +Z points out of the screen. The center (X,Y,Z) point is the origin point, or (0,0,0). When we draw basic geometric primitives we specify the coordinates of multiple vertices, a single vertex at a time.

As shown above, OpenGL has 10 functions for drawing points, lines, and faces. They all have a similar syntax and are very easy to use. All you need to do is use the glVertex3f() function flanked by calls to glBegin() and glEnd().

Example of drawing two points
glBegin(GL_POINTS);

glVertex3f(4.0, 0.0, 4.0);
glVertex3f(1.0, -4.0, -2.0);

glEnd();

Example of drawing three lines
glBegin(GL_LINES);

glVertex3f(6.0, 4.0, 2.0);
glVertex3f(2.0, -4.0, 3.3);

glVertex3f(5.0, 8.0, 8.0);
glVertex3f(-4.7, 5.0, -3.0);

glVertex3f(0.0, 0.0, 0.0);
glVertex3f(6.0, -1.0, -7.0);

glEnd();

Example of drawing a single triangle
glBegin(GL_TRIANGLES);

glVertex3f(-3.0, -3.0, 2.0);
glVertex3f(3.0, -3.0, -1.0);
glVertex3f(0.0, 3.0, 4.0);

glEnd();

The rest are quite simple to figure out, here is a table of all the available OpenGL point, line, and face drawing functions:

Points | GL_POINTS
Lines | GL_LINES, GL_LINE_STRIP, GL_LINE_LOOP
Faces | GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_QUADS, GL_QUAD_STRIP, GL_POLYGON

Now for a note about color. Notice in the code below the use of glColor() before some of the vertex definitions. When OpenGL sees different colors for different vertices of polygons it blends the colors across the face.

Common Color Reference
OpenGL Color Function | Color | Color HEX | Color RGB
glColor3f(0.0, 0.0, 0.0); | Black | #000000 | rgb(0,0,0)
glColor3f(1.0, 0.0, 0.0); | Red | #FF0000 | rgb(255,0,0)
glColor3f(0.0, 1.0, 0.0); | Green | #00FF00 | rgb(0,255,0)
glColor3f(0.0, 0.0, 1.0); | Blue | #0000FF | rgb(0,0,255)
glColor3f(1.0, 1.0, 0.0); | Yellow | #FFFF00 | rgb(255,255,0)
glColor3f(0.0, 1.0, 1.0); | Cyan | #00FFFF | rgb(0,255,255)
glColor3f(1.0, 0.0, 1.0); | Magenta | #FF00FF | rgb(255,0,255)
glColor3f(1.0, 1.0, 1.0); | White | #FFFFFF | rgb(255,255,255)

Now it's time to try things out! As you can see, the code below is based on the previous lesson with some additions. Some simple calls to drawing functions have been added along with a keyboard routine using the glutKeyboardFunc() function. The glRotatef() function is used to rotate the data on the screen in 3 dimensions.

//---------------------------------------------------------------------
//
Drawing in 3D with openGL GLUT
// Points, Lines and Faces
//
//
Includes basic user keyboard
input
//---------------------------------------------------------------------

#include <windows.h>
#include <gl\gl.h>
#include <gl\glut.h>

void init(void);
void display(void);
void keyboard(unsigned char, int, int);

int main (int
argc, char **argv)
{
glutInit(&argc,
argv);
glutInitDisplayMode(GLUT_DOUBLE |
GLUT_RGB);
glutInitWindowSize(200, 200);
glutInitWindowPosition(50,
50);
glutCreateWindow("Drawing in
3D");
init();
glutDisplayFunc(display);
glutKeyboardFunc(keyboard);
//set keyboard handler
glutMainLoop();
return 0;
}

void
init(void)
{
glClearColor(0.0, 0.0, 0.0,
0.0);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-15.0,
15.0, -15.0, 15.0, -15.0, 15.0);
}

void
display(void)
{
glClear(GL_COLOR_BUFFER_BIT);

glBegin(GL_QUADS);
glColor3f(1.0, 0.0,
0.0); //red
glVertex3f(-3.0, -3.0, 0.0);
glVertex3f(3.0, -3.0,
0.0);
glColor3f(1.0, 0.0, 1.0); //blue
glVertex3f(3.0, 3.0,
0.0);
glVertex3f(-3.0, 3.0, 0.0);
glEnd();

glBegin(GL_TRIANGLES);
glColor3f(1.0, 0.0, 0.0);
glVertex3f(0.0, 6.0, 0.0);
glColor3f(0.0, 0.0, 0.9f);
glVertex3f(-3.0, 3.0, 0.0);
glVertex3f(3.0, 3.0, 0.0);
glEnd();

glBegin(GL_LINES);
glColor3f(0.0, 0.0, 1.0); //blue

glVertex3f(0.0,
7.0, 0.0);
glVertex3f(0.0, -4.0, 0.0);
glEnd();

glutSwapBuffers();
}

void keyboard(unsigned char key, int x, int y)
{
/* this is the keyboard event handler
the x and y parameters are the mouse
coordintes when the key was struck */
switch (key)
{
case 'u':
case 'U':
glRotatef(3.0, 1.0, 0.0, 0.0); // rotate up
break;
case 'd':
case 'D':
glRotatef(-3.0, 1.0, 0.0, 0.0); // rotate down
break;
case 'l':
case 'L':
glRotatef(3.0, 0.0, 1.0, 0.0); // rotate left
break;
case 'r':
case 'R':
glRotatef(-3.0, 0.0, 1.0, 0.0); // rotate right
}
display(); // repaint the window
}
//---------------------------------------------------------------------

There is a reason drawing only occurs on a single plane in this example. Experiment and you will see faces do not intersect properly. This effect is due to a lack of depth buffering, also known as Z-buffering, which will be covered in the next lesson.


Creating 3D interactive Worlds
with OpenGL GLUT
By Christopher Hill

This tutorial introduces Perspective, 3D Interaction, Depth Testing, Material and Lighting properties and Regular Poyhedra drawing functions.

Try out the code below then visit the How it Works page for information on what the code is doing. You can travel through this 3D world, reference the list below for keyboard controls.

Keyboard Controls
"n": move north
"s": move south
"e": move east
"w": move west
"t": toggle depth-testing

"y": yaw
"p": pitch
"r": roll

"u": move up
"d": move down

//---------------------------------------------------------------------
// Creating a 3D Interactive World
// Perspective, Solids, Polyhedra, and Depth Buffering
//
//---------------------------------------------------------------------

#include <windows.h>
#include <gl\gl.h>
#include <gl\glut.h>
#include <gl\glu.h> // GLU extension library - already in Pelles C

// material and lighting
properties
float materialAmbient[]={0.3, 0.2, 0.2, 1.0};
float
materialDiffuse[]={0.9, 0.1, 0.1, 1.0};
float materialSpecular[]={0.0, 1.0,
1.0, 0.5};
float lightAmbient[]= {0.5, 0.5, 1.5, 1.0};
float
lightDiffuse[]= {0.9, 0.9, 0.9, 1.0};
float lightSpecular[]={0.0, 0.0, 0.0,
1.0};
float lightPosition[]={1.0, 1.0, 1.0, 0.0};

void init(void);
void display(void);
void keyboard(unsigned char, int, int);
void resize(int, int);

int is_depth; // depth testing flag

int
main (int argc, char **argv)
{
glutInit(&argc,
argv);
glutInitDisplayMode(GLUT_DOUBLE |
GLUT_RGB);
glutInitWindowSize(600, 600);
glutInitWindowPosition(40,
40);
glutCreateWindow("3D Interactive
World");
init();
glutDisplayFunc(display);
glutKeyboardFunc(keyboard);

//keep the aspect ratio constant by trapping the window resizes */

glutReshapeFunc(resize);

glutMainLoop();
return 0;
}

void init(void)
{
glClearColor(0.0, 0.0, 0.0, 0.0);
glEnable(GL_DEPTH_TEST);
is_depth = 1;
glMatrixMode(GL_MODELVIEW);

// material and lighting properties

glEnable(GL_POINT_SMOOTH);
glEnable(GL_LINE_SMOOTH);
glLightfv(GL_LIGHT0,GL_AMBIENT,lightAmbient);
glLightfv(GL_LIGHT0,GL_DIFFUSE,lightDiffuse);
glLightfv(GL_LIGHT0,GL_POSITION,lightPosition);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT,materialAmbient);
glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,materialDiffuse);
glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,materialSpecular);
glMaterialf(GL_FRONT_AND_BACK,
GL_SHININESS, 20.0);

}

void
display(void)
{
if (is_depth)
glClear(GL_COLOR_BUFFER_BIT |
GL_DEPTH_BUFFER_BIT);
else
glClear(GL_COLOR_BUFFER_BIT);
/* draw the
ground */
glBegin(GL_QUADS);
//glColor3f(0.2f, 0.2f,
0.2f);
glVertex3f(-100.0, -50.0, -100.0);
//glColor3f(0.4f, 0.4f,
0.4f);
glVertex3f(-100.0, -50.0, 100.0);
//glColor3f(0.6f, 0.6f,
0.6f);
glVertex3f(100.0, -50.0, 100.0);
//glColor3f(0.8f, 0.8f,
0.8f);
glVertex3f(100.0, -50.0, -100.0);
glEnd();

// Draw stuff here

//glTranslatef(0.0, 0.0,
1.0);

glutSolidIcosahedron();

//glTranslatef(0.0, 0.0, 100.0);
glutWireCube(10.0);

glPushMatrix(); //save the current
matrix
glTranslatef(10.0, 0.0, 0.0);
glutSolidDodecahedron();
glTranslatef(-20.0, 0.0, 0.0);
glutSolidOctahedron();
glTranslatef(10.0, 10.0, 0.0);
glutSolidCube(1.0);
glTranslatef(0.0, -20.0, 0.0);
glutSolidTetrahedron();

glPopMatrix(); //return the previous matrix

glutSwapBuffers();
}

void keyboard(unsigned char key, int x, int y)
{
/* Keyboard controls:

"n": move north
"s": move south
"e": move east
"w": move west
"t": toggle depth-testing

"y": yaw
"p": pitch
"r": roll

"u": move up
"d": move down

*/
switch (key)
{

case 'y': //yaw
case 'Y':
glRotatef(10.0, 0.0, 1.0, 0.0);
break;

case 'p': //pitch
case 'P':
glRotatef(10.0, 1.0, 0.0, 0.0);
break;

case 'r': //roll
case 'R':
glRotatef(10.0, 0.0, 0.0, 1.0);
break;

case 'w':
case 'W':
glTranslatef(5.0, 0.0, 0.0);
break;

case 'e':
case 'E':
glTranslatef(-5.0, 0.0, 0.0);
break;

case 'n':
case 'N':
glTranslatef(0.0, 0.0, 5.0);
break;

case 's':
case 'S':
glTranslatef(0.0, 0.0, -5.0);
break;

case 'd':
case 'D':
glTranslatef(0.0, 5.0, -0.0);
break;

case 'u':
case 'U':
glTranslatef(0.0, -5.0, 0.0);
break;

case
't':
case 'T':
if (is_depth)
{
is_depth =
0;
glDisable(GL_DEPTH_TEST);
}
else
{
is_depth =
1;
glEnable(GL_DEPTH_TEST);
}
}

display();
}

void
resize(int width, int height)
{
if (height == 0) height = 1;

glMatrixMode(GL_PROJECTION);
glLoadIdentity();

// divide width by height to get aspect ratio
gluPerspective(45.0, width / height, 1.0, 400.0);

// set initial position
glTranslatef(0.0, 0.0, -100.0); //south 100 units from origin

glMatrixMode(GL_MODELVIEW);
}

//---------------------------------------------------------------------

Program Output


Tutoriaux sur la gestion dynamique de l'éclairage avec OpenGL

Voici une série de tutoriaux que j'ai réalisé sur la gestion de l'éclairage dynamique avec OpenGL. Le but premier de cette série est de montrer pour chaque tutoriel une nouvelle méthode pour gérer des lumières dynamiques sans avoir à se préoccuper des contraintes lié au nombres maximum de lumières que peut gérer une carte 3D. En effet, les cartes 3D ne peuvent gérer qu'un nombre limité de lumières à la fois (on peut connaitre ce nombre en récuperant l'attribut GL_MAX_LIGHTS d'OpenGL.)

Gestion de l'éclairage dynamique

Tutoriel | Description
Partie 1 : l'éclairage par vertex | Tutoriel sur la mise en place d'un système simple de gestion de l'éclairage dynamique : l'éclairage par vertex. Ce tutoriel détail les differents mode de gestion de l'éclairage dynamique que nous verrons dans la serie de tutoriaux et montre comment mettre en oeuvre l'éclairage par vertex.
Partie 2 : Le light mapping dynamique simple | Tutoriel sur la mise en place d'un système de light mapping dynamique simple. Ce tutoriel tourne sur toutes cartes graphiques supportant le multi texturing. Il explique le fonctionnement du light mapping standard et explique comment lui ajouter un système pour gérer des lumières dynamiques.
Partie 3 : Le light mapping à base de texture 3D | Ce tutoriel reprend le précédent mais utilise une texture 3D comme light map dynamique au lieu d'utiliser deux textures 2D. Ce tutoriel est relativement simple, mais c'est une étape obligatoire avant de commencer à étudier l'éclairage par pixel.
Partie 4 : l'éclairage par pixel simple | Ce tutoriel présente l'éclairage par pixel et introduit toutes les notions nécessaires à sa compréhension. C'est sans doute le tutoriel le plus compliqué à appréhender. J'y explique notamment le principe de l'espace local des vertex et l'utilisation de normal maps.
Partie 5 : l'éclairage par pixel complet | Ce tutoriel approfondi la gestion de l'éclairage par pixel. J'y traite de l'utilisation d'une cube map de normalisation pour résoudre les problèmes d'interpolation des vecteurs vu dans le tutoriel 4, et je présente aussi une méthode basé sur un rendu multi passes pour gérer les lumières colorées.
Partie 6 : éclairage par pixel complexe à base de shaders. | Ce dernier tutoriel montre comment mettre en place un éclairage complexe en utilisant un fragment program ARB (l'équivalent OpenGL des pixel shaders en assembleur de DirectX). Dans ce tutoriel, nos lumières supportent les reflets spéculaire mais aussi le parallax mapping.

Remerciements

Je tiens à remercier tous ceux qui m'ont permis de réaliser ces articles, notamment l'équipe 2D/3D/Jeux de developpez.com, et plus particulièrement à buchs, loka, fearyourself et miles pour leurs relectures.

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2006 Michel de VERDELHAN. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -

Developpez.com


Gestion dynamique de la lumière avec OpenGL - Partie 1 : l'éclairage par vertex

Table des matières Plier Déplier

  • I. But de ce tutoriel.
  • II. Pourquoi mettre en place son propre système de gestion de la lumière.
  • III. Les différents modèles d'éclairage.
    • III-1. L'éclairage par vertex
    • III-2. Le light mapping
    • III-3. L'éclairage par pixel
    • III-4. Autres modèles
  • IV. Un premier système de gestion de la lumière : l'éclairage par vertex.
    • IV-1. Principe de l'éclairage par vertex
    • IV-2. Structure de données.
    • IV-3. Gestion de l'atténuation.
    • IV-4. Gestion de l'ombrage.
    • IV-5. Le rendu final
  • V. Conclusions
  • VI. Liens

Ce tutoriel a pour but la mise en place d'un système de gestion des lumières dynamiques par vertex lighting avec OpenGL. nous présenterons aussi une vue d'ensemble des méthodes de gestion des lumières dynamiques dans les moteurs 3D, qui seront traitées pour la plupart dans les prochains tutoriaux.

I. But de ce tutoriel.

Cette série de tutoriaux a pour but d'étudier la mise en oeuvre des systèmes d'éclairage dans un moteur 3D.

Il existe de très nombreuses façons de gérer son éclairage dans un moteur 3D. Dans cette série de tutoriaux, nous aborderons les techniques d'éclairages les plus connus. Néanmoins, nous ne traiterons que les techniques permettant de gérer un éclairage dynamique de nos scènes, et nous ne parlerons que des lumières ponctuelles omnidirectionnelles à rayon fini. Nous n'aborderons donc pas les sources de lumières infini, ou les spots de lumière.

Cette série de tutoriaux se décomposera en 6 parties où nous verrons :
  1. L'éclairage par vertex.
  2. Le light mapping dynamique simple.
  3. Le light mapping dynamique avec texture 3D.
  4. L'éclairage par pixel simple.
  5. L'éclairage par pixel standard.
  6. La création d'un système d'éclairage complexe basé sur les shaders.

Pour la mise en oeuvre, nous utiliserons les librairies OpenGL et GLUT pour gérer l'affichage. Néanmoins, l'ensemble des principes étudié ici est facilement portable sous Direct3D.

II. Pourquoi mettre en place son propre système de gestion de la lumière.

Une question souvent posée sur les forums est de savoir comment gérer les lumières dans un moteur 3D. En effets, même si OpenGL propose une gestion des lumières standard en interne, celle ci n'est pas réellement utilisable dans un moteur complexe. Bien qu'accélérées par le matériel, les lumières OpenGL posent plusieurs problèmes lorsqu'on souhaite les utiliser dans un moteur complexe :

  • L'éclairage est calculé par vertex.
    Cette façon de gérer l'éclairage oblige à avoir un nombre de polygones élevé pour obtenir une qualité d'éclairage intéressante. En effet, l'éclairage étant interpolé entre les sommets d'un polygone, si les polygones sont trop étendus, l'éclairage ne sera pas bon au centre du polygone. La seule solution pour résoudre ce problème est de découper les grand polygones en plusieurs petits polygones (on parle d'augmentation de la tesselation). Cette solution résout relativement bien le problème de l'interpolation de la lumière, mais elle a aussi comme conséquence d'augmenter fortement la géométrie ce qui a un impact important sur les performances, notamment au niveau de :
    - l'envoi de la géométrie à la carte 3D
    - l'utilisation de certains algorithmes (par exemple : les collisions) qui seront plus coûteux, ou encore pour le calcul des ombres volumétriques qui dépendent fortement de la géométrie.
  • L'éclairage étant calculé par la carte graphique, le nombre de lumières utilisables simultanément est limité par le matériel. C'est principalement cette limitation qui impose la mise en place de systèmes de gestion de la lumière dans les moteurs 3D. En effet, on imagine mal un jeu moderne qui ne permet d'avoir que 8 lumières dans un niveau. Il existe bien entendu des méthodes qui permettent d'activer/désactiver des lumières quand elles ne sont plus visibles. Ceci permet de repousser la limite du nombre de lumières, mais cette limite existe toujours. Imaginez un très grand hangar avec seulement 8 lumières là où on en aurait mis plusieurs dizaines dans la réalité, le résultat risque de ne pas être très crédible.

Tous ces problèmes ont contraint les créateurs de moteurs 3D à mettre en place des systèmes de gestion de la lumière plus ou moins complexes.

III. Les différents modèles d'éclairage.

Il existe de nombreuses manières d'appréhender la lumière. La plupart des systèmes de gestion de la lumière sont basé sur les modèles de Phong et de Blinn. Néanmoins, de nombreux moteurs 3D n'implémentent pas ces modèles dans leurs totalités.

Je ne détail pas ici les modèles de Phong et Blinn, mais le net regorge de sites qui les traitent très bien.

Le principe de base d'un système de gestion de la lumière est de pouvoir noircir plus ou moins les éléments d'une scène en fonction de la position et de l'orientation du pixel par rapport à la lumière.

Dans la plupart des modèles d'éclairage utilisé, la lumière se décompose généralement en 4 composantes :
  1. La composante ambiante : elle correspond à la luminosité minimum de la scène.
    Elle permet en fait de simuler les sources de lumières indirectes. En effet, dans la réalité, les objets sont éclairé par 2 types de sources lumineuses : les sources directes et les sources indirectes. Les sources directes sont les générateurs de lumières (ampoule, soleil et autre), et les sources indirectes sont des objets qui ont reçu de la lumière et en restituent une partie.
    Comme les algorithmes de calcul de restitution de la lumière indirecte ont une complexité temporelle trop importante pour être utilisé en temps réel, le résultat est approché en utilisant une couleur ambiante commune à toute la scène.
  2. elle correspond à la lumière reçu par l'objet. Elle varie en fonction de la distance et de l'orientation de la surface par rapport à la lumière. Le calcule de la lumière diffuse est généralement

    Ou est la couleur diffuse, est le vecteur normal à la surface, le vecteur allant de la surface à la lumière, est le facteur d'atténuation en fonction de la distance, et le . représente le produit scalaire entre les deux vecteurs. Image non disponible l'éclairage d'une surface est fonction de la normale à la surface et de la direction vers la lumière

    Ce calcul est généralement nommé NdotL (dot représentant le produit scalaire) dans la littérature, nous utiliserons donc ce terme au cours des tutoriaux

  3. La composante spéculaire : elle correspond à la réflexivité de la surface. Par exemple, une surface métallique brillante aura une forte composante spéculaire alors qu'un pneu de voiture n'en aura pas.
  4. La composante émissive : plus anecdotique, la composante émissive correspond à la lumière émise par la surface. Cette composante est toujours affichée même quand la surface est ombrée.

Généralement, les modèles d'éclairage considèrent que la lumière est additive. Ce qui signifie que si une surface est éclairée par deux lumières, le résultat final sera l'addition des deux éclairages. Ainsi une surface éclairée par une lumière rouge {1,0,0} et par une lumière bleu {0,0,1} aura la même couleur que si elle avait été éclairé par une lumière violette {1,0,1}. Ceci est particulièrement important pour déterminer la façon dont seront affichées les lumières par la suite.

Maintenant que nous avons vue cette introduction basique des modèles d'éclairages, nous allons voir des systèmes classiques pour les mettre en oeuvre.

Les types de systèmes de gestion de l'éclairage dynamiques les plus connus sont :

III-1. L'éclairage par vertex

C'est, historiquement, le premier modèle d'éclairage en temps réel utilisé. En effet, l'éclairage en temps réel étant coûteux, lorsque les ordinateurs n'avaient pas de carte accélératrice, il fallait utiliser un système très rapide, même si il présente les désavantages présenté précédemment.

C'est ce modèle dont nous allons étudier la mise en oeuvre dans ce premier tutoriel.

III-2. Le light mapping

Comme son nom l'indique (si vous n'êtes pas anglophobe), le light mapping consiste à stocker l'éclairage dans une texture. Cet algorithme se décompose en 2 étapes :

  1. Le rendu de la scène en utilisant la texture d'éclairage. Les parties dans l?ombre sont affichées en noir alors que le reste est affiché dans la couleur de l'éclairage local.
  2. Le rendu de la scène avec la texture normale, en multipliant la couleur précédente. Ainsi, si la couleur précédente était du noir, le résultat final sera du noir, donc une zone ombrée. Si la couleur précédente était du blanc, la couleur final obtenu est celle de la texture, donc une zone éclairée.

Cette technique a comme gros avantage de permettre de pré calculer la texture d'éclairage pour les lumières statiques, et, étant basé sur l'utilisation de textures, d'utiliser principalement les ressources de la carte graphique.

Le light mapping est à la base un système de gestion des lumières statiques, mais il est facile de lui ajouter une gestion de l'éclairage dynamique.
Cette méthode sera vue dans les tutoriaux 2 et 3.

III-3. L'éclairage par pixel

Basé sur des extensions comme le DOT3 bump mapping ou les shaders, l'éclairage par pixel est possible de nos jours car les cartes graphiques ayant évolué, le développeur peut maintenant intervenir sur la façon dont sont calculé les pixels avant qu'ils ne soient ajouté à la scène. Cette méthode de gestion de l'éclairage permet un très bon rendu graphique, tout en étant très flexible. Le choix peut donc être fait entre la qualité graphique ou la performance, en implémentant seulement une partie d'un modèle d'éclairage par exemple.

Cette méthode permettant de nombreuses implémentations, nous l'étudierons durant 3 tutoriaux.

III-4. Autres modèles

Il existe d'autres méthodes (qui sont généralement calculées par pixel) comme les spherical harmonics par exemple, mais ces techniques étant plus anecdotiques dans l'industrie 3D, nous ne les étudierons pas ici.

IV. Un premier système de gestion de la lumière : l'éclairage par vertex.

Nous allons maintenant voir comment mettre en oeuvre un système d'éclairage par vertex, mais avant de voir sa mise en oeuvre précise, je vais essayer d'en expliquer rapidement le principe.

IV-1. Principe de l'éclairage par vertex

L'éclairage par vertex consiste simplement à effectuer l'ensemble des calculs d'éclairage au niveau des vertex. Ce système est très simple et peut se résumer à l'algorithme en pseudo code suivant :

Sélectionnez

initialiser la luminosite de tout les vertex a la lumiere ambiante
pour chaque lumiere
pour chaque face
pour chaque vertex de la face
vertex.couleur = lumière.couleur * (NdotL) * atténuation
fin pour
fin pour
fin pour
rendre la scène

L'algorithme peut être simplifié en ne prenant pas en compte les vertex des faces, mais directement la géométrie, mais cela a pour conséquence de ne pas pouvoir avoir de vertex à normales multiples.

Un vertex à normales multiples est un point de l'espace qui est partagé entre plusieurs faces ayant des orientations très différentes. Par exemple un cube a 8 vertex, mais ses faces ont des orientations suffisamment différentes pour qu'on ne souhaite pas lisser les normales de ces vertex (on parle de normal smoothing), le vertex aura alors plusieurs normales (dans le cas du cube, une par face qui utilise le vertex). Généralement la gestion des normales lissées est laissée à la discrétion de l'artiste qui crée la scène via un outil de modélisation. Ici, nous utiliserons donc les normales contenues dans le fichier.

Pour commencer l'étude de la mise en oeuvre, nous allons voir la structure de données utilisée par nos calculs, ensuite nous aborderons le problème du calcul de l'atténuation de la luminosité, et pour finir, nous parlerons de l'ombrage.

Nous ne traiterons pas des composantes spéculaire et émissive de la lumière, mais leur implémentation est relativement facile à mettre en place.

IV-2. Structure de données.

Ici, nous allons parler des structures de donnée utilisé pour le calcule de la luminosité de la scène. J'utilise dans ce tutoriel des classes basiques pour gérer mes vecteurs, couleurs et coordonnées de textures. Ces classes ne seront pas détaillées ici mais elles sont disponibles avec le code.

Pour le tutoriel nous utilisons une scène stockée dans un fichier .obj. Le chargement de ce fichier n'entre pas en compte dans ce tutoriel, mais il nous permet de récupérer les informations de géométrie sous la forme de 4 tableaux qui contiennent :

  1. Les positions des vertex.
  2. Les normales.
  3. Les coordonnées de textures.
  4. Les faces.

Les faces sont des structures qui contiennent les indices des vertex/normales/coordonnées de texture de chacun des points de la face. Ici nous ne traitons que des faces triangulaires.
Nous avons donc besoin d'une structure pour stocker ces faces

La structure gerant une face.
Sélectionnez

struct Face
{
    unsigned int vertexIndex[3];
    unsigned int texCoordIndex[3];
    unsigned int normalIndex[3];
    Color color[3];
};

Ici, la face contient aussi une couleur pour chacun de ses vertex, c'est la couleur qui sera utilisée pour déterminer l'éclairage final du vertex. Cette information est propre à ce tutoriel et disparaîtra dans les tutoriaux suivants.

Nous avons besoin d'une classe qui va gérer notre géométrie. Cette classe doit pouvoir stocker les informations sur les vertex, normales, coordonnées de textures et sur les faces. Elle doit aussi permettre de charger un fichier de géométrie, gérer l'éclairage et le rendu de la scène.

La classe gerant un modèle (une scène)
Sélectionnez

class Model
{
private:
    Vecteur * vertex;
    Vecteur * normals;
    TexCoord * texCoords;
    Face * faces;
    int nbVertex;
    int nbNormals;
    int nbTexCoord;
    int nbFaces;
public:
    Model(void);
    virtual ~Model(void);
    bool load(const std::string & filePath);
    void initLighting(const Color& ambiant);
    void addLight(Light& light);
    void render();
};

La méthode load() sert à charger la géométrie depuis un fichier, elle n'est pas particulièrement intéressante ici, la seul information qui nous intéresse est que le chargement prend en compte les normales partagées, nous avons donc un modèle dont certains vertex ont plusieurs normales et donc un rendu cohérent des cubes et autres formes avec des angles important entre les faces.

La méthode initLighting prend en paramètre la couleur ambiante et l'assigne à tout les vertex de la scène. Cette méthode donne donc :

La méthode d'initialisation de la lumière
Sélectionnez

void Model::initLighting(const Color& ambiant)
{
    for (int i = 0; i < nbFaces; i++)
    {
        faces[i].color[0] = ambiant;
        faces[i].color[1] = ambiant;
        faces[i].color[2] = ambiant;
    }
}

La méthode addLight prend en paramètre une lumière, effectue les calculs d'éclairage et les appliquent aux vertex. Le code donne donc :

La méthode permettant d'ajouter une lumière au modèle
Sélectionnez

void Model::addLight(Light& light)
{
    // pour chaque face
    for (int i = 0; i < nbFaces; i++)
    {
        // pour chaque vertex de la face
        for (int j = 0; j < 3; j++)
        {
            // on calcul la lumiere
            Color c = light.computeLighting(vertex[faces[i].vertexIndex[j]],normals[faces[i].normalIndex[j]]);
            // et on l'addition avec la lumiere precedente
            faces[i].color[j].r += c.r;
            faces[i].color[j].g += c.g;
            faces[i].color[j].b += c.b;
        }
    }
}

Avec computeLighting, la fonction de calcul de la luminosité du vertex. Cette méthode sera vue plus loin.

Ici la luminosité calculée est additionnée à celle calculée précédemment, car, comme nous l'avons vue, la lumière est additive.

Il ne nous reste plus que la méthode permettant le rendu de la scène. Elle est relativement simple, et ici, je n'utilise aucune optimisation à base de tableaux de vertex ou autre, j'effectue un simple rendu en mode immédiat.

Méthode de rendu du modèle
Sélectionnez

void Model::render()
{
    glBegin(GL_TRIANGLES);
    // pour chaque face
    for(int i = 0;i < nbFaces; i++)
    {
        // pour chaque vertex de la face
        for (int j = 0; j < 3; j++)
        {
            // on recupere la couleur, les coordonnees de texture et la position
            Color &c = faces[i].color[j];
            TexCoord &tc = texCoords[faces[i].texCoordIndex[j]];
            Vecteur &v = vertex[faces[i].vertexIndex[j]];
            // et on les envoient à openGL
            glColor3f(c.r,c.g,c.b);
            glTexCoord2f(tc.u,tc.v);
            glVertex3f(v.x,v.y,v.z);
        }
    }
    glEnd();
}

Maintenant que nous avons notre classe de gestion de la géométrie, nous avons besoins d'une classe qui gère une lumière.

Pour nous simplifier la gestion des calculs, c'est cette classe qui va calculer l'éclairage des vertex en fonction de leur position et de leur normale.

La classe de gestion des lumières
Sélectionnez

class Light
{
private:
    float radius;
    Color color;
    Vecteur position;
public:
    Light(void);
    virtual ~Light(void);
    void setRadius(float r);
    float getRadius();
    void setColor(float r, float g, float b);
    Color getColor();
    void setPosition(float x, float y, float z);
    Vecteur getPosition();
    Color computeLighting(const Vecteur & position, const Vecteur & normal);
};

Notre lumière contient simplement une position, un rayon et une couleur. Mis à part les accesseur et modificateur, cette classe ne contient qu'une méthode intéressante : computeLighting qui prend en paramètre la position d'un vertex et sa normal et retourne sa couleur d'éclairage. C'est cette méthode qui permet de calculer l'éclairage du vertex comme nous allons le voir.

IV-3. Gestion de l'atténuation.

La première chose à prendre en compte dans notre calcul de la luminosité est l'atténuation de la lumière. En effet plus notre vertex est loin de la lumière, moins il est éclairé, et si il dépasse le rayon d'action de la lumière, il n'est plus du tout éclairé.

Il existe plusieurs équations pour calculer l'atténuation (linéaire, quadratique et autre). Ici, nous utiliserons une équation linéaire. C'est à dire qu'un vertex situé à mi-distance du rayon de la lumière recevra la moitié de sa luminosité.
L'équation donne donc

atténuation = max ( 0, 1 ? (distance / rayon))

ou atténuation est le facteur d'atténuation de la lumière en fonction de la distance, distance est la distance du vertex par rapport à la position de la lumière et rayon est le rayon maximum de la lumière.

Le code du calcul de l'atténuation donne donc :

Code du calcul de l'attenuation
Sélectionnez

// on calcul le verteur allant du vertex à la lumiere.
Vecteur vertexToLight = this->position - position;
float attenuation = std::max<float>(0.0f,1.0f - (vertexToLight.getLength() / radius));

Nous calculons le vecteur allant du vertex à la lumière. Ici, le sens du vecteur n'a pas d'importance étant donné que ce qui nous intéresse est sa longueur, mais il est néanmoins important de le calculer dans le bon sens car il est réutilisé par la suite dans la gestion de l'ombrage.

IV-4. Gestion de l'ombrage.

Ce qui est appelé ombrage en infographie est à différencier des ombres portées. L'ombre portée correspond à l'ombre générée par un objet sur un autre objet. L'ombrage lui est juste le fait qu'une face est moins éclairé si elle ne fait pas face à la lumière et est complètement ombrée si elle tourne le dos à la lumière.

Nous voulons donc obtenir ce comportement pour nos vertex. Pour cela, nous allons justement utiliser le fameux calcul NdotL. Mais d'abord un petit rappel de maths sur le produit scalaire s'impose.

Le produit scalaire entre deux vecteurs normalisé (de longueur 1) a une propriété intéressante :

Si les 2 vecteurs sont identiques, le produit scalaire vaut 1. Si les 2 vecteurs ont la même direction, le produit scalaire est compris dans ]0..1]. Si les 2 vecteurs sont perpendiculaires, le produit scalaire vaut 0, si ils sont de sens opposé, le produit scalaire est compris dans [-1..0[, et si ils sont complètement opposé, le produit scalaire vaut -1.

Appliqué à notre problème, si le produit scalaire entre la normale et le vecteur vertexToLight est supérieur à 0, c'est que la face est éclairé, sinon, elle est dans l'ombre. Et, encore plus intéressant, si la normal est exactement identique au vecteur vertexToLight (c'est a dire si le polygone fait exactement face à la lumière), le produit scalaire vaut 1. Nous avons donc, à moindre coût, le calcul exacte de l'ombrage de nos faces avec un simple produit scalaire (et une normalisation de vecteur car il faut que le vecteur vertexToLight soit normalisé pour que ce calcul fonctionne). Nous avons donc retrouvé notre NdotL, mais nous l'appliquons par vertex et non par faces.

Notre fonction de calcul de la luminosité devient donc :

Calcul de l'attenuation en prenant en compte l'ombrage
Sélectionnez

// on calcul le verteur allant du vertex a la lumiere.
Vecteur vertexToLight = this->position - position;
float attenuation = 1.0f - (vertexToLight.getLength() / radius);
// on normalise le vecteur
vertexToLight.normalise();
// on calcul le produit scalaire NdotL avec N = normal et L = vertexToLight
attenuation *= vertexToLight*normal;
attenuation = std::max<float>(0.0f,attenuation);

Maintenant que nous avons calculé la luminosité de notre vertex, nous devons calculer sa couleur. Ceci se fait simplement en multipliant la couleur de la lumière par la luminosité du vertex. Nous obtenons donc le code final de la méthode computeLighting :

La methode de calcul de la couleur d'un vertex
Sélectionnez

Color Light::computeLighting(const Vecteur & position, const Vecteur & normal)
{
    // on calcul le verteur allant du vertex à la lumiere.
    Vecteur vertexToLight = this->position - position;
    float attenuation = 1.0f - (vertexToLight.getLength() / radius);
    // on normalise le vecteur
    vertexToLight.normalise();
    // on calcul le produit scalaire
    attenuation *= vertexToLight*normal;
    attenuation = std::max<float>(0.0f,attenuation);
    Color ret = this->color;
    ret.r *= attenuation;
    ret.g *= attenuation;
    ret.b *= attenuation;
    return ret;
}

IV-5. Le rendu final

Maintenant que nous avons tout ce qu'il faut pour calculer notre éclairage, il nous faut l'afficher. Ceci se fait très simplement en respectant la séquence suivante :

La séquence de code à suivre pour rendre la scène
Sélectionnez

scene->initLighting(ambiant);
// on ajoute nos lumieres
scene->addLight(light1);
scene->addLight(light2);
// on active la texture
glEnable(GL_TEXTURE_2D);
// on affiche la scene
scene->render();

Ce qui donne le resultat suivant :

Image non disponible
Scène éclairée par 3 lumières : 1 rouge, 1 bleu et 1 blanche

V. Conclusions

La méthode d'éclairage par vertex est simple à mettre en oeuvre et présente comme avantage important par rapport aux techniques que nous étudierons plus tard de dessiner la scène qu'une seule fois. Nous économisons donc du temps sur l'envoi de la géométrie, mais c'est du coup le processeur qui prend en charge la majorité des calculs.

Un autre défaut vu précédemment est le problème des artefacts d'éclairage sur les polygones trop étendu. Cette artefact se produit principalement au niveau du calcul de l'atténuation. En effet, lorsqu'on place une lumière sur un polygone trop étendu, on peut tomber sur la situation ou le rayon de la lumière n'est pas suffisant pour éclairer les vertex du polygone, il n'est donc pas éclairé alors que la lumière le touche.

Image non disponible
Cas ou l'atténuation de l'éclairage par vertex pose problème

Pour résoudre ces problèmes, nous allons devoir changer de méthode d'éclairage et passer au light mapping que nous verrons dans le prochain tutoriel.

VI. Liens

Vous pouvez télécharger les sources de ce tutoriel ici
ici

Image non disponible retour au menu. Tutoriel 2 : light mapping simple. Image non disponible

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2006 Michel de VERDELHAN. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -

Developpez.com


Gestion dynamique de la lumière avec OpenGL - Partie 2 : le light mapping simple

Table des matières Plier Déplier

  • I. Pourquoi utiliser le light mapping.
  • II. Le light mapping : un système simple et puissant.
    • II-1. Rappels sur le blending.
    • II-2. Principe du light mapping.
  • III. Modifications du programme.
    • III-1. Gérer les extensions.
    • III-2. Modifications de la classe de model.
    • III-3. Modification du calcul de la luminosité.
    • III-4. Le rendu final
  • IV. Conclusions.
  • VI. Liens

Ce tutoriel ainsi que celui qui le suit montre la mise en oeuvre d'un système de gestion de lumières dynamiques par light mapping. Ce tutoriel détaille le principe du light mapping et utilise une mise en oeuvre simple à base de textures 2D.

I. Pourquoi utiliser le light mapping.

Nous avons vu dans le tutoriel précédent qu'utiliser les vertex pour calculer la luminosité est simple à mettre en oeuvre mais pose des problèmes au niveau de l'atténuation de la lumière sur les polygones étendus. Ce problème peut être contourné en augmentant la tesselation de la scène, mais dans un moteur de jeu cette solution n'est généralement pas utilisable. En effet, augmenter la tesselation va augmenter la géométrie, ce qui va accentuer la charge de travail des algorithmes basé sur cette géométrie (collisions, ombres volumétriques et autres.)

Une solution à ce problème a été trouvée par John Carmack pour le jeu Quake 1. Cette solution se base sur l'utilisation de textures pour stocker la luminosité des surfaces. On utilise donc 2 textures par polygone : une texture standard contenant l'image qu'on souhaite afficher sur notre polygone, et une texture de luminosité appelée light map, d'où le nom de la méthode : le light mapping.

Cette méthode étant basée sur des textures et non plus directement sur la géométrie, les problèmes lors du calcul de l'atténuation sont donc supprimés.

Nous allons voir dans les deux tutoriaux à venir comment mettre en place un système de light mapping.

II. Le light mapping : un système simple et puissant.

Le light mapping est basé sur la modulation de textures. Je vais donc faire un bref rappel sur le blending qui va nous permettre d'utiliser cette modulation de textures. Ensuite je détaillerai le light mapping, puis je vais expliquer les modifications apportées au tutoriel précédent pour obtenir l'effet souhaité.

II-1. Rappels sur le blending.

L'API OpenGL propose en standard une gestion du blending. Le blending consiste à effectuer plusieurs rendu de la scène et à mélanger les couleurs de la scène rendue précédemment avec celles qu'on est en train de rendre (to blend : mélanger en anglais).

Il existe plusieurs familles de blending. Parmi elles, deux nous intéresse plus particulièrement :

  • le blending additif : la couleur courante est additionnée à la couleur précédente. Le blending additif a comme avantage d'être indépendant de l'ordre d'affichage, on peut donc rendre la scène dans l'ordre qu'on souhaite, le résultat sera le même. Image non disponible
  • Le blending multiplicatif : la couleur courante est multipliée à la précédente. Comme pour le blending additif, l'ordre d'affichage n'a pas d'importance. Ce blending est souvent utilisé avec une composante en niveau de gris, ce qui permet d'assombrir le résultat précédent. Image non disponible

Ces deux modes de blending peuvent s'obtenir en passant à la fonction glBlendFunc() les paramètres (GL_ONE,GL_ONE) pour le blending additif et (GL_DST_COLOR, GL_ZERO) pour le blending multiplicatif.

On peut aussi passer les paramètres (GL_DST_COLOR,GL_SRC_COLOR) pour obtenir un blending multiplicatif + additif. Ce blending va multiplier la couleur précédente par la couleur courante et va additionner la couleur courante au résultat. Ce blending peut être utilisé par le light mapping pour obtenir un effet de saturation des couleurs.

Nous ne traiterons pas d'autres modes de blending ici car ils ne sont pas utiles pour le light mapping.

II-2. Principe du light mapping.

Maintenant que nous savons comment fonctionne le blending (au moins la partie du blending qui nous intéresse), nous allons reparler du light mapping.

Le light mapping est un algorithme de rendu de la lumière multi- passes. La première passe consiste à afficher le niveau en affichant uniquement les textures de luminosité (light map). Une fois cet affichage effectué, nous avons donc notre scène rendu en niveaux de gris (ou en niveaux de couleur si on souhaite avoir des lumières colorées). Ensuite, il ne reste plus qu'à activer le blending multiplicatif et rendre la seconde passe en affichant le niveau texturé. Le résultat final sera le niveau texturé mais avec des zones d'ombres dans les textures correspondantes aux zones non éclairée.

Image non disponible Les 2 passes du light mapping et le rendu final.

À la base, le light mapping est un système créé pour gérer des lumières statiques. Le calcul des light maps peut être très coûteux si on utilise des algorithmes complexes (radiosité ou autre) pour calculer la luminosité des texels. Toutefois, on peut facilement utiliser une simplification pour prendre en compte des lumières dynamiques sphériques.

Pour cela, nous allons simuler une texture en 3 dimensions représentant une sphère de lumière à l'aide d'une texture en 2D particulière et du multitexturing. Cette texture, appelée light map dynamique, est en fait un cercle de luminosité dont les bords doivent impérativement être noir.

Image non disponible
La light map dynamique

Utilisée en mode non répétitif (GL_CLAMP_TO_EDGE) et avec une utilisation judicieuse du multitexturing, on peut obtenir une approximation d'une texture 3D efficace. Le concept consiste à utiliser la première unité de texture comme un tube lumineux (on calcul les coordonnées de texture sur le plan XY par exemple), puis à n'utiliser que la ligne centrale de la deuxième unité de texture (donc une bande de luminosité allant du noir au noir en passant par le blanc) comme facteur d'atténuation sur le troisième axe (dans notre exemple : Z).

Le calcul de nos coordonnées de texture pour nos deux unités de textures donne donc :

texture1.texcoord : {u = vertexToLight.x / rayon + 0,5 ; v = vertexToLight.y / rayon + 0,5}

texture2.texcoord : {u = vertexToLight.z / rayon + 0,5 ; 0,5}

Nous ajoutons 0,5 aux coordonnées de texture car on considère que le centre de la lumière est au centre de la texture et non pas à son origine. La dernière coordonnée de texture est positionnée à 0,5 car on souhaite utiliser la ligne centrale de la texture.

Ce calcule n'effectue pas réellement une sphère, mais un cube. Cependant, étant donné que notre texture représente un cercle, le résultat visuel s'approche d'une sphère. (Essayer de changer la light map par une texture blanche avec juste les bords noirs pour voir la différence).

Maintenant que nous savons comment calculer les coordonnées de textures, il faut afficher les lumières. Comme nous l'avons vu plus haut, le light mapping s'effectue en 2 passes, et, étant donné que le blending utilisé est multiplicatif, l'ordre de ces 2 passes n'est pas important. On peut rendre d'abord les light maps puis les textures ou l'inverse car l'équation d'éclairage est :

couleur finale = couleur light map * couleur texture.

Néanmoins pour la mise en place du light mapping dynamique, il faut changer la façon dont est rendu le niveau. Nous allons d'abord rendre le niveau avec les light maps, puis nous afficherons les lumières (avec du blending additif), pour finalement multiplier le résultat par les textures du niveau. Ceci donne donc l'équation suivante :

couleur finale = (couleur light map + couleur lumières dynamiques) * couleur texture.

Qui ne donne pas le même résultat que si nous avions d'abord rendu les textures, puis les light maps et finalement les lumières dynamiques dont l'équation est :

couleur finale = (couleur texture * couleur light map) + couleur lumières dynamiques

Nous avons donc besoin de modifier notre programme pour prendre en compte ces changements de calculs, mais aussi pour gérer le multitexturing.

III. Modifications du programme.

Dans cette section, nous allons voir les modifications apportées au tutoriel précédent pour pouvoir utiliser le light mapping dynamique. Nous verrons d'abord comment charger les fonctions de l'extension de multitexturing, puis nous détaillerons les changements dans les méthodes de gestion de la lumière, pour finalement voir la séquence de commandes OpenGL à utiliser pour obtenir le rendu voulu.

III-1. Gérer les extensions.

Pour gérer le multitexturing dans notre application nous avons besoin de deux fonctions que nous déclarons dans le fichier Main.cpp comme variables globales :

Sélectionnez

PFNGLMULTITEXCOORD2FARBPROC        glMultiTexCoord2fARB = NULL;
PFNGLACTIVETEXTUREARBPROC        glActiveTextureARB = NULL;

La fonction glMultiTexCoord2fARB sert à envoyer les coordonnées de texture 2D pour une unité de texture donnée et la fonction glActiveTextureARB permet de definir quelle unité de texture est en cours d'utilisation.

Pour avoir accès aux fonctions des extension, il ne faut pas oublier d'inclure le fichier glext.h

Comme nous aurons aussi besoins de la fonction d'envoi des coordonnées de textures dans notre classe de Model, nous la déclarons en externe dans le fichier Model.cpp

Sélectionnez

extern PFNGLMULTITEXCOORD2FARBPROC        glMultiTexCoord2fARB;

Maintenant que nous avons déclaré nos fonctions, il faut les charger. Charger une fonction consiste à récupérer son adresse auprès du pilote de la carte graphique. Pour cela, nous ajoutons une fonction dans Main.cpp qui va s'occuper de charger les extensions pour ce tutoriel et ceux à venir. Cette fonction donne donc :

La fonction de chargement des extensions
Sélectionnez

void initExtensions()
{
    if (glutExtensionSupported("GL_ARB_multitexture"))
    {
        glMultiTexCoord2fARB =    (PFNGLMULTITEXCOORD2FARBPROC)wglGetProcAddress("glMultiTexCoord2fARB");
        glActiveTextureARB =    (PFNGLACTIVETEXTUREARBPROC)wglGetProcAddress("glActiveTextureARB");
        if (!glActiveTextureARB || !glMultiTexCoord2fARB )
        {
            fprintf(stderr,"impossible d'initialiser l'extension GL_ARB_multitexture\n");
            exit(0);
        }
    }
    else
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_ARB_multitexture\n");
        exit(0);
    }
}

Ici, nous utilisons la fonction wglGetProcAddress pour récupérer l'adresse des fonctions de multitexturing. Cette fonction étant une fonction wgl, elle est propre à la plateforme Windows et n'est pas portable. Il existe une fonction glxGetProcAddress pour Linux, ou encore une fonction portable dans la SDL.

Maintenant que nous avons chargé les fonctions, nous pouvons les utiliser comme n'importe quelle fonction OpenGL.

III-2. Modifications de la classe de model.

Pas de changement dans l'interface de la classe Model, par contre, les méthodes initLighting, addLight et render ne vont plus effectuer la même chose.

La méthode initLighting sert toujours à paramétrer l'éclairage ambiant. Dans un système avec light maps statiques, cette méthode consisterait à afficher la scène avec les light maps statiques, mais comme nous n'en avons pas, ici, nous nous contenterons d'afficher la lumière ambiante. Le code donne donc :

La méthode d'affichage de la couleur ambiante
Sélectionnez

void Model::initLighting(const Color& ambiant)
{
    glColor3f(ambiant.r,ambiant.g,ambiant.b);
    glBegin(GL_TRIANGLES);
    for(int i = 0;i < nbFaces; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            Vecteur &v = vertex[faces[i].vertexIndex[j]];
            glVertex3f(v.x,v.y,v.z);
        }
    }
    glEnd();
}

La méthode addLight permet d'ajouter une lumière à la scène. Comme nous l'avons vu, elle doit dessiner la scène en calculant l'éclairage des polygones. On suppose que la méthode est toujours appelée au bon endroit, c'est-à-dire que le blending additif est activé et que la couleur ambiante a déjà été rendue. Il faut aussi que les deux premières unités de textures contiennent la light map dynamique. Ici, la méthode computeLighting de la classe Light ne calcule plus une couleur, mais des coordonnées de texture en 3D qui sont retournées sous la forme d'un vecteur 3D. Comme nous utilisons du blending additif, l'ordre dans lequel sont ajouté les lumières n'a pas d'importance. Le code de la méthode donne donc :

La méthode permettant d'ajouter une lumière à la scène
Sélectionnez

void Model::addLight(Light& light)
{
    glColor3f(light.getColor().r,light.getColor().g,light.getColor().b);
    glBegin(GL_TRIANGLES);
    // pour chaque face
    for (int i = 0; i < nbFaces; i++)
    {
        // pour chaque vertex de la face
        for (int j = 0; j < 3; j++){
            // on calcule la lumiere
            Vecteur lightTexCoord = light.computeLighting
                            (vertex[faces[i].vertexIndex[j]]);
            glMultiTexCoord2fARB(GL_TEXTURE0_ARB,
                            lightTexCoord.x,lightTexCoord.y);
            glMultiTexCoord2fARB(GL_TEXTURE1_ARB,lightTexCoord.z,0.5f);
            Vecteur &v = vertex[faces[i].vertexIndex[j]];
            glVertex3f(v.x,v.y,v.z);
        }
    }
    glEnd();
}

La méthode render sert à effectuer la dernière passe de l'algorithme qui consiste à multiplier le résultat de l'éclairage par la texture de la scène. Ici aussi, nous supposons qu'OpenGL est bien paramétré pour que le rendu fonctionne, c'est-à-dire que l'équation de blending est placée en mode multiplicatif. Le code de la méthode donne donc :

La méthode permettant de rendre la dernière passe de l'algorithme
Sélectionnez

void Model::render()
{
    glColor3f(1,1,1);
    glBegin(GL_TRIANGLES);
    for(int i = 0;i < nbFaces; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            TexCoord &tc = texCoords[faces[i].texCoordIndex[j]];
            Vecteur &v = vertex[faces[i].vertexIndex[j]];
            glTexCoord2f(tc.u,tc.v);
            glVertex3f(v.x,v.y,v.z);
        }
    }
    glEnd();
}

Ne pas oublier de positionner la couleur courante sur du blanc, sinon le résultat final sera déformé par la couleur.

III-3. Modification du calcul de la luminosité.

Comme nous l'avons vu dans la méthode addLight, la méthode computeLighting de la classe Light est changée. Elle doit calculer les coordonnées de textures à appliquer à nos deux unités de textures. Le calcul correspond à celui décrit dans la partie d'explication du light mapping. Il est donc très simple et se résume à :

La méthode de calcul de la lumière
Sélectionnez

Vecteur Light::computeLighting(const Vecteur & position)
{
    Vecteur ret;
    ret.x = (position.x - this->position.x) / (radius) + 0.5f;
    ret.y = (position.y - this->position.y) / (radius) + 0.5f;
    ret.z = (position.z - this->position.z) / (radius) + 0.5f;
    return ret;
}

Ne pas oublier que la méthode a changé de type de retour depuis le tutoriel précédent, elle ne retourne plus une couleur mais des coordonnées de textures 3D dans un vecteur.

III-4. Le rendu final

Contrairement au tutoriel précédent, la mise en place du rendu est plus compliquée. Le rendu s'effectue en trois étapes :

  • rendre la scène avec les light maps statiques (ici, la couleur ambiante).
  • ajouter toutes les lumières à la scène.
  • multiplier le résultat par la texture de la scène.

Ce qui nous donne donc :

Rendu de la couleur ambiante
Sélectionnez

model->initLighting(ambiant);
Ajout des lumières à la scène
Sélectionnez

// On configure les unites de texture pour pouvoir effectuer le multitexturing.
// En fait les deux unites de texture utilisent la meme texture.
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, lightmap);

glActiveTextureARB(GL_TEXTURE1_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, lightmap);

// On doit activer le blending additif pour pouvoir utiliser ce mode d'eclairage
glEnable(GL_BLEND);
glBlendFunc(GL_ONE,GL_ONE);
// Inutile d'ecrire dans le depth buffer, la passe d'ambiante l'a deja fait
glDepthMask(GL_FALSE);
// On ajoute nos lumieres
model->addLight(light1);
model->addLight(light2);
// On desactive la seconde unite de texture qui ne nous sert plus par la suite
glActiveTextureARB(GL_TEXTURE1_ARB);
glDisable(GL_TEXTURE_2D);
Multiplier le résultat
Sélectionnez

// Et on bind la premiere sur la texture du model
glActiveTextureARB(GL_TEXTURE0_ARB);
glBindTexture(GL_TEXTURE_2D,textureId);

// On doit changer le mode de blending : on passe en mode multiplicatif
glBlendFunc(GL_DST_COLOR,GL_ZERO);

model->render();

glDisable(GL_TEXTURE_2D);

glDepthMask(GL_TRUE);
glDisable(GL_BLEND);

Ici, au lieu d'utiliser une fonction de blending multiplicatif simple, on peut utiliser du blending multiplicatif + additif (GL_DST_COLOR,GL_SRC_COLOR) qui va donner un résultat avec des couleurs nettement plus saturées.

Image non disponible
Le rendu final en mode multiplicatif normal.
Image non disponible
Le rendu final en mode multiplicatif + additif.

IV. Conclusions.

Contrairement à ce que certains pensent, mettre en place un système de light mapping dynamique demande très peu de code pour un résultat de bonne qualité. Par contre, contrairement à la méthode par vertex, le light mapping demande de nombreux rendus de la scène ( un rendu pour les light maps statiques, un par lumière dynamique et un dernier pour multiplier la lumière par la texture de la scène.) mais ceci permet d'avoir un bon rendu de la lumière quelque soit la géométrie contrairement à l'éclairage par vertex.

La solution présentée ici a quand même des désavantages : elle ne prend pas en compte l'orientation des faces, c'est-à-dire qu'une face qui tourne le dos à la lumière sera éclairée de la même façon que si elle lui faisait face. Nous n'avons donc pas de gestion de l'ombrage.

Un autre problème est que cette solution n'est pas facilement extensible pour prendre en compte des lumières de type spot. Pour cela, il faudrait mettre en place un système de projection de textures nettement plus complexe.

Nous verrons dans le prochain tutoriel comment mettre en oeuvre un système de light mapping utilisant une texture 3D comme light map dynamique à la place de deux textures 2D.

VI. Liens

Vous pouvez télécharger les sources de ce tutoriel ici

Image non disponible Tutoriel 1 : éclairage par vertex. Tutoriel 3 : light mapping avec texture 3D Image non disponible

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2006 Michel de VERDELHAN. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -

Developpez.com


Gestion dynamique de la lumière avec OpenGL - Partie 3 : light mapping dynamique avec texture 3D

Table des matières Plier Déplier

  • I. Pourquoi utiliser une texture 3D ?
  • II. Modifications apportées au programme
    • II-1. Gestion des extensions
    • II-2. Générer une texture 3D
    • II-3. Modification du rendu des models
    • II-4. Le rendu final
  • III. Conclusions

Ce tutoriel fait suite au tutoriel sur la mise en place d'un système de light mapping basique. Dans celui-ci, nous verrons comment mettre en place un système de light mapping se basant sur l'utilisation de textures 3D.
Contrairement aux tutoriaux précédents, ici, nous ne verrons pas de nouvelle méthode de gestion de la lumière, mais nous allons approfondir la technique précédente. Ce tutoriel est relativement court et simple si on maîtrise bien le concept du light mapping, mais c'est une étape importante pour faciliter la compréhension des tutoriaux suivants.

I. Pourquoi utiliser une texture 3D ?

Le light mapping que nous avons vu dans le tutoriel précédent pose quelques problèmes, aussi bien d'un point de vue qualitatif que d'un point de vue de l'optimisation du rendu. En effet, comme nous l'avons vu, le fait d'utiliser deux unités de textures pour simuler notre sphère pose problème du point de vue de la qualité de la sphère. Ce n'est pas une vraie sphère, et, si le rendu pour une petite lumière semble acceptable, il l'est nettement moins pour le rendu des lumières avec un rayon plus important. C'est donc une première raison d'utiliser une texture 3D comme light map : on obtient une sphère nettement plus sphérique, ce qui améliore la qualité d'affichage des grandes lumières.

Le deuxième problème posé est que notre système utilise deux unités de textures, ce qui est important, surtout quand on souhaite utiliser un modèle d'éclairage complexe très demandeur en unités de texture (comme nous le verrons dans le sixième tutoriel.). De plus, cette forte consommation en unités de textures limite l'utilisation de l'éclairage par pixel sur les cartes anciennes (les GeForce 3 et 4 n'ont que 4 unités de textures, les GeForce 2, seulement 2...). Un des avantages de l'utilisation d'une texture 3D est justement de n'utiliser qu'une seule unité de texture pour la light map.

Ceci nous permettra d'utiliser une optimisation du rendu réduisant le nombre de passes nécessaires. Bien que cette optimisation soit utilisable sans texture 3D, le fait de n'avoir qu'une unité de texture pour la light map permet cette optimisation sur les cartes ne possédant que 2 unités de textures, et surtout, nous nous rapprochons plus d'un model d'éclairage réaliste.

II. Modifications apportées au programme

Les modifications apportées au programme dans ce tutoriel sont relativement légères. Dans un premier temps, nous allons voir le chargement des extensions nécessaires, puis nous verrons comment effectuer le calcul de la texture 3D représentant la sphère d'atténuation, puis, pour terminer, nous aborderons le rendu final de la scène.

II-1. Gestion des extensions

Ici, pas de changement majeur. Nous allons juste ajouter plusieurs fonctions nécessaires au fonctionnement de notre programme, mais le principe reste le même que pour le tutoriel précédent.

Etant donné que nous allons utiliser une texture 3D, nous avons besoin de pouvoir lui passer des coordonnées de textures 3D. Pour cela, nous devons donc charger les fonctions qui prennent en charge cela pour le multitexturing. Nos déclarations de fonctions deviennent donc :

Déclaration des fonctions nécessaires pour le multitexturing : main.cpp
Sélectionnez

// les fonctions utilisees pour gerer le multitexturing
PFNGLMULTITEXCOORD2FARBPROC		glMultiTexCoord2fARB = NULL;
PFNGLMULTITEXCOORD3FARBPROC		glMultiTexCoord3fARB = NULL;
PFNGLMULTITEXCOORD4FARBPROC		glMultiTexCoord4fARB = NULL;
PFNGLACTIVETEXTUREARBPROC		glActiveTextureARB = NULL;
PFNGLCLIENTACTIVETEXTUREARBPROC	glClientActiveTextureARB = NULL;
Déclaration des fonctions nécessaire pour le multitexturing : Model.cpp
Sélectionnez

extern PFNGLMULTITEXCOORD2FARBPROC		glMultiTexCoord2fARB;
extern PFNGLMULTITEXCOORD3FARBPROC		glMultiTexCoord3fARB;
extern PFNGLMULTITEXCOORD4FARBPROC		glMultiTexCoord4fARB;

Maintenant que nous pouvons passer nos coordonnées de textures à nos textures 3D, il nous faut gérer ces textures 3D, nous devons donc charger les fonctions suivantes :

Déclaration des fonctions nécessaire pour les textures 3D
Sélectionnez

// les fonctions utilisees pour gerer les textures 3D
PFNGLTEXIMAGE3DEXTPROC			glTexImage3DEXT = NULL;
PFNGLTEXSUBIMAGE3DEXTPROC		glTexSubImage3DEXT = NULL;

Les textures 3D sont une extension EXT et non pas ARB, c'est-à-dire qu'elle n'est pas forcément implémentée sur toutes les cartes 3D.

Maintenant que nous avons déclaré nos fonctions, il ne faut pas oublier de les charger. Notre fonction de chargement des extensions devient donc :

Fonction de chargement des extensions OpenGL
Sélectionnez

void initExtensions()
{
    if (glutExtensionSupported("GL_ARB_multitexture"))
    {
        glMultiTexCoord2fARB =		(PFNGLMULTITEXCOORD2FARBPROC)wglGetProcAddress("glMultiTexCoord2fARB");
        glMultiTexCoord3fARB =		(PFNGLMULTITEXCOORD3FARBPROC)wglGetProcAddress("glMultiTexCoord3fARB");
        glMultiTexCoord4fARB =		(PFNGLMULTITEXCOORD4FARBPROC)wglGetProcAddress("glMultiTexCoord4fARB");
        glActiveTextureARB =		(PFNGLACTIVETEXTUREARBPROC)wglGetProcAddress("glActiveTextureARB");
        glClientActiveTextureARB =	(PFNGLCLIENTACTIVETEXTUREARBPROC)wglGetProcAddress("glClientActiveTextureARB");
        if (!glActiveTextureARB || !glMultiTexCoord2fARB || !glMultiTexCoord3fARB
            || !glMultiTexCoord4fARB || !glClientActiveTextureARB)
        {
            fprintf(stderr,"impossible d'initialiser l'extension GL_ARB_multitexture\n");
            exit(0);
        }
    }
    else
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_ARB_multitexture\n");
        exit(0);
    }
    if (glutExtensionSupported("GL_EXT_texture3D"))
    {
        glTexImage3DEXT = (PFNGLTEXIMAGE3DEXTPROC) wglGetProcAddress("glTexImage3DEXT");
        glTexSubImage3DEXT = (PFNGLTEXSUBIMAGE3DEXTPROC) wglGetProcAddress("glTexSubImage3DEXT");
        if (!glTexImage3DEXT || !glTexSubImage3DEXT)
        {
            fprintf(stderr,"impossible d'initialiser l'extension GL_EXT_texture3D\n");
            exit(0);
        }
    }
    else
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_EXT_texture3D\n");
        exit(0);
    }
}

Une fois que nous avons chargé les extensions, nous pouvons voir comment calculer la texture 3D qui va nous servir de light map.

II-2. Générer une texture 3D

Avant de voir comment calculer notre light map 3D, je vais commencer par faire une rapide explication sur les textures 3D.
Les textures 3D peuvent être vu comme une superposition de plusieurs textures de même taille. Généralement, elles sont générées de manière procédural (comme c'est le cas ici), mais elles peuvent aussi être crées en chargeant plusieurs textures de même taille dans les différentes couches de la texture, pour créer, par exemple, une texture animée simplement (et en profitant de l'interpolation hardware entre les couches de la texture). Il faut aussi bien différencier les textures 3D des cubes maps, qui, bien qu'elles soient elles aussi des représentations 3D d'images, ne représentent pas un volume comme la texture 3D.
Dans notre cas, nous souhaitons crée une texture dont le volume correspond à une sphère de luminosité, c'est-à-dire que plus on s'éloigne du centre de la texture, plus la luminosité diminue. Pour effectuer cette sphère, nous allons utiliser une fonction qui va nous la générer plutôt que de charger plusieurs textures représentant les couches de la texture.

Le concept du calcul de la texture 3D est très simple, il suffit, pour chaque texel de la texture, de calculer sa distance par rapport au "centre" de la texture. Si le texel est proche du centre, il sera blanc, alors que si la distance est supérieure ou égal au rayon de la sphère (la moitié de la taille de la texture), alors le texel sera noir.

Ce calcul s'effectue sur le même principe que le calcule de l'atténuation de l'éclairage par vertex.

La fonction de calcul de la light map est sans doute la partie la plus compliquée de ce tutoriel, mais si vous avez bien compris le principe du calcul de l'atténuation présenté dans le premier tutoriel, celui-ci de va sans doute pas poser de problèmes. Le code de la fonction donne donc :

Fonction de création d'une light map 3D
Sélectionnez

void generateSphereMap(int size)
{
    glEnable(GL_TEXTURE_3D_EXT);
    if(size<1)
    {
        fprintf(stderr,"la taille de la texture 3D doit etre superieur à 1\n");
        return;
    }

    float *datas = new float[size*size*size*3];
    //on remplie avec la couleur noir
    for(int i=0; i<size*size*size*3; i++)
    {
        datas[i] = 0.0f;
    }
    float length;
    //la demi largeur de la texture 3d en texels (represente le rayon de la sphere)
    float size2= (float)size / 2.0f;

    // pour chaque texel de la texture
    for(float i=0; i<size; i++)
    {
        for(float j=0; j<size; j++)
        {
            for(float k=0; k<size; k++)
            {
                //la demi-largeur moins la distance du centre au texel courant de la texture.
                //size2-1 pour eviter l'elairage sur le bord de la texture.
                length = (size2-1) - sqrt( (i-size2)*(i-size2) +
                            (j-size2)*(j-size2) + (k-size2)*(k-size2) );
                //normalisation dans la plage[0,1] (intensite de la lumiere)
                length /= size2;

                //on stocke la couleur avec son intensite dans le texel
                datas[(int)i*size*size*3 + (int)j*size*3 + (int)k*3   ] = length;
                datas[(int)i*size*size*3 + (int)j*size*3 + (int)k*3 +1] = length;
                datas[(int)i*size*size*3 + (int)j*size*3 + (int)k*3 +2] = length;
            }
        }
    }

    // on generer une texture openGL
    glGenTextures(1,&lightmap);
    glBindTexture(GL_TEXTURE_3D_EXT,lightmap);
    // on parametre la texture (ici on utilise que du filtrage linaire, en cas
    // d'utilisation de mipmapping, il faut calculer les different niveaux
    // de mipmap.)
    glTexParameteri(GL_TEXTURE_3D_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_3D_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    // le mode de repetition : CLAMP_TO_EDGE pour ne pas voir la texture se repeter.
    glTexParameteri(GL_TEXTURE_3D_EXT, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_3D_EXT, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_3D_EXT, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
    // la texture doit moduler la couleur precedente.
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

    // on envoi la texture à openGL
    glTexImage3DEXT(GL_TEXTURE_3D_EXT, 0, GL_RGB, size, size, size, 0, GL_RGB, GL_FLOAT, datas);

    glDisable(GL_TEXTURE_3D_EXT);
    delete [] datas;
}

Ici, nous n'utilisons qu'un filtrage linéaire de la texture. Pour utiliser du filtrage bilinéaire ou trilinéaire (GL_LINEAR_MIPMAP_NEAREST et GL_LINEAR_MIPMAP_LINEAR), il faut générer tous les niveaux de mipmap de la texture 3D.

Maintenant que nous avons une light map, nous pouvons voir les modifications à apporter au rendu de modèles pour qu'ils prennent en compte l'utilisation d'une texture 3D.

II-3. Modification du rendu des models

C'est dans cette partie que nous allons voir l'optimisation présentée au début du tutoriel permettant de réduire le nombre de passes pour rendre la scène.

Dans le tutoriel précédent, le rendu s'effectuait en 3 partie : le rendu des light maps statiques, le rendu des light maps dynamiques en niveau de couleur, puis la multiplication du résultat. La scène était rendue dans cet ordre pour que le programme puisse tourner sur toutes les cartes supportant le multitexturing (donc avec deux unités de textures).

Image non disponible Le mode de rendu présenté dans le tutoriel précédent.

L'optimisation présentée ici peut être appliquée au tutoriel précédent si la carte supporte plus de deux unités de textures.
Cette optimisation consiste à supprimer la dernière étape de rendu de la scène. En effet, si la scène est rendu à chaque étape avec à la fois la light map et la texture du modèle, on a donc à chaque étape à la fois la luminosité et la couleur de la texture qui sont multipliées par le multitexturing, puis le résultat est ajouté à la scène.
Le dessin suivant explique mieux de concept.

Image non disponible Rendu de la scène en deux étapes

La première étape de ce dessin représente le résultat de l'éclairage dynamique sur une surface avec multiplication de la texture par la light map dynamique. La deuxième étape représente l'affichage de la light map statique avec la texture de la surface. Le tout est additionné pour donner le résultat final. On a donc ici uniquement deux étapes de rendu : une étape de rendu des light maps, et une étape de rendu des lumières dynamiques. Un autre avantage de cette méthode est qu'elle nous redonne la possibilité d'organiser ses deux étapes dans l'ordre que l'on souhaite.

Etant donné que nous utilisons désormais une seule unité de texture pour notre light map, un tel système est simple à mettre en oeuvre, il nous suffit d'attribuer la light map à une unité de texture, et la texture de la surface à une autre unité de texture.

Nous allons donc modifier le comportement de notre programme en conséquence.
Comme pour le tutoriel précèdent, ici, nous n'avons pas de light maps statiques, nous allons donc juste rendre la scène avec une couleur ambiante pour simuler l'utilisation de light maps. Il faut donc que notre méthode initLighting affiche l'ensemble du niveau avec ses textures multipliées par la couleur ambiante. Le code de la méthode devient donc :

Code de la méthode initLighting
Sélectionnez

void Model::initLighting(const Color& ambiant)
{
    glColor3f(ambiant.r,ambiant.g,ambiant.b);
    glBegin(GL_TRIANGLES);
    for(int i = 0;i < nbFaces; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            TexCoord &tc = texCoords[faces[i].texCoordIndex[j]];
            Vecteur &v = vertex[faces[i].vertexIndex[j]];
            glTexCoord2f(tc.u,tc.v);
            glVertex3f(v.x,v.y,v.z);
        }
    }
    glEnd();
}

De la même façon, le code de la méthode addLight doit simplement afficher le modèle en lui attribuant les coordonnées de texture 3D pour la light map dynamique. Ici, nous supposons qu'OpenGL est dans un état cohérent, c'est-à-dire que l'unité de texture 0 contient bien la texture du modèle, et que l'unité de texture 1 contient la light map dynamique en 3D. Le code de la méthode addLight devient donc :

Code de la méthode addLight
Sélectionnez

void Model::addLight(Light& light)
{
    glColor3f(light.getColor().r,light.getColor().g,light.getColor().b);
    glBegin(GL_TRIANGLES);
    for(int i = 0;i < nbFaces; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            TexCoord &tc = texCoords[faces[i].texCoordIndex[j]];
            Vecteur &v = vertex[faces[i].vertexIndex[j]];
            Vecteur lightTexCoord = light.computeLighting(v);
            glMultiTexCoord2fARB(GL_TEXTURE0_ARB,tc.u,tc.v);
            glMultiTexCoord3fARB(GL_TEXTURE1_ARB,lightTexCoord.x,lightTexCoord.y,lightTexCoord.z);
            glVertex3f(v.x,v.y,v.z);
        }
    }
    glEnd();
}

La méthode computeLighting n'a pas changé depuis le dernier tutoriel.

Maintenant que nous avons tous les outils pour effectuer le rendu de notre scène, nous allons voir comment effectuer ce rendu.

II-4. Le rendu final

Comme nous l'avons vu, le rendu final de la scène ne s'effectue plus en trois étapes mais en deux. La première consiste à rendre les light maps statiques (simplement la lumière ambiante dans notre cas), et s'effectue tout simplement comme ça :

Rendu de la lumière ambiante
Sélectionnez

// on configure la premiere unite de texture : elle contient la texture de la scene
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, textureId);

// on effectue le rendu de la lumiere ambiante
model->initLighting(ambiant);

La deuxième étape consiste à ajouter chaque lumière à la scène. Le code donne donc :

Rendu des lumières dynamiques
Sélectionnez

// on configure la deuxieme unite de texture : elle contient la lightmap
glActiveTextureARB(GL_TEXTURE1_ARB);
glEnable(GL_TEXTURE_3D_EXT);
glBindTexture(GL_TEXTURE_3D_EXT,lightmap );

// on doit activer le blending additif pour pouvoir utiliser ce mode d'eclairage
glEnable(GL_BLEND);
glBlendFunc(GL_ONE,GL_ONE);
glDepthMask(GL_FALSE);

// on ajoute nos lumieres
model->addLight(light1);
model->addLight(light2);

// on desactive la seconde unite de texture
glActiveTextureARB(GL_TEXTURE1_ARB);
glDisable(GL_TEXTURE_3D_EXT);
// on desactive la premiere unite de texture
glActiveTextureARB(GL_TEXTURE0_ARB);
glDisable(GL_TEXTURE_2D);

glDepthMask(GL_TRUE);
glDisable(GL_BLEND);

Voila c'est tout. Nous avons maintenant une gestion dynamique des lumières par light mapping avec texture 3D.
Le rendu final de la scène donne ça :

Image non disponible
Le rendu final de la scène.

III. Conclusions

Ca y est, nous en avons fini avec le light mapping. Nous avons maintenant un système fonctionnel, mais il faut néanmoins faire attention à bien l'utiliser. En effet, dans notre cas, la scène affichée est simple et, surtout, elle n'est composée que d'une seule texture. Dans une vraie scène, on utilise généralement de nombreuses textures, ce qui peut poser problème avec notre réduction du nombre de passes. En effet, si la gestion des textures est faite de façon anarchique, il risque d'y avoir multiplication des bind de textures (étant donné qu'on a toujours besoin d'au moins deux unités de textures par rendu), ce qui risque de faire perdre le bénéfice de l'optimisation proposée. Donc si vous souhaitez implémenter un tel système, prenez bien garde à limiter autant que possible les changements de textures.

Contrairement aux tutoriaux précédents et à ceux qui vont suivre, celui-ci est plutôt simple et ne présente pas de nouveau concepts de gestion de la lumière, mais il est néanmoins indispensable de bien l'appréhender pour pouvoir comprendre la suite.

Autant les tutoriaux entre l'éclairage par vertex et l'éclairage par light mapping sont relativement déconnectés, autant le lien entre ce tutoriel et les suivants sur l'éclairage par pixel est important. En effet, l'éclairage par pixel utilise les principes du light mapping pour gérer l'atténuation de la lumière.

Pour récapituler, nous avons vu à l'heure actuelle comment mettre en oeuvre un système de gestion de l'éclairage dynamique par vertex et par light mapping, et il nous reste à voir comment mettre en oeuvre l'éclairage par pixel. Ceci fera l'objet des trois prochains tutoriaux.

Vous pouvez télécharger les sources de ce tutoriel ici ou ici [http]
Une version PDF de ce tutoriel est disponible ici ou ici [http]

Image non disponible Tutoriel 2 : light mapping simple. Tutoriel 4 : éclairage par pixel simple. Image non disponible

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2006 Michel de VERDELHAN. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -

Developpez.com


Gestion dynamique de la lumière avec OpenGL - Partie 4 : éclairage par pixel simple

Table des matières Plier Déplier

  • I. Pourquoi utiliser de l'éclairage par pixel ?
  • II. Explications sur le bump mapping
    • II-1. Rappel sur les calculs d'éclairage
    • II-2. La normal map
    • II-3. L'extension env_dot3
  • III. Gestion de l'espace local
    • III-1. Pourquoi utiliser un espace local ?
    • III-2. Modification du chargement des modèles
    • III-3. Calcul de l'espace local
  • IV. Modifications apportées au rendu des modèles
  • V. Rendu final
  • VI. Conclusions
  • Liens

Ce tutoriel présente une version simplifiée de l'éclairage par pixel. Son but est avant tout de présenter les concepts liés à la gestion de l'éclairage par pixel, c'est-à-dire, le calcul et la gestion de l'espace local des vertex. Nous allons aussi modifier notre façon d'utiliser les modèles pour les adapter aux problèmes liés au format de fichiers utilisé.
Dans ce tutoriel et dans celui qui suit, nous verrons comment utiliser l'extension env_dot3 d'OpenGL qui est l'extension à utiliser pour pouvoir effectuer l'éclairage par pixel sans utiliser de fragment program/shaders.

I. Pourquoi utiliser de l'éclairage par pixel ?

Nous avons vu dans les tutoriaux précédents comment calculer l'éclairage, d'abord par vertex, puis en utilisant une light map dynamique, mais la vie est ainsi faite qu'on en souhaite toujours plus... Vous aurez peut être remarqué que si l'éclairage par vertex présente certains problèmes, il a quand même l'avantage de prendre en compte l'orientation de la face pour calculer sa luminosité (ou plutôt, il prend en compte l'orientation des vertex). Ceci n'est pas pris en compte par le light mapping dynamique qui ne calcule que l'atténuation de la lumière, ce qui pose problème lorsqu'on souhaite avoir un éclairage réaliste. Par contre, l'éclairage par light mapping dynamique ne pose pas de problèmes liés à la tesselation (1) des modèles. L'idéal serai donc de réussir à lier les avantages des deux techniques. C'est ce que nous allons faire avec l'éclairage par pixel.

Bien que le fait de pouvoir prendre en compte l'orientation des faces sans avoir les problèmes liés à la tesselation soit intéressant, ce n'est pas non plus le principal avantage de l'éclairage par pixel. Le gros avantage de cette technique est de pouvoir, comme son nom l'indique en fait, calculer l'éclairage au niveau du pixel et non plus du vertex. On obtient ainsi une meilleure finesse dans l'éclairage. Bien évidemment, cette méthode a un coût en terme de temps de calculs.

Dans ce tutoriel, nous allons d'abord voir les principes de l'éclairage par pixel, ensuite nous étudierons plus en détail un des éléments qui est le plus important de l'éclairage par pixel : l'espace local des vertex, et finalement, nous verrons les modifications apportées au programme pour prendre en compte l'éclairage par pixel.

II. Explications sur le bump mapping

Dans cette partie, nous allons voir les principes de fonctionnement de l'éclairage par pixel. Dans un premier temps, je vais revenir sur les calculs d'éclairage, puis j'expliquerai le fonctionnement des normal maps, et pour finir, je présenterai rapidement l'extension env_dot3 qui va nous permettre d'effectuer le calcul de l'éclairage par pixel.

II-1. Rappel sur les calculs d'éclairage

Pour bien comprendre le fonctionnement de l'éclairage par pixel, je reviens rapidement sur le calcul de l'éclairage déjà vu dans le premier tutoriel sur l'éclairage par vertex.
Comme nous l'avons vu, l'éclairage d'une surface se calcul en fonction de :

  • Son éloignement par rapport à la lumière, c'est ce qu'on appel l'atténuation. Nous avons vu comment calculer l'atténuation grâce au light mapping dynamique, je ne reviendrai donc pas dessus dans ce tutoriel.
  • Son orientation par rapport à la lumière. Ainsi, si la surface tourne le dos à la lumière, elle n'est pas éclairée, alors que si elle est orientée en face de la lumière, elle reçoit l'ensemble de la lumière. C'est cette gestion de l'orientation de la surface qui nous intéresse plus particulièrement dans ce tutoriel.

Pour gérer l'orientation de la face par rapport à la lumière, nous avons vu dans le premier tutoriel que nous avons besoin de deux informations : le vecteur perpendiculaire à la surface aussi appelé la normale à la surface et le vecteur dirigé vers la lumière. Une fois ces deux vecteurs récupérés, il nous suffit d'effectuer le produit scalaire entre les deux vecteurs pour avoir le coefficient de luminosité perçu par la surface. C'est le fameux NdotL, avec N représentant la normale à la surface, L le vecteur vers la lumière, et dot le produit scalaire.

Image non disponible
Les vecteurs utilisés pour calculer l'éclairage

Jusqu'à présent, nous avions utilisé la normale des vertex pour effectuer ce calcul, mais à partir de maintenant, nous allons utiliser une normal map dont je vais présenter le principe.

II-2. La normal map

Pour notre calcul, il nous faut récupérer la normale à la surface. Lorsqu'on utilise une normale par vertex, le résultat des calculs est interpolé entre les sommets du polygone, ce qui donne de mauvais résultats lorsque le polygone est trop étendu. En fait, on se retrouve dans des situations où le résultat correspond à un cas où la normale est interpolée comme sur le schéma suivant.

Image non disponible
Résultat de l'interpolation linéaire de deux normales : la normale interpolée n'est plus normalisée.

On voit clairement que le résultat de l'interpolation n'est pas valide pour le calcul de la luminosité : la normale interpolée n'est plus normalisée, on va donc avoir des problèmes d'échelle dans le résultat final (on parle de scaling problems). En effet, si la normale est de longueur 0.8, la luminosité finale sera réduite de 20 % par rapport à ce qu'elle aurait dû être.

C'est pour résoudre ce problème qu'on utilise une texture qui va stocker les normales de la surface, ainsi, on réduit les problèmes liés à l'interpolation des normales (ils ne sont pas complètement supprimés étant donné qu'une texture de trop faible résolution sera elle-même interpolée quand on s'en approche ).

Une normal map est en faite une texture standard dont les composantes RBG ne sont plus interprétées comme des couleurs, mais comme des vecteurs 3D représentant la normale d'une surface. On peut donc facilement donner l'impression qu'un polygone n'est plus plat en modifiant l'orientation des normales dans la normal map. Les calculs d'éclairage vont s'effectuer sur ces normales perturbées, donnant ainsi une impression de volume virtuel.

Image non disponible
L'utilisation d'une normal map perturbée permet de créer un volume virtuel utilisé pour créer une impression de volume sur la surface.

Contrairement à ce qu'on pourrait croire, une normal map peut parfaitement se regarder à l'oeil et est parfaitement compréhensible une fois qu'on sait ce quelle représente. Voici par exemple l'exemple de la local map utilisé dans ce tutoriel :

Image non disponible La local map utilisée dans le tutoriel : on voit bien les limites des volumes générés par la perturbation des normales.

J'en vois dans le fond qui se demandent bien pourquoi j'utilise le terme de local map au lieu de parler de normal map comme je l'ai fait jusqu'à présent. Tout cela s'explique par le fait qu'il existe deux catégories de normal maps : les global maps et les local maps.

Les global maps sont des normal maps qui stockent des vecteurs orientés dans toutes les directions. Le gros avantage des global maps est qu'elles peuvent être utilisées directement sans avoir à passer par le calcul de l'espace local des vertex. Ceci permet donc de calculer l'éclairage plus vite étant donné que nous n'avons pas tous les calculs de projections, que nous verrons par la suite, à effectuer. Par contre, le gros problème des global maps est qu'elles ne sont pas réutilisables. C'est-à-dire qu'une global map ne peut être utilisée que pour un objet donné. Imaginez une scène ou les graphistes sont obligés de faire une global map propre à chaque polygone... Ce serait totalement impossible à gérer. En fait les global maps sont très peu utilisées, on en trouve parfois lorsqu'il faut afficher des terrains. Etant donné que le terrain a peu de chance d'être modifié, on calcule une global map au lancement, et ainsi, on gagne en temps d'exécution lors de l'affichage.

Les local maps, au contraire des global maps, ne contiennent que des normales orientées sur l'axe des Z positifs. Ainsi, une local map ne représente pas la surface d'un objet, mais une surface générique pouvant être utilisée sur plusieurs objets différents. Le fait qu'elles ne soit orientées que dans une seule direction oblige à des calculs relativement complexes à comprendre au départ, mais, en contrepartie, on peut réutiliser les local maps à volonté. Le nom de local map vient du fait que pour pouvoir utiliser une local map, il faut effectuer les calculs d'espaces locaux des vertex, que nous verrons par la suite. On peut facilement différencier une local map d'une global map et regardant la couleur générale de la texture. Une global map sera potentiellement de toutes les couleurs alors qu'une local map est généralement bleue étant donné que les vecteurs pointent vers les Z positifs (donc la composante bleu du vecteur est souvent proche de 1).

Etant donné que nous ne nous préoccupons que des local maps, j'utiliserai indifféremment le terme normal map et local map pour designer la local map par la suite.

Maintenant que nous avons vu le principe de la normal map, il nous reste une étape à voir : comment faire pour pouvoir calculer le produit scalaire pour chaque pixel ? C'est ce que nous allons voir dans la partie qui vient.

II-3. L'extension env_dot3

OpenGL ne propose pas de fonctions standard pour modifier le calcul au niveau des pixels : pour ça, il faut encore une fois passer par des extensions. Ici, nous allons utiliser l'extension env_dot3 qui va nous permettre de calculer le produit scalaire entre la normale de la surface et le vecteur vers la lumière.

Contrairement aux extensions utilisées dans les tutoriaux précédents, l'extension env_dot3 n'utilise pas de fonctions particulières à charger, elle apporte juste des paramètres en plus à passer aux fonctions standard OpenGL. Comme ce n'est pas une utilisation commune d'une extension, je vais présenter rapidement son fonctionnement afin d'avoir une meilleure compréhension des calculs effectués par la carte graphique.

Voici donc comment faire pour configurer une unité de texture pour qu'elle réalise le calcul de DOT3 bump mapping :

Code pour configurer une unité de texture pour qu'elle effectue le produit scalaire
Sélectionnez

glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, normalMapId);
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_COMBINE_ARB);
glTexEnvf(GL_TEXTURE_ENV,GL_RGB_SCALE_ARB,1);
glTexEnvf(GL_TEXTURE_ENV,GL_COMBINE_RGB_ARB,GL_DOT3_RGB_ARB);
glTexEnvf(GL_TEXTURE_ENV,GL_SOURCE0_RGB_ARB,GL_PREVIOUS_ARB);
glTexEnvf(GL_TEXTURE_ENV,GL_SOURCE1_RGB_ARB,GL_TEXTURE);
glTexEnvf(GL_TEXTURE_ENV,GL_OPERAND0_RGB_ARB,GL_SRC_COLOR);
glTexEnvf(GL_TEXTURE_ENV,GL_OPERAND1_RGB_ARB,GL_SRC_COLOR);

Voila, c'est tout ce qu'il y a à faire. En fait, on signale à OpenGL qu'on souhaite que la carte graphique effectue le produit scalaire entre l'unité de texture courante et la couleur précédente dans le pipeline. Il nous suffit donc de passer le vecteur vers la lumière dans la couleur précédente (par exemple via un appel à glColor comme nous allons le faire dans ce tutoriel ou via une cube map comme nous le ferons dans le prochain tutoriel), et la carte graphique va se charger pour nous du calcul.

III. Gestion de l'espace local

Nous avons vu plus tôt que nous utilisons une local map, il nous faut donc prendre en charge la gestion de l'espace local des vertex. Dans cette partie, je vais d'abord présenter en quoi consiste l'espace local des vertex, puis j'expliquerai les problèmes que peut engendrer cet espace local au niveau du chargement des modèles, et finalement, je donnerai le code utilisé pour calculer l'espace local pour chaque vertex.

III-1. Pourquoi utiliser un espace local ?

La gestion de l'espace local est très certainement la partie la plus complexe à comprendre (et à expliquer ;-) ) du bump mapping. En effet, elle fait appel à des notions d'algèbre linéaire qui ne sont pas triviales à comprendre dans un premier temps. Je vais essayer de l'expliquer, mais si jamais vous ne comprenez pas, n'hésitez pas à allez chercher d'autres sources de documentation sur internet. En effet, il faut parfois plusieurs explications différentes pour bien appréhender ce concept.

Le problème avec les normal maps, c'est que si on tourne l'objet sur lequel la texture est plaquée, les normales, elles, ne sont pas tournées, on se retrouve donc avec un objet qui n'est plus sur le plan XY (plan sur lequel sont générées les normal maps qui pointent donc vers les Z positifs), mais avec des normales qui sont restées sur le plan XY. Pour mieux montrer les problèmes, quelques schémas sont sûrement plus efficaces que des lignes de textes :

Image non disponible Cas idéal : la surface est sur le bon plan pour les calculs.

Ici, on a le cas idéal : la surface est bien orientée sur le plan XY, les normales (en rouge) de la normal map pointent donc dans la bonne direction, le calcul va bien s'effectuer.

Image non disponible Cas souhaité : la normale est orientée en même temps que la surface.

Ici, le schéma montre ce que l'on souhaiterais avoir : la surface n'est pas alignée sur le plan XY, mais les normales de la normal map ont bien été tournées, le calcul va donc bien s'effectuer. Mais malheureusement, comme je l'expliquais au dessus, ce n'est pas ce qu'il se passe réellement. On se trouve en fait avec le cas suivant :

Image non disponible Mauvais cas : les calculs ne prennent pas en compte le changement d'orientation de la surface.

Ici, la surface a bien été tournée, mais les normales de la normal map ne subissent pas de transformations (ce qui se passe effectivement au niveau de la carte graphique qui n'a pas de notions d'orientation des surfaces), on se retrouve donc avec un calcul faux (ici, le calcul va donner un produit scalaire supérieur à 0, donc éclairé, alors que la surface ne devrais pas l'être car elle tourne le dos à la lumière. Etant donné qu'on ne peut pas modifier les normales de la normal map, le seul moyen d'obtenir un calcul juste est de modifier le seul vecteur modifiable : le vecteur vers la lumière, ce qui nous donne le résultat suivant :

Image non disponible Cas effectivement pratiqué pour le bump mapping dot3 : on ne peut pas modifier la normale, alors on transforme le vecteur vers la lumière.

Ici, on a modifié le vecteur vers la lumière pour obtenir un calcul valide. En fait, sur ce schéma, l'angle entre le vecteur vers la lumière et la normale de la surface est le même que sur le deuxième schéma (aux erreurs de dessin près ;-) ). On a donc un calcul valide qui donne le même résultat que si nous avions tournée la normale de la surface comme dans le cas du deuxième schéma.

Il nous faut donc calculer ce vecteur vers la lumière (qui n'est du coup plus vraiment orienté vers la lumière ;-) ) pour prendre en compte les surface transformées. Si toutes nos surfaces étaient générées sur le plan XY puis tournées pour atteindre leur places finales, le calcul ne poserait pas de problème, il suffirait de faire subir la rotation inverse à notre vecteur vers la lumière pour obtenir ce que l'on souhaite. Mais malheureusement, ce n'est pas le cas. Nous n'avons généralement que trois vertex pour définir un triangle et pas de rotation ou autre à notre disposition. Il nous faut donc calculer quelque chose qui permet d'obtenir le résultat souhaité : l'espace local.

L'espace local (aussi appelé espace tangent) est un espace tridimensionnel formé de trois vecteurs et d'une position dans l'espace. Les trois vecteurs permettent d'orienter l'espace local dans l'espace global, et la position correspond à l'origine de l'espace local dans l'espace global. Oui, dit comme ça, c'est incompréhensible ;-). En fait il faut voir cet espace de la même manière que les axes standards X, Y et Z qui forment eux aussi un espace local positionné en {0,0,0}.

Bon j'ai dis que notre espace local était formé de trois vecteurs, il est temps d'en parler plus en détails. Les trois vecteurs qui forment notre espace local sont appelés Tangente, Binormale et Normale (on voit souvent le diminutif TBN utilisé pour parler de l'espace local). On connaît déjà la normale qui n'est autre que la normale du vertex. Les deux autres vecteurs sont perpendiculaires à la normale, mais surtout, ils sont proportionnels aux coordonnées de texture du polygone. En effet, il faut que notre espace prenne en compte le fait que les coordonnées de textures puissent être tournées/étirées/inversées.

Encore une fois, un petit schéma permet d'avoir les idées plus claires.

Image non disponible Un polygone avec l'espace local de chacun de ses vertex : quand le polygone change d'orientation, les espaces locaux suivent.

Nous avons vu ce qu'est un espace local, mais il faut encore savoir à quoi il peut bien nous servir. En fait c'est lui qui va nous permettre de calculer le vecteur vers la lumière modifiée pour qu'il soit valide. Pour cela, il suffit de projeter le vecteur vers la lumière dans l'espace local. C'est ici qu'intervient l'algèbre linéaire : pour projeter un vecteur dans un espace 3D formé de trois vecteurs, il suffit, pour chaque composante du vecteur cible, de calculer le produit scalaire entre le vecteur à transformer et le bon vecteur de l'espace local (en fait il faut les prendre dans l'ordre Tangente pour la composante X, Binormale pour la composante Y et Normale pour la composante Z). Le calcul de la projection d'un vecteur correspond donc à ça :
VecteurDestination.x = vecteurSource . Tangente
VecteurDestination.y = vecteurSource . Binormale
VecteurDestination.z = vecteurSource . Normale

Maintenant que nous savons ce qu'est un espace local et à quoi il sert, nous allons pouvoir voir comment le calculer. Mais avant, je vais parler des problèmes que peut entraîner tout ceci au niveau du chargement des modèles.

III-2. Modification du chargement des modèles

Le fait de prendre les tangentes et binormales en compte nous oblige à modifier notre classe de modèle. En effet, il nous faut ajouter un tableau de vecteurs pour les tangentes et un autre tableau pour les binormales. Tout ceci se fait facilement en ajoutant au fichier Model.h ceci :

Ajout des tableaux de binormales et tangentes
Sélectionnez

/**
 * le tableau de binormales (il y a autant de binormales que de normales)
 */
Vecteur * binormals;
/**
 * le tableau de tangentes (il y a autant de tangentes que de normales);
 */
Vecteur * tangents;

Que l'on oubliera pas de supprimer dans le destructeur bien entendu.

Mais le principal problème vient du remplissage de ces tableaux. Idéalement, pour connaître la taille des tableaux, il suffit de prendre la taille du tableau de normales. En effet, si le format de modèles gère bien les normales partagées, nous aurons donc un espace local par normale partagée. Malheureusement, dans notre cas, les normales ne sont pas bien partagées. Lors de l'export en .obj, si deux normales ont les mêmes valeurs, alors l'exporteur n'en conserve qu'une seule et change les indices des faces en conséquences. Ceci pose problème car on peut très bien avoir des normales orientées dans le même sens mais liées à des faces ayant des coordonnées de textures différentes. Ceci entraîne de graves incohérences dans les calcules par la suite.

Dorénavant, nous n'utiliserons plus les normales contenues dans le fichier, mais nous les calculerons à la main au chargement. Le problème que pose cette méthode est que les normales seront toutes adoucies, y compris celles qui sont sur des faces avec une orientation très différente (comme sur un cube par exemple). Ceci aura des conséquences sur la qualité de l'éclairage, certaines faces pouvant tourner le dos à la lumière tout en étant toujours éclairées. J'ai choisis de ne pas régler ce problème car il entraîne une trop forte complication du code pour un débutant, et surtout, il peut être réglé en choisissant un format de modèle qui nous assure d'une bonne gestion des normales partagées.

Maintenant nous pouvons voir le nouveau code de chargement des modèles :

Méthode de chargement des modèles modifiés
Sélectionnez

bool Model::load(const std::string & filePath)
{
    // on cree un lecteur de .obj
    OBJReader reader(filePath);
    // on charge le model
    if (reader.load())
    {
        // le model est bien charge
        // on alloue le tableau de vertex
        vertex = new Vecteur[reader.getVertex().size()];
        nbVertex = 0;
        // on copie les informations des vertex
        for (std::vector<OBJReader::OBJVector>::iterator it =
        reader.getVertex().begin();	it != reader.getVertex().end(); ++it)
        {
            vertex[nbVertex].x = (*it).x;
            vertex[nbVertex].y = (*it).y;
            vertex[nbVertex].z = (*it).z;
            nbVertex++;
        }
        // on alloue le tableau de normales
        // les tableaux de binormales et tangentes font la
        // meme taille que le tableau de normales
        normals = new Vecteur[nbVertex];
        binormals = new Vecteur[nbVertex];
        tangents = new Vecteur[nbVertex];
        // on les initialise au vecteur null
        for (int i = 0; i < nbVertex; i++)
        {
            normals[i].x = normals[i].y = normals[i].z =
            binormals[i].x = binormals[i].y = binormals[i].z =
            tangents[i].x = tangents[i].y = tangents[i].z = 0.0f;
        }
        // on alloue le tableau de coordonnees de textures
        texCoords = new TexCoord[reader.getTexture().size()];
        nbTexCoord = 0;
        // on copie les informations de coordonnees de texture
        for (std::vector<OBJReader::OBJtexCoord>::iterator it =
        reader.getTexture().begin();it != reader.getTexture().end(); ++it)
        {
            texCoords[nbTexCoord].u = (*it).u;
            texCoords[nbTexCoord].v = (*it).v;
            nbTexCoord++;
        }
        // on alloue le tableau de faces
        faces = new Face[reader.getFace().size()];
        nbFaces = 0;
        // on copie les informations des faces
        for (std::vector<OBJReader::OBJface>::iterator it = reader.getFace().begin();
        it != reader.getFace().end(); ++it)
        {
            faces[nbFaces].vertexIndex[0] = (*it).vertex[0];
            faces[nbFaces].vertexIndex[1] = (*it).vertex[1];
            faces[nbFaces].vertexIndex[2] = (*it).vertex[2];
            faces[nbFaces].normalIndex[0] = (*it).normal[0];
            faces[nbFaces].normalIndex[1] = (*it).normal[1];
            faces[nbFaces].normalIndex[2] = (*it).normal[2];
            faces[nbFaces].texCoordIndex[0] = (*it).texture[0];
            faces[nbFaces].texCoordIndex[1] = (*it).texture[1];
            faces[nbFaces].texCoordIndex[2] = (*it).texture[2];
            nbFaces++;
        }

        // maintenant que nous avons toutes les informations sur les faces,
        // on peut calculer l'espace local
        for (int i = 0; i < nbFaces; i++)
        {
            computeLocalSpace(faces[i]);
        }
        // il ne faut pas oublier de renormaliser les tangentes et binormales
        for (int i = 0; i < nbVertex; i++)
        {
            binormals[i].normalise();
            tangents[i].normalise();
            normals[i].normalise();
        }
        return true;
    }
    else
    {
        return false;
    }
}

La méthode computeLocalSpace() correspond à la méthode de calcul de l'espace local que nous allons voir maintenant.

III-3. Calcul de l'espace local

Le calcul de l'espace local pour une face est finalement assez simple une fois qu'on a compris ce qu'est réellement l'espace local. Il suffit de calculer deux vecteurs qui suivent les bords du polygone et sont proportionnels aux coordonnées de textures. Une fois qu'on a effectué ce calcul pour la face, il ne nous reste plus qu'à additionner le résultat aux tangentes, binormales et normales des vertex du polygone. Je tiens à signaler qu'il existe plusieurs algorithmes pour calculer cet espace local, il est donc possible que vous en trouviez des différents sur le net.

Le calcul de l'espace local pour une face donne le code suivant :

Méthode de calcul de l'espace local pour une face
Sélectionnez

void Model::computeLocalSpace(Face &face)
{
    // on calcule 2 vecteurs formant les bords du triangle
    // le vecteur side0 est celui allant du vertex 0 au vertex 1
    Vecteur side0 = vertex[face.vertexIndex[1]] - vertex[face.vertexIndex[0]];
    // le vecteur side1 est celui allant du vertex 0 au vertex 2
    Vecteur side1 = vertex[face.vertexIndex[2]] - vertex[face.vertexIndex[0]];

    // ici on calcule la normale à la face
    Vecteur normal = side0.cross(side1);
    normal.normalise();

    // maintenant on calcule les coefficients des tangentes
    // ces coefficients sont calcules en fonction des
    // coordonnées de textures du polygone
    float deltaT0 = texCoords[face.texCoordIndex[1]].v -
                    texCoords[face.texCoordIndex[0]].v;
    float deltaT1 = texCoords[face.texCoordIndex[2]].v -
                    texCoords[face.texCoordIndex[0]].v;
    // on effectue la meme chose pour la binormale
    float deltaB0 = texCoords[face.texCoordIndex[1]].u -
                    texCoords[face.texCoordIndex[0]].u;
    float deltaB1 = texCoords[face.texCoordIndex[2]].u -
                    texCoords[face.texCoordIndex[0]].u;
    // le facteur permettant de rendre les tangentes et binormales
    // proportionnelles aux coordonnees de textures.
    // nous aurons donc des vecteurs dont la norme depend des coordonnees
    // de textures.
    float scale = 1/ ((deltaB0 * deltaT1) - (deltaB1 * deltaT0));

    // on calcule la tangente temporaire
    Vecteur tmpTangente = ((side0*deltaT1) - (side1*deltaT0))*scale;
    tmpTangente.normalise();

    // on calcule la binormale temporaire
    Vecteur tmpBinormal = ((side0*(-deltaB1)) + (side1*deltaB0))*scale;
    tmpBinormal.normalise();

    // nous avons donc maintenant les tangentes, binormales et normales de la face.
    // on les additionne a celles deja presentes dans les tableaux
    tangents[face.vertexIndex[0]] += tmpTangente;
    tangents[face.vertexIndex[1]] += tmpTangente;
    tangents[face.vertexIndex[2]] += tmpTangente;

    binormals[face.vertexIndex[0]] += tmpBinormal;
    binormals[face.vertexIndex[1]] += tmpBinormal;
    binormals[face.vertexIndex[2]] += tmpBinormal;

    normals[face.vertexIndex[0]] += normal;
    normals[face.vertexIndex[1]] += normal;
    normals[face.vertexIndex[2]] += normal;

    // il ne faudra pas oublier de renormaliser les vecteurs par la suite.
}

Attention à bien renormaliser les vecteurs après avoir calculé l'espace local de toutes les faces.

IV. Modifications apportées au rendu des modèles

Maintenant que nous avons toutes les informations nécessaires pour pouvoir effectuer notre éclairage par pixels, nous allons voir comment le faire en pratique. Pour cela, nous allons voir les modifications apportées au programme pour rendre les modèles. Les modifications apportées ici ne sont pas importantes au niveau taille du code, mais elles sont néanmoins primordiales pour le bon fonctionnement de l'éclairage.

Je vais commencer par une petite méthode utilitaire qui va nous servir à debugger nos calculs d'espaces locaux : la méthode drawLocalSpace qui affiche les tangentes, binormales et normales de chaque vertex. Cette méthode est très simple à comprendre, je ne rentrerais donc pas dans les détails. Tout ce qu'elle fait est d'afficher trois lignes de couleurs différentes qui suivent les trois vecteurs de notre espace local.
Le code de la méthode donne donc :

Méthode servant à dessiner l'espace local de chaque vertex
Sélectionnez

void Model::drawLocalSpace()
{
    glBegin(GL_LINES);
    for(int i = 0;i < nbFaces; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            int vertexIndex = faces[i].vertexIndex[j];
            Vecteur &v = vertex[vertexIndex];
            // tangente en rouge
            Vecteur tangent = tangents[vertexIndex] + v;
            glColor3f(1,0,0);
            glVertex3f(v.x,v.y,v.z);
            glVertex3f(tangent.x,tangent.y,tangent.z);
            // binormale en vert
            Vecteur binormal = binormals[vertexIndex] + v;
            glColor3f(0,1,0);
            glVertex3f(v.x,v.y,v.z);
            glVertex3f(binormal.x,binormal.y,binormal.z);
            // normale en bleu
            Vecteur normal = normals[vertexIndex] + v;
            glColor3f(0,0,1);
            glVertex3f(v.x,v.y,v.z);
            glVertex3f(normal.x,normal.y,normal.z);
        }
    }
    glEnd();
}

Maintenant, nous allons passer à la modification la plus importante de l'affichage : la méthode addLight, que j'ai renommé en addLightPerVertex pour pouvoir la différencier de la méthode que nous verrons dans le prochain tutoriel.

Cette méthode doit effectuer deux choses :

  1. Calculer le vecteur transformé vers la lumière.
  2. Afficher le modèle en prenant en compte le vecteur vers la lumière.

Pour calculer notre vecteur transformé vers la lumière, nous allons utiliser le calcul de la projection d'un vecteur dans un espace 3D vu plus haut. Pour cela, nous calculons d'abord le vecteur vers la lumière standard comme ceci :

Calcul du vecteur du vertex vers la lumière
Sélectionnez

Vecteur tempVect = lightPos - vertex[vertexIndex];

Puis nous calculons sa projection dans l'espace local du vertex comme cela :

Projection du vecteur vers la lumière dans l'espace local du vertex
Sélectionnez

Vecteur vertexToLight;
vertexToLight.x = tangents[vertexIndex]*tempVect;
vertexToLight.y = binormals[vertexIndex]*tempVect;
vertexToLight.z = normals[vertexIndex]*tempVect;

Comme nous l'avons vu dans la partie sur l'extension env_dot3, il nous faut envoyer le vecteur vers la lumière dans la couleur précédant l'unité de texture configuré pour calculer le bump mapping. Pour cela, nous nous contenterons d'utiliser un simple glColor ici. Ensuite, il ne nous reste plus qu'à afficher le modèle. Le code complet de la méthode donne ça :

Méthode d'ajout d'une lumière à la scène
Sélectionnez

void Model::addLightPerVertex(Light& light)
{
    Vecteur lightPos = light.getPosition();
    glBegin(GL_TRIANGLES);
    for(int i = 0;i < nbFaces; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            int vertexIndex = faces[i].vertexIndex[j];
            // on calcule le vecteur vertex->lumiere normalisee
            Vecteur tempVect = lightPos - vertex[vertexIndex];
            Vecteur vertexToLight;
            vertexToLight.x = tangents[vertexIndex]*tempVect;
            vertexToLight.y = binormals[vertexIndex]*tempVect;
            vertexToLight.z = normals[vertexIndex]*tempVect;
            vertexToLight.normalise();
            // nous avons donc maintenant le vecteur du vertex vers la lumiere
            // transforme dans l'espace local du vertex.
            // on l'envoie sous forme de couleur a openGL
            glColor3f(vertexToLight.x,vertexToLight.y,vertexToLight.z);
            // maintenant, on envoie les coordonnees de textures
            // la premiere unite de texture contient la normal map, ce sont donc
            // les meme coordonnees de textures que la scene
            TexCoord &tc = texCoords[faces[i].texCoordIndex[j]];
            glMultiTexCoord2fARB(GL_TEXTURE0_ARB,tc.u,tc.v);
            // la deuxieme unite de texture contient la texture 3D d'attenuation,
            // on calcule donc l'eclairage
            Vecteur &v = vertex[vertexIndex];
            Vecteur lightTexCoord = light.computeLighting(v);
            glMultiTexCoord3fARB(GL_TEXTURE1_ARB,lightTexCoord.x,
                                lightTexCoord.y,lightTexCoord.z);
            // la troisieme unite de texture contient la texture diffuse,
            // donc les coordonnees de texture de la scene
            glMultiTexCoord2fARB(GL_TEXTURE2_ARB,tc.u,tc.v);
            glVertex3f(v.x,v.y,v.z);
        }
    }
    glEnd();
}

Comme d'habitude, ici, nous supposons qu'OpenGL est configuré de la manière suivante : l'unité de texture 0 contient la normal map configurée pour effectuer le calcul de DOT3 bump mapping, l'unité de texture 1 contient la light map 3D d'atténuation et l'unité de texture 2 contient la texture standard de la scène (aussi appelée texture diffuse).

V. Rendu final

Maintenant que nous pouvons afficher un modèle en prenant en compte l'éclairage par pixel, nous allons voir la séquence d'appels à effectuer pour rendre la scène complète. Encore une fois, le code est relativement simple et ne devrait pas poser de problèmes. On doit, dans un premier temps, rendre la composante ambiante de la scène comme ceci :

Rendu de la composante ambiante de la scène
Sélectionnez

// on configure la premiere unité de texture : elle contient la texture de la scene
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, textureId);
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);

// on effectue le rendu de la lumiere ambiante
model->initLighting(ambiant);

Ensuite, il faut configurer les unités de textures pour qu'elles correspondent à ce dont on a besoin dans notre méthode addLight. Ça se fait comme ceci :

Code de configuration de unités de textures
Sélectionnez

// on doit reconfigurer les unites de textures
// la premiere unite contient la normal map
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, normalMapId);
// on configure la normal map pour qu'elle effectue la passe de DOT3
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_COMBINE_ARB);
glTexEnvf(GL_TEXTURE_ENV,GL_RGB_SCALE_ARB,1);
glTexEnvf(GL_TEXTURE_ENV,GL_COMBINE_RGB_ARB,GL_DOT3_RGB_ARB);
glTexEnvf(GL_TEXTURE_ENV,GL_SOURCE0_RGB_ARB,GL_PREVIOUS_ARB);
glTexEnvf(GL_TEXTURE_ENV,GL_SOURCE1_RGB_ARB,GL_TEXTURE);
glTexEnvf(GL_TEXTURE_ENV,GL_OPERAND0_RGB_ARB,GL_SRC_COLOR);
glTexEnvf(GL_TEXTURE_ENV,GL_OPERAND1_RGB_ARB,GL_SRC_COLOR);

// la deuxieme contient la texture d'attenuation
glActiveTextureARB(GL_TEXTURE1_ARB);
glEnable(GL_TEXTURE_3D_EXT);
glBindTexture(GL_TEXTURE_3D_EXT,lightmap );
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
// la troisieme contient la texture de diffuse
glActiveTextureARB(GL_TEXTURE2_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,textureId );
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);

Et maintenant, il ne nous reste plus qu'à ajouter les lumières en activant le blending additif, puis à désactiver les unités de textures.

Sélectionnez

// on doit activer le blending additif pour pouvoir utiliser ce mode d'éclairage
glEnable(GL_BLEND);
glBlendFunc(GL_ONE,GL_ONE);
glDepthMask(GL_FALSE);

// on ajoute nos lumieres
model->addLightPerVertex(light1);
model->addLightPerVertex(light2);

// on desactive la troisieme unite de texture
glActiveTextureARB(GL_TEXTURE2_ARB);
glDisable(GL_TEXTURE_2D);
// on desactive la seconde unite de texture
glActiveTextureARB(GL_TEXTURE1_ARB);
glDisable(GL_TEXTURE_3D_EXT);
// on desactive la premiere unite de texture
glActiveTextureARB(GL_TEXTURE0_ARB);
glDisable(GL_TEXTURE_2D);

glDepthMask(GL_TRUE);
glDisable(GL_BLEND);

Ce qui nous permet d'avoir le rendu final suivant :

Image non disponible
Le rendu final de la scène.

VI. Conclusions

Voila, nous avons un système de gestion de l'éclairage par pixel simplifié qui fonctionne. Pourquoi simplifié ? Tout simplement car il manque quelques éléments pour pouvoir effectivement prétendre gérer correctement les lumières.

Premièrement, nous calculons le vecteur vers la lumière par vertex. Nous retombons donc sur les mêmes problèmes qu'avec l'éclairage par vertex, à savoir : l'interpolation des vecteurs va nous donner des vecteurs non normalisés aux centres des polygones, et si les polygones sont trop grands, on peut tomber sur des cas ou l'éclairage semble venir d'une direction alors que la lumière n'est pas située dans cette direction. C'est dommage d'avoir un éclairage par pixel et de retomber sur les mêmes problèmes que l'éclairage par vertex. Nous verrons donc dans le prochain tutoriel comment résoudre ce problème en utilisant une cube map de normalisation pour définir le vecteur vers la lumière au lieu d'utiliser la couleur du vertex.

Le second problème posé par le système actuel est qu'on ne peut plus avoir de lumières colorées. Elles sont forcéments blanches. Nous verrons donc dans le prochain tutoriel une méthode basée sur un rendu multi passes pour obtenir des lumières colorées.

Liens

Vous pouvez télécharger les sources de ce tutoriel ici ou ici [http]
Une version PDF de ce tutoriel est disponible ici ou ici [http]

Image non disponible Tutoriel 3 : light mapping avec une texture 3D. Tutoriel 5 : éclairage par pixel standard. Image non disponible
(1)
La tesselation représente le niveau de découpage en polygones. Un modèle fortement tesselé est un modèle avec beaucoup de polygones alors qu'un modèle avec une faible tesselation est un modèle composé de peu de polygones.

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2006 Michel de VERDELHAN. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -

Developpez.com


Gestion dynamique de la lumière avec OpenGL - Partie 5 : éclairage par pixel standard

Table des matières Plier Déplier

  • I. Introduction.
  • II. La « cube map » de normalisation.
    • II-1. Pourquoi avons nous besoin d'une cube map de normalisation.
    • II-2. Qu'est ce qu'une cube map de normalisation.
    • II-3. Comment calculer la cube map de normalisation.
  • III. Modification du rendu.
    • III-1. Utiliser la cube map de normalisation.
    • III-2. Rendu des lumières colorées.
    • III-3. Le rendu final.
  • IV. Conclusions.
  • Liens

Dans ce tutoriel, nous allons approfondir la gestion de l'éclairage par pixel vu dans le tutoriel précédent. Nous allons notamment voir comment utiliser une cube map de normalisation pour résoudre les problèmes liés à l'interpolation des vecteurs sur les polygones larges. Je présenterais aussi une technique basée sur un rendu multi-passes pour pouvoir afficher des lumières colorées.

I. Introduction.

Nous avons vu dans le tutoriel précédent comment calculer l'éclairage par pixel. Pour cela, nous avons besoin d'une normal map qui représente les normales de la surface. Nous avons aussi besoin de calculer un espace local par vertex qui nous permet de calculer le vecteur vers la lumière qui nous sert pour le calcul du DOT3 bump mapping. Comme nous l'avons vu, bien qu'il soit assez peu visible dans le tutoriel précédent, le fait d'utiliser les vertex pour envoyer le vecteur vers la lumière pose problème. En effet, l'interpolation des vecteurs donne un résultat non normalisé, et on risque aussi d'avoir des artefacts plutôt gênant qui éclairent le polygone d'une façon incohérente par rapport à la position de la lumière. Pour résoudre ce problème, nous avons besoin d'un mécanisme qui nous permet d'avoir des vecteurs qui pointent tous effectivement dans la direction de la lumière, pas seulement au niveau des vertex mais partout sur le polygone. En plus, il serait intéressant de pouvoir améliorer par la même occasion le problème de normalisation des vecteurs pour avoir un éclairage plus cohérent. Pour cela, je vais présenter une solution basé sur l'utilisation d'une cube map particulière appelée cube map de normalisation.

Un autre problème du tutoriel précédent est que nous ne pouvons plus afficher de lumières colorées. En effet, l'extension env_dot3 utilise la couleur des vertex pour effectuer le calcul de DOT3 bump mapping. Comme nous n'avons pas de moyen standard pour pouvoir modifier la couleur de sortie du multi texturing sans utiliser de fragment program/shader, je vais présenter une solution basée sur un rendu multi passes.

II. La « cube map » de normalisation.

Nous allons voir dans cette partie ce qu'est une cube map de normalisation, pourquoi et comment l'utiliser.

II-1. Pourquoi avons nous besoin d'une cube map de normalisation.

Comme je l'ai dit dans l'introduction, l'utilisation de la couleur des vertex pour envoyer un vecteur n'est pas satisfaisante. Il nous faut un mécanisme permettant d'avoir des vecteurs qui pointent bien tous vers la lumière, et, si possible, qui soient normalisés. Comme pour le light mapping, la solution aux problèmes d'interpolation se base sur l'utilisation de textures. Néanmoins, contrairement au light mapping, ici, nous n'utiliserons pas une texture 2D ou 3D, mais une cube map. L'avantage de la cube map est qu'elle permet de calculer automatiquement une projection multidirectionnelle de textures. Si vous ne voyez pas ce qu'est une projection multidirectionnelle de texture, imaginez simplement une projection de texture dans une direction, puis effectuez cette même projection dans les six directions d'un cube. Voila, vous y êtes, la cube map permet de projeter six textures comme si elles etaient sur un cube projeté à l'infini. A l'origine, les cube maps étaient prévus pour calculer des pseudo réflexions sur des objets, mais en les utilisant d'une certaine manière, elles peuvent résoudre nos problèmes de vecteurs vers la lumière. C'est ce que nous allons voir tout de suite.

II-2. Qu'est ce qu'une cube map de normalisation.

Comme nous l'avons vu au dessus, on peut appréhender les cube maps comme des cubes infiniment lointains autour d'une position. Ceci permet d'avoir à moindre coût une projection dans les six directions d'un cube. Tout ceci est bien beau, mais en quoi cela va-t-il résoudre notre problème de vecteurs? Tout simplement en remplissant notre cube map de vecteurs.

En fait, ce qu'on appel une cube map de normalisation n'est rien d'autre qu'une cube map dont chaque texel est un vecteur pointant vers le centre de la cube map. Ainsi, lorsqu'on projette notre cube map sur un polygone, on obtient un polygone dont tous les pixels sont des vecteurs dirigés vers le centre de la cube map. Il ne nous reste donc plus qu'à placer notre cube map à l'emplacement de la lumière pour que notre polygone soit couvert de vecteurs vers la lumière. En plus, comme chaque texel de la cube map est un vecteur normalisé, le problème des vecteurs non normalisés à cause de l'interpolation entre les vertex est réduit. Par contre, il n'est pas non plus totalement éliminé, étant donné qu'on peut toujours avoir de l'interpolation entre les texels de la cube map. Néanmoins, le résultat est très nettement supérieur à celui de la méthode par vertex.

Comme souvent, un schéma explique mieux qu'un long discours.

Image non disponible Cube map de normalisation

Voici le contenu d'une cube map de normalisation : chaque texel représente un vecteur pointant vers le centre de la cube map.

Image non disponible
Projection d'une cube map de normalisation sur des polygones.

Et voila le résultat de l'utilisation d'une cube map sur des polygones : les cotés de la cube map sont projetés sur les polygones, les remplissant de vecteurs dirigés vers le centre de la cube map.

Maintenant que nous savons ce qu'est une cube map de normalisation, nous allons voir comment la calculer.

II-3. Comment calculer la cube map de normalisation.

Tout d'abord, nous allons nous occuper de gérer l'extension. Comme pour l'extension env_dot3, les cube maps n'ont pas besoin d'utiliser de nouvelles fonctions. Le chargement de l'extension est donc simple, il suffit de vérifier si la carte supporte les cube maps. La fonction de chargement des extensions devient donc ça :

Fonction de chargement des extensions
Sélectionnez

void initExtensions()
{
    if (glutExtensionSupported("GL_ARB_multitexture"))
    {
        glMultiTexCoord2fARB =		(PFNGLMULTITEXCOORD2FARBPROC)wglGetProcAddress("glMultiTexCoord2fARB");
        glMultiTexCoord3fARB =		(PFNGLMULTITEXCOORD3FARBPROC)wglGetProcAddress("glMultiTexCoord3fARB");
        glMultiTexCoord4fARB =		(PFNGLMULTITEXCOORD4FARBPROC)wglGetProcAddress("glMultiTexCoord4fARB");
        glActiveTextureARB =		(PFNGLACTIVETEXTUREARBPROC)wglGetProcAddress("glActiveTextureARB");
        glClientActiveTextureARB =	(PFNGLCLIENTACTIVETEXTUREARBPROC)wglGetProcAddress("glClientActiveTextureARB");
        if (!glActiveTextureARB || !glMultiTexCoord2fARB || !glMultiTexCoord3fARB
            || !glMultiTexCoord4fARB || !glClientActiveTextureARB)
        {
            fprintf(stderr,"impossible d'initialiser l'extension GL_ARB_multitexture\n");
            exit(0);
        }
    }
    else
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_ARB_multitexture\n");
        exit(0);
    }
    if (glutExtensionSupported("GL_EXT_texture3D"))
    {
        glTexImage3DEXT = (PFNGLTEXIMAGE3DEXTPROC) wglGetProcAddress("glTexImage3DEXT");
        glTexSubImage3DEXT = (PFNGLTEXSUBIMAGE3DEXTPROC) wglGetProcAddress("glTexSubImage3DEXT");
        if (!glTexImage3DEXT || !glTexSubImage3DEXT)
        {
            fprintf(stderr,"impossible d'initialiser l'extension GL_EXT_texture3D\n");
            exit(0);
        }
    }
    else
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_ARB_multitexture\n");
        exit(0);
    }
    if (!glutExtensionSupported("GL_ARB_texture_env_dot3"))
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_ARB_texture_env_dot3\n");
        exit(0);
    }
    if (!glutExtensionSupported("GL_ARB_texture_cube_map"))
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_ARB_texture_cube_map\n");
        exit(0);
    }
}

Maintenant, nous pouvons voir comment calculer la cube map. Pour cela, j'utilise deux fonctions. La première, getCubeVector, permet de récupérer un vecteur pointant vers le centre de la cube map en fonctions de la face du cube et de la position du texel dans la face. Cette fonction est assez simple à comprendre et donne le code suivant :

Fonction permettant de calculer un vecteur vers le centre de la cube map en fonction de sa face et de sa position dans la face.
Sélectionnez

void getCubeVector(int i, int cubesize, int x, int y, float *vector)
{
    float s, t, sc, tc, mag;

    s = ((float)x + 0.5) / (float)cubesize;
    t = ((float)y + 0.5) / (float)cubesize;
    sc = s*2.0 - 1.0;
    tc = t*2.0 - 1.0;

    // selon la face passee en parametre, on calcul le vecteur vers le centre de la cube map.
    switch (i)
    {
        case 0:
            vector[0] = 1.0;
            vector[1] = -tc;
            vector[2] = -sc;
            break;
        case 1:
            vector[0] = -1.0;
            vector[1] = -tc;
            vector[2] = sc;
            break;
        case 2:
            vector[0] = sc;
            vector[1] = 1.0;
            vector[2] = tc;
            break;
        case 3:
            vector[0] = sc;
            vector[1] = -1.0;
            vector[2] = -tc;
            break;
        case 4:
            vector[0] = sc;
            vector[1] = -tc;
            vector[2] = 1.0;
            break;
        case 5:
            vector[0] = -sc;
            vector[1] = -tc;
            vector[2] = -1.0;
            break;
    }
    // on normalise le vecteur.
    mag = 1.0/sqrt(vector[0]*vector[0] + vector[1]*vector[1] + vector[2]*vector[2]);
    vector[0] *= mag;
    vector[1] *= mag;
    vector[2] *= mag;
}

La deuxième fonction utilisée est celle qui calcule effectivement la cube map et l'envoi à OpenGL. Cette fonction prend en paramètre la taille en texel d'un coté de la cube map et génère donc une cube map de normalisation. Pour cela, elle utilise un buffer de la taille d'un coté de la cube map et s'en sert pour générer les six cotés différents en utilisant la fonction getCubeVector pour récupérer chaque texel. Le code de la fonction donne donc ça :

Fonction de chargement d'une cube map de normalisation.
Sélectionnez

void generateNormalisationCubeMap(int size)
{
    glEnable(GL_TEXTURE_CUBE_MAP_ARB);
    //genere l'objet de texture OpenGL en memoire vidéo
    glGenTextures(1,&normalCubeMapId);
    glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, normalCubeMapId);

    float vector[3];
    int i, x, y;
    GLubyte *pixels = new GLubyte[size*size*3];

    // on configure la texture. On utilise un filtrage lineaire pour ne pas avoir à
    // generer les mipmaps.
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

    // Pour chaque face
    for (i = 0; i < 6; i++)
    {
        // on remplit le buffer de texture avec des vecteurs
        for (y = 0; y < size; y++)
        {
            for (x = 0; x < size; x++)
            {
                getCubeVector(i, size, x, y, vector);
                pixels[3*(y*size+x) + 0] = 128 - 127*vector[0];
                pixels[3*(y*size+x) + 1] = 128 - 127*vector[1];
                pixels[3*(y*size+x) + 2] = 128 - 127*vector[2];
            }
        }
        // on envoi le buffer de texture à OpenGL
        gluBuild2DMipmaps(GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB+i,GL_RGB8,size,size,GL_RGB,GL_UNSIGNED_BYTE,pixels);
    }

    delete [] pixels;

    glDisable(GL_TEXTURE_CUBE_MAP_ARB);
}

Ici, je n'utilise qu'un filtrage linéaire, je n'ai donc pas généré les différents niveaux de mipmap, mais il faudrait les générer en cas d'utilisation de filtrage autre.

Ces fonctions sont des versions légèrement modifiées des fonctions utilisées dans les démo de nVidia sur le bump mapping qu'on peut trouver dans la section développeurs du site nvidia.com.

III. Modification du rendu.

Maintenant que nous savons comment calculer notre cube map de normalisation, nous allons voir comment l'utiliser. Ensuite, nous verrons comment faire pour avoir des lumières colorées, puis nous verrons le rendu final comme sur tous les tutoriaux précédents.

III-1. Utiliser la cube map de normalisation.

Maintenant que nous avons notre cube map de normalisation, il faut encore savoir comment l'utiliser. En fait, c'est très simple. Pour cela, il suffit de calculer pour chaque vertex les bonnes coordonnées de texture pour la cube map, et la projection automatique de la cube map va se charger pour nous de recouvrir le polygone comme il faut.

Ces coordonnées de textures sont en faite très simples à calculer, il s'agit tout simplement... d'un vecteur encore une fois. En effet, notre cube map peut être vu comme une sorte particulière de texture en 3D, donc pour récupérer ses informations, il faut lui passer des coordonnées de texture 3D. Voici un petit schéma qui montre comment se calculent les coordonnées de texture de la cube map.

Image non disponible
Le calcul valide des coordonnées de texture de la cube map.

Ici, le vecteur noir représente des coordonnées de texture qui vont adresser une face de la cube map, ensuite, la carte graphique va se charger de tous les calculs de projection. On peut noter ici que contrairement au tutoriel précédent, le vecteur calculé est le vecteur de la lumière vers le vertex. Dans le tutoriel précédent, nous avions fait l'inverse. Le problème est que, pour calculer notre bump mapping, nous avons besoin d'un vecteur allant du vertex vers la lumière, mais notre cube map contient des vecteurs dans le mauvais sens comme le montre le schéma suivant.

Image non disponible
Le calcul non valide des coordonnées de texture de la cube map : le vecteur final au niveau du vertex est dans le mauvais sens.

Nous voulons donc un vecteur dirigé dans l'autre sens comme sur le premier schéma, c'est pour ça que nous calculons le vecteur de la lumière vers le vertex.

A part ce point, et le fait que les unités de textures soient configurées de manières différentes du tutoriel précédent, le code de la méthode pour ajouter une lumière est relativement proche de celui du tutoriel précédent. J'ai néanmoins changé le nom de la méthode en addLightCubeMap pour bien faire la différence entre les deux tutoriaux. Le code de la méthode donne donc :

Méthode d'ajout d'une lumière à la scène avec éclairage par pixel
Sélectionnez

void Model::addLightCubeMap(Light& light)
{
    Vecteur lightPos = light.getPosition();
    glBegin(GL_TRIANGLES);
    for(int i = 0;i < nbFaces; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            int vertexIndex = faces[i].vertexIndex[j];
            // on calcule le vecteur lumiere->vertex projete dans l'espace local du vertex
            Vecteur tempVect = vertex[vertexIndex] - lightPos;
            Vecteur lightToVertex;
            lightToVertex.x = tangents[vertexIndex]*tempVect;
            lightToVertex.y = binormals[vertexIndex]*tempVect;
            lightToVertex.z = normals[vertexIndex]*tempVect;
            // nous avons donc maintenant le vecteur de la lumiere vers le vertex
            // transforme dans l'espace local du vertex.
            // on l'envoie en temps que coordonnees de texture pour la cube map
            glMultiTexCoord3fARB(GL_TEXTURE0_ARB,lightToVertex.x,lightToVertex.y,lightToVertex.z);
            // maintenant, on envoie les coordonnees de textures
            // la seconde unite de texture contient la normal map, ce sont donc
            // les eme coordonnees de textures que la scene
            TexCoord &tc = texCoords[faces[i].texCoordIndex[j]];
            glMultiTexCoord2fARB(GL_TEXTURE1_ARB,tc.u,tc.v);
            // la troisieme unite de texture contient la texture 3D d'attenuation,
            // on calcule donc l'eclairage
            Vecteur &v = vertex[vertexIndex];
            Vecteur lightTexCoord = light.computeLighting(v);
            glMultiTexCoord3fARB(GL_TEXTURE2_ARB,lightTexCoord.x,lightTexCoord.y,lightTexCoord.z);
            // la quatrieme unite de texture contient la texture de diffuse,
            // donc les coordonnees de textures de la scene
            glMultiTexCoord2fARB(GL_TEXTURE3_ARB,tc.u,tc.v);
            glVertex3f(v.x,v.y,v.z);
        }
    }
    glEnd();
}

Comme d'habitude, la méthode suppose qu'OpenGL est bien configuré, à savoir : la première unité de texture contient la cube map de normalisation, la deuxième contient la normal map configurée pour effectuer la passe de DOT3, la troisième contient la texture 3D d'atténuation et la dernière contient la texture diffuse de la scène. De plus, le blending doit être positionné en mode additif pour que les lumières puissent bien être ajoutées à la scène.

III-2. Rendu des lumières colorées.

Ca y est nous avons un rendu de l'éclairage par pixel correct, mais il nous manque quand même un élément important que nous avions avec l'éclairage par vertex et les light maps : nos lumières ne sont plus colorées. Le problème, c'est que nous ne pouvons plus modifier la couleur de notre lumière via glColor car le fait d'utiliser l'extension env_dot3 nous génère forcément un résultat en niveaux de gris, quelque soit la couleur précédente du pixel. Comme il n'existe pas d'extensions standard pour modifier la couleur après la passe de DOT3 (on peut le faire avec des extension propriétaires comme les register combiner ou encore les textures shaders sur GeForce 4, mais je tiens à n'utiliser que des extensions validées par l'ARB.), et comme on n'utilise pas de fragment program/shaders ici, il va falloir passer à un rendu multi passes pour obtenir nos lumières colorées.

Pour cela, il nous suffit simplement d'effectuer une passe supplémentaire par lumières en multipliant le résultat précédent par la couleur souhaitée. Pour éviter au maximum les changements d'état OpenGL, on peut organiser ça en rendant d'abord toutes les lumières avec le bump mapping, puis on effectue une seconde passe en multipliant toutes les lumières par leur couleur. Le problème, c'est que si on utilise le blending multiplicatif simple, on ne pourra effectuer cette méthode qu'avec une seule lumière. En effet, le fait de multiplier la scène va rendre noires toutes les parties qui ne sont pas dans la lumière courante, on obtient donc seulement l'intersection de toutes les lumières de la scène. Il nous faut donc trouver une autre solution.
Cette solution est toute simple, elle consiste à utiliser le blending multiplicatif+additif présenté dans le premier tutoriel sur le light mapping. Ainsi, on peut multiplier la lumière par sa couleur sans pour autant noircir le reste de la scène. Cela donne un rendu de la scène plus clair et les lumières peuvent saturer vers le blanc, mais le resultat est acceptable.
L'algorithme de rendu donne donc le pseudo code suivant :

Pseudo code de l'algorithme de rendu des lumières colorées.
Sélectionnez

Rendu de la couleur ambiante
Blending additif
Pour chaque lumière :
    Ajouter la passe de bump mapping à la scène
Fin pour
Blending multiplicatif+additif
Pour chaque lumière :
    Multiplier la scène précédente par la couleur de la lumière.
Fin pour

Comme vous pouvez le voir, il nous faut pouvoir afficher juste la couleur de nos lumières pour la deuxième passe. Ceci correspond en fait à une passe d'affichage de la texture d'atténuation, autrement dit : une simple passe de light mapping comme nous l'avons fait dans les tutoriaux 2 et 3, mais sans multiplier le tout par la texture diffuse de la scène. Ici, nous ne rendons que la light map dynamique avec la couleur de la lumière. Ceci nous donne donc la méthode suivante :

Méthode de rendu de la couleur de la lumière.
Sélectionnez

void Model::renderColor(Light& light)
{
    glColor3f(light.getColor().r,light.getColor().g,light.getColor().b);
    Vecteur lightPos = light.getPosition();
    glBegin(GL_TRIANGLES);
    for(int i = 0;i < nbFaces; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            Vecteur &v = vertex[faces[i].vertexIndex[j]];
            Vecteur lightTexCoord = light.computeLighting(v);
            // la premiere unité de texture contient la texture d'attenuation
            glMultiTexCoord3fARB(GL_TEXTURE0_ARB,lightTexCoord.x,lightTexCoord.y,lightTexCoord.y);
            glVertex3f(v.x,v.y,v.z);
        }
    }
    glEnd();
}

III-3. Le rendu final.

Maintenant que nous avons tous les éléments en main pour rendre nos lumières, nous pouvons voir comment effectuer le rendu final. Voici d'abord le code nécessaire pour rendre les lumières par pixel correctement sans prendre en compte la couleur des lumières :

Code de rendu des lumières par pixel sans couleur
Sélectionnez

// on configure la premiere unite de texture : elle contient la texture de la scene
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, textureId);
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);

// on effectue le rendu de la lumière ambiante
model->initLighting(ambiant);
if (drawLocalSpace)
{
    glDisable(GL_TEXTURE_2D);
    model->drawLocalSpace();
}

// on doit reconfigurer les unites de textures
// la premiere unite contient la cube map de normalisation
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_CUBE_MAP_ARB);
glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, normalCubeMapId);
// on souhaite que la cube map remplace la couleur des vertex
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_REPLACE);

// la deuxieme unité de texture contient la normal map
glActiveTextureARB(GL_TEXTURE1_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, normalMapId);
// on configure la normal map pour qu'elle effectue la passe de DOT3
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_COMBINE_ARB);
glTexEnvf(GL_TEXTURE_ENV,GL_RGB_SCALE_ARB,1);
glTexEnvf(GL_TEXTURE_ENV,GL_COMBINE_RGB_ARB,GL_DOT3_RGB_ARB);
glTexEnvf(GL_TEXTURE_ENV,GL_SOURCE0_RGB_ARB,GL_PREVIOUS_ARB);
glTexEnvf(GL_TEXTURE_ENV,GL_SOURCE1_RGB_ARB,GL_TEXTURE);
glTexEnvf(GL_TEXTURE_ENV,GL_OPERAND0_RGB_ARB,GL_SRC_COLOR);
glTexEnvf(GL_TEXTURE_ENV,GL_OPERAND1_RGB_ARB,GL_SRC_COLOR);

// la troisieme contient la texture d'attenuation
glActiveTextureARB(GL_TEXTURE2_ARB);
glEnable(GL_TEXTURE_3D_EXT);
glBindTexture(GL_TEXTURE_3D_EXT,lightmap );
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
// la quatrieme contient la texture de diffuse
glActiveTextureARB(GL_TEXTURE3_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,textureId );
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);

// on doit activer le blending additif pour pouvoir utiliser ce mode d'eclairage
glEnable(GL_BLEND);
glBlendFunc(GL_ONE,GL_ONE);
glDepthMask(GL_FALSE);

// on ajoute nos lumières
model->addLightCubeMap(light1);
model->addLightCubeMap(light2);

// on desactive la quatrieme unite de texture
glActiveTextureARB(GL_TEXTURE3_ARB);
glDisable(GL_TEXTURE_2D);
// on desactive la troisieme unite de texture
glActiveTextureARB(GL_TEXTURE2_ARB);
glDisable(GL_TEXTURE_3D_EXT);
// on desactive la seconde unite de texture
glActiveTextureARB(GL_TEXTURE1_ARB);
glDisable(GL_TEXTURE_2D);
// on desactive la premiere unite de texture
glActiveTextureARB(GL_TEXTURE0_ARB);
glDisable(GL_TEXTURE_CUBE_MAP_ARB);

// on desactive le blending
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);

Et maintenant, le code pour avoir des lumières colorées. Ce code s'ajoute simplement à la suite du précédent (mais avant la désactivation du blending).

Code de rendu des lumières colorées
Sélectionnez

glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_3D_EXT);
glBindTexture(GL_TEXTURE_3D_EXT,lightmap );
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);

glBlendFunc(GL_DST_COLOR,GL_ONE);
model->renderColor(light1);
model->renderColor(light2);
glActiveTextureARB(GL_TEXTURE0_ARB);
glDisable(GL_TEXTURE_3D_EXT);

Et voila le résultat final de notre gestion de l'éclairage par pixel avec comme d'habitude 3 lumières : 1 rouge, une bleue et une blanche.

Image non disponible Le rendu final de la scène.

IV. Conclusions.

Ca y est, nous avons une gestion de l'éclairage par pixel fonctionnelle et complète. Enfin, complète, c'est très relatif. Comme je le disais au début de ce tutoriel, on en veut toujours plus, et, si vous vous souvenez du premier tutoriel, j'avais parlé de la composante spéculaire de la lumière, or, elle n'est pas gérée ici. C'est pourquoi nous allons voir dans le prochain tutoriel comment mettre en place un système de gestion des lumières complexes basé sur l'utilisation de shaders. Nous y verrons donc comment gérer la composante spéculaire de l'éclairage et en plus, nous y ajouterons la gestion du parallax mapping (aussi appelé offset bump mapping), qui permet de déformer les textures pour donner un effet de relief impressionnant pour un coût très faible.
Néanmoins, la technique présentée ici peut être très suffisante pour la plus part des moteurs graphiques amateurs. En effet, il ne faut pas oublier que si l'éclairage par pixel a un très bon rendu visuel, il impose au graphistes de créer des normal maps pour les modèles et pour le niveau, ce qui est un surcoût important en temps de production, et le modèle d'éclairage présenté dans le prochain tutoriel sera encore plus coûteux en terme de création de textures.

Il existe une technique permettant de se passer de cube map de normalisation, mais qui n'est pas non plus totalement satisfaisante. Elle consiste à stocker des vecteurs directement dans la texture 3D. Chaque vecteur pointe vers le centre de la texture, et surtout, sa longueur est proportionnelle à l'éloignement du texel par rapport au centre de la texture. On a donc des vecteurs dont la norme est proche de 1 vers le centre de la texture et nulle sur les bords de la texture. Ainsi, la texture 3D permet de stocker en même temps les vecteurs vers la lumière et l'atténuation de l'éclairage. Le problème de cette méthode est que les texels au centre de la texture vont eux aussi être nul (car pointant sur eux même étant donné qu'ils sont au centre). On a donc un trou noir au centre de notre lumière. Si la lumière ne s'approche pas trop des polygones, cet artefact ne se verra pas, mais ça n'en reste pas moins une limitation importante. Je ne présenterais donc pas plus en détail cette technique.

Liens

Vous pouvez télécharger les sources de ce tutoriel ici ou ici [http]
Une version PDF de ce tutoriel est disponible ici ou ici [http]

Image non disponible Tutoriel 4 : éclairage par pixel simple. Tutoriel 6 : éclairage par pixel complexe avec shaders Image non disponible

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2006 Michel de VERDELHAN. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -


Gestion dynamique de la lumière avec OpenGL - Partie 6 : éclairage complexe à base de shaders

Table des matières Plier Déplier

  • I. Les concepts lié à la lumière.
    • I-1. L'éclairage spéculaire.
    • I-2. Les materials.
    • I-3. Le parallax mapping.
  • II. Modifications apportée au programme.
    • II-1. Gestion des extensions.
    • II-2. Modification du rendu des modèles.
    • II-3. Les vertex et fragment programs.
    • II-4. Le rendu final.
  • III. Réflexions sur la gestion de la lumière.
  • IV. Conclusions.
  • VI. Liens

Ce tutoriel représente le dernier de cette série de tutoriaux sur la gestion de l'éclairage dynamique en temps réel avec OpenGL. Dans ce tutoriel, je présente un système d'éclairage complexe à base de shaders. Nous y verrons notamment comment gérer la composante spéculaire de la lumière, mais aussi une méthode permettant une meilleure impression de relief sur nos textures : le parallax mapping. Attention : ce tutoriel requiert d'avoir bien compris les tutoriaux précédents ou d'être familier avec les concepts de la lumière pour être bien compris. De même, des notions sur le fonctionnement des vertex/fragments programs sont utiles pour une bonne compréhension. Ce tutoriel n'a pas pour but d'expliquer le fonctionnement de ces extensions.

I. Les concepts lié à la lumière.

Dans cette partie, je vais (re)présenter les concepts fondamentaux du fonctionnement de la lumière, puis je parlerai d'une notion très liée à la lumière : la gestion des materials, et finalement, je parlerai du parallax mapping.

I-1. L'éclairage spéculaire.

Nous avons déjà vu dans les tutoriaux précédents une grande partie du fonctionnement de la lumière. Nous savons donc comment calculer l'atténuation de la lumière via une texture 3D d'atténuation. Nous avons aussi vu comment prendre en compte l'orientation de la face par pixel grâce au calcul du produit scalaire via l'extension env_dot3. Il ne nous reste plus qu'à voir comment fonctionne la composante spéculaire de la lumière.

La composante spéculaire de la lumière est en fait la capacité qu'a une surface à refléter la lumière. On parle souvent de reflets spéculaires. Pour bien voir ce que représente cette composante spéculaire, il suffit d'imaginer la différence entre une surface chromée et un pneu. Les deux surfaces seront noires en l'absence de lumière, mais si il y a une lumière, la surface chromée sera plus brillante que le pneu. Ceci est dû au fait que la surface chromée renvoi plus de lumière que le pneu qui, au contraire, en absorbe la majeur partie.

Pour calculer la composante spéculaire, il suffit de calculer le produit scalaire entre la normal à la surface et le vecteur de demi angle... qu'est ce que c'est que le vecteur de demi angle ? C'est tout simplement le vecteur représentant la moitié de l'angle entre le vecteur vers la lumière et le vecteur vers la camera. Comme ce vecteur dépend de la position de la camera, nous avons donc bien un calcul qui va changer en fonction de la camera et donc donner l'impression de reflet.

Image non disponible
Le vecteur de demi angle représente le vecteur median au vecteurs vers la lumière et vers oeil.

Pour calculer le vecteur de demi angle, rien de plus simple, il suffit d'additionner le vecteur vers la lumière et le vecteur vers la camera, et on obtient notre vecteur. Il ne nous reste plus qu'a le normaliser et voila, on peut calculer notre produit scalaire pour obtenir la composante spéculaire. Malheureusement, si on effectue simplement le calcul comme cela, on obtient une surface bien trop réfléchissante. Pour réduire cet effet de surbrillance, on utilise une fonction puissance qui va réduire la réflexivité des pixels peu éclairés tout en préservant la luminosité des pixels fortement éclairés.

Voici le résultat de la composante spéculaire seule sans utiliser de fonction puissance :

Image non disponible
Composante spéculaire sans utiliser de fonction puissance (et sans atténuation)

Et le résultat avec fonction puissance :

Image non disponible
Composante spéculaire avec fonction puissance.

On perçoit nettement mieux l'impression que la lumière se reflète sur les surfaces avec une fonction puissance. Dans ce tutoriel, le reflet spéculaire est mis à la puissance 8.

Le but de la composante spéculaire étant de faire saturer la couleur des pixels aux endroits de forte réflexion, elle est donc additionnée aux autres composantes de la lumière. Le calcul de la luminosité finale devient donc :

Luminosité = ambiante + ( (diffuse + spéculaire) * atténuation)

Le problème avec cette équation, c'est qu'elle impose d'avoir le même taux de reflets spéculaires sur toute la surface, or il est intéressant de pouvoir spécifier cela par pixel pour pouvoir, par exemple, simuler des surface brillantes par endroit et mat à d'autres endroits. Un bon exemple pour comprendre l'intérêt de pouvoir moduler la composante spéculaire est une plaque de métal rouillée. La ou le métal n'est pas rouillé, on a un reflet spéculaire maximum, mais la ou il est rouillé, il n'y a pas de reflets. Pour mieux illustrer l'intérêt de la composante spéculaire, voici deux images de notre scène finale : la première sans reflets spéculaires.

Image non disponible
Scène sans reflets spéculaires.

Et le deuxième avec des reflets spéculaires.

Image non disponible
Scène avec reflets spéculaires.

Pour pouvoir effectuer cela, nous allons utiliser une gloss map, qui est une des composantes des materials que nous allons voir maintenant.

I-2. Les materials.

Dans le model d'éclairage d'OpenGL, on peut spécifier à chaque vertex sa couleur ambiante, sa couleur diffuse, et sa couleur spéculaire, or maintenant, nous n'utilisons plus de l'éclairage par vertex mais par pixel. Il est dommage de devoir spécifier ces composantes par vertex alors que le reste des calculs d'éclairage va se faire par pixel. C'est pour cela que nous avons besoin de la même notion de materials qu'OpenGL, mais cette fois ci par pixel (pour pouvoir simuler l'effet de la plaque de métal rouillée vu plus haut par exemple.). Pour cela, nous allons utiliser plusieurs textures par materials.

  • La texture de diffuse : Elle représente la couleur du material à son éclairage maximum. Contrairement au textures qu'on aperçoit généralement dans les jeux, notre texture de diffuse ne doit par contenir d'information d'éclairage, c'est-à-dire, qu'elle ne doit pas être pré ombrée, sinon, ces informations d'éclairage vont entrer en conflit avec notre éclairage en temps réel (en étant mal orienté par exemple). Voici la texture de diffuse utilisée dans ce tutoriel.
    Image non disponible
  • La normal map : Bien qu'elle ne soit pas une des composante direct de l'éclairage, la normal map nous sert à calculer la composante diffuse et spéculaire. Elle représente le volume virtuel de notre surface comme nous l'avons vu dans le quatrième tutoriel. Pour réduire la place occupée sur le disque par les textures, on peut retirer la normal map de la height map, que nous verrons plus loin, mais je ne le fais pas dans ce tutoriel. Voici la normal map utilisée dans ce tutoriel. Image non disponible
  • La gloss map : Comme nous l'avons vu plus haut, la gloss map permet de spécifier par pixel l'intensité, mais aussi la couleur des reflets spéculaires. On peut par exemple faire une gloss map jaune pour l'utiliser sur un material représentant de l'or, ainsi, on s'assure que, quelque soit la couleur de la lumière qui éclaire notre surface, le reflet spéculaire sera toujours jaune. Voici la gloss map utilisée dans ce tutoriel. Image non disponible
  • La height map : Ici, la height map n'entre pas dans le calcul de la lumière (bien qu'on puisse l'utiliser pour calculer la normal map), mais elle va nous servir au calcul du parallax mapping. La height map est une texture représentant, comme son nom l'indique, la hauteur de chaque pixel. Il peut être préférable d'utiliser des height maps servant à générer des normal maps au lieu d'utiliser directement des normal maps pour plusieurs raisons. En effet, une height map en niveau de gris prend moins de place sur le disque, permet de générer la normal map et de parallax mapping, mais il y a aussi des considérations qualitatives que je ne présenterai pas ici. Voici la height map utilisée dans ce tutoriel. Image non disponible

Vous aurez peut être remarqué qu'il n'y a pas d'ambiante map... Cela est dû au fait que la lumière ambiante peut être obtenu à partir de la texture de diffuse.

I-3. Le parallax mapping.

Bien qu'il ne soit pas directement lié à la gestion de la lumière, le parallax mapping n'est néanmoins utilisable qu'avec un système d'éclairage par pixel. En effet, le parallax mapping consiste à déformer les textures en fonction d'une height map pour obtenir un effet de volume plus prononcé qu'avec simplement du bump mapping. Il n'y a donc aucun intérêt à utiliser du parallax mapping avec du simple light mapping par exemple.

Un gros avantage du parallax mapping est qu'il est très peu consommateur en ressources et qu'il est facile à mettre en oeuvre. En effet, il suffit d'avoir une height map (on peut la stocker dans la composante alpha de la normal map) et un vecteur du pixel vers la camera pour calculer les nouvelles coordonnées de textures déformées.

Le parallax mapping consiste simplement à calculer un décalage dans les coordonnées de textures en fonction de la direction du pixel par rapport à la camera et de la hauteur du pixel dans la height map. Ceci se résume à trois lignes supplémentaires dans le fragment program.Encore une fois, pour bien voir la différence, rien ne vaut des images comparatives avec/sans.

Image non disponible
Scène sans parallax mapping. Image non disponible
Scène avec parallax mapping.

On peut voir que les pierres on été déformées pour donner une meilleur impression de volume, mais surtout, cette déformation se fait par rapport à la position de la camera, donc si la camera bouge, la déformation va se modifier pour prendre en compte la nouvelle direction de la camera.

II. Modifications apportée au programme.

Dans cette partie, nous allons voir les modifications apportées au programme. Nous verrons d'abord la nouvelle gestion des extensions mise en place pour ce tutoriel, puis nous verrons les modifications apportées au rendu des modèles. Je détaillerais ensuite les vertex et fragment program utilisé dans le tutoriel, puis, finalement, nous verrons comment mettre en place le rendu final de nos lumières.

II-1. Gestion des extensions.

Depuis le deuxième tutoriel, nous avons ajouté à chaque nouveau tutoriel de nouvelles extensions. Bien que nous n'ayons pas chargé toutes les fonctions liées à ces extensions, notre fonction de chargement des extensions devient de plus en plus grosse. Il nous faut aussi passer les fonctions aux classes qui vont les utiliser (la classe de modèles utilise les fonctions du multitextuing par exemple), ce qui implique l'utilisation de nombreuses variables externes. Pour faciliter la gestion des extensions, nous utiliserons désormais GLEW. GLEW est une bibliothèque qui permet de gérer facilement et de façon totalement transparente les extensions OpenGL. En utilisant GLEW, notre fonction de chargement des extensions devient simplement ça :

Fonction de chargement des extensions.
Sélectionnez

void initExtensions()
{
    GLenum err = glewInit();
    if (GLEW_OK != err)
    {
        // impossible d'initialiser glew
        fprintf(stderr, "Erreur a l'initialisation de GLEW : %s\n", glewGetErrorString(err));
        exit(0);
    }
    if (!glewIsSupported("GL_ARB_multitexture"))
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_ARB_multitexture\n");
        exit(0);
    }
    if (!glewIsSupported("GL_EXT_texture3D"))
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_ARB_multitexture\n");
        exit(0);
    }
    if (!glewIsSupported("GL_ARB_texture_env_dot3"))
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_ARB_texture_env_dot3\n");
        exit(0);
    }
    if (!glewIsSupported("GL_ARB_texture_cube_map"))
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_ARB_texture_cube_map\n");
        exit(0);
    }
    if (!glewIsSupported("GL_ARB_vertex_program"))
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_ARB_vertex_program\n");
        exit(0);
    }
    if (!glewIsSupported("GL_ARB_fragment_program"))
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_ARB_fragment_program\n");
        exit(0);
    }
}

Ici, on vérifie simplement que la carte supporte bien les extensions, mais on ne les chargent pas, c'est la fonction glewInit qui s'en est chargé pour nous. Nous pouvons maintenant utiliser toutes nos fonctions supportées par la carte comme si elles étaient des fonctions standard OpenGL.

Lors de l'inclusion du header glew.h, bien vérifier que l'inclusion est placée avant celle du fichier gl.h.

II-2. Modification du rendu des modèles.

Ici, pas de gros changements. Nous allons juste modifier la méthode d'ajout d'une lumière. Comme nous allons utiliser un vertex program, l'ensemble des calculs effectués par vertex se feront dans le vertex program, ce qui va réduire d'autant notre fonction d'ajout des lumières. La seule chose à bien comprendre ici, c'est que nous devons envoyer certaines informations au vertex/fragment program comme les trois vecteurs de l'espace local du vertex ou encore la position de la lumière et son rayon. Pour cela, j'utilise les coordonnées de textures, mais on aurai très bien pu passer par des tableaux d'attributs... A part cela, la méthode est assez simple à comprendre puisqu'il ne s'agit que d'envoyer la géométrie à la carte graphique, plus les informations nécessaires aux calculs. Le code de la méthode donne donc ça :

Méthode d'ajout des lumières à la scène.
Sélectionnez

void Model::addLightCubeMap(Light& light)
{
    Vecteur lightPos = light.getPosition();
    // on passe la couleur de la lumiere
    glColor3f(light.getColor().r,light.getColor().g,light.getColor().b);
    // on passe la position de la lumiere et son rayon dans la coordonnée de texture 1
    glMultiTexCoord4fARB(GL_TEXTURE1_ARB,lightPos.x,lightPos.y,lightPos.z,
                            1/light.getRadius());
    glBegin(GL_TRIANGLES);
    for(int i = 0;i < nbFaces; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            int vertexIndex = faces[i].vertexIndex[j];
            // on passe les tangentes, binormales et normales dans
            // les coordonnees de textures 2,3 et 4
            glMultiTexCoord3fARB(GL_TEXTURE2_ARB,tangents[vertexIndex].x,
                            tangents[vertexIndex].y,tangents[vertexIndex].z);
            glMultiTexCoord3fARB(GL_TEXTURE3_ARB,binormals[vertexIndex].x,
                            binormals[vertexIndex].y,binormals[vertexIndex].z);
            glMultiTexCoord3fARB(GL_TEXTURE4_ARB,normals[vertexIndex].x,
                            normals[vertexIndex].y,normals[vertexIndex].z);
            // on passe les coordonnée de la scene dans la coordonnée de texture 0
            TexCoord &tc = texCoords[faces[i].texCoordIndex[j]];
            glMultiTexCoord2fARB(GL_TEXTURE0_ARB,tc.u,tc.v);
            Vecteur &v = vertex[vertexIndex];
            glVertex3f(v.x,v.y,v.z);
        }
    }
    glEnd();
}

Ici, je n'envoie pas le rayon de la lumière, mais l'inverse de son rayon. Cela permet de réduire les calculs car pour calculer l'atténuation j'ai besoin de faire une division par le rayon, or, il est plus rapide de multiplier par l'inverse du rayon que de faire cette division directement dans le vertex/fragment program.

Comme d'habitude, je suppose ici qu'OpenGL est bien configuré, c'est-à-dire que les vertex/fragment programs sont bien activé, le blending est en mode additif, et les unités de textures sont bien positionnées.

II-3. Les vertex et fragment programs.

Dans cette partie, je vais présenter le vertex program et le fragment program utilisés dans le tutoriel pour calculer l'éclairage. J'ai préféré utiliser des programs assembleurs ARB au lieu de vertex/fragment shaders ARB car ceux-ci sont supporté par moins de cartes, mais aussi car les programs sont en assembleur, ce qui est plus compliqué à mettre en oeuvre, mais permet de mieux se rendre compte de l'étendu des calculs effectués par la carte graphique.

Dans un premier temps, il nous faut envoyer nos programs à OpenGL pour les compiler et les faire valider par la carte graphique. C'est le travail effectué par la méthode initShaders suivante.

Fonction d'envoi des shaders à la carte graphique
Sélectionnez

void initShaders()
{
    glGenProgramsARB(1, &fragmentProgramId);
    glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, fragmentProgramId);
    glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB,
                strlen((char*) fragmentProgram), fragmentProgram);

    if(GL_INVALID_OPERATION == glGetError() )
    {
        //une erreur est detectee
        //recupere les informations sur l'erreur survenue
        const unsigned char* errorString;
        GLint errorPos = 0;
        errorString=glGetString(GL_PROGRAM_ERROR_STRING_ARB);
        glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB,&errorPos);

        fprintf(stderr,"une erreur est survenue a la position : %d\n%s\n",
                 errorPos, errorString);
        // on affiche le program jusqu'a l'erreur
        for (int i = 0; i < errorPos; i++)
        {
            printf("%c", fragmentProgram[i]);
        }
        printf("\n");
    }

    // chargement du vertex program
    glGenProgramsARB(1, &vertexProgramId);
    glBindProgramARB(GL_VERTEX_PROGRAM_ARB, vertexProgramId);
    glProgramStringARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB,
                strlen((char*) vertexProgram), vertexProgram);

    if(GL_INVALID_OPERATION == glGetError())
    {
        //une erreur est detectee
        //recupere les informations sur l'erreur survenue
        const unsigned char* errorString;
        GLint errorPos = 0;
        errorString=glGetString(GL_PROGRAM_ERROR_STRING_ARB);
        glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB,&errorPos);

        fprintf(stderr,"une erreur est survenue a la position : %d\n%s\n",
                 errorPos, errorString);
        // on affiche le program jusqu'a l'erreur
        for (int i = 0; i < errorPos; i++)
        {
            printf("%c", vertexProgram[i]);
        }
        printf("\n");
    }
}

Maintenant que nous avons chargé nos programs, nous allons voir ce qu'ils contiennent. Les explications des programs sont contenues directement en tant que commentaires pour éviter d'avoir à les découper en petites parties. D'abord, voilà le vertex program utilisé. Il effectue tous les calculs par vertex que nous faisions avant sur le CPU.

Vertex program utilisé dans le tutoriel
Sélectionnez

# vertex program utilisé pour gérer l'éclairage
# il effectue la projection du vecteur vers la lumière et
# du vecteur vers la camera qui sont utilisé dans le fragment
# program par la suite.
# @param vertex.texcoord[0] : coordonnées de texture du polygone
# @param vertex.texcoord[1] : position de la lumiere + rayon dans w
# @param vertex.texcoord[2] : tangente
# @param vertex.texcoord[3] : binormal
# @param vertex.texcoord[4] : normale
# @param vertex.texcoord[5] : position de la camera
# @param vertex.color : couleur de la lumiere
#
# @out result.texcoord[0] : coordonnée de texture du polygone
# @out result.texcoord[1] : vecteur du vertex vers la camera
# @out result.texcoord[2] : vecteur du vertex vers la lumiere
!!ARBvp1.0 OPTION ARB_position_invariant;

# mvit repesente l'inverse transposé de la matrice
# de modelview
PARAM    mvit[4] = {state.matrix.modelview.invtrans};
# modelview est la matrice de modelview
PARAM    modelView[4]    = { state.matrix.modelview };
# camPos est la position de la camera
# note : pour récupérer la position de la camera,
# il suffit de prendre la quatrième ligne de l'inverse
# transposé de la matrice de modelview
PARAM    camPos = state.matrix.modelview.invtrans.row[3];
# les texcoord 2 contienne la tangente
ATTRIB    tangent    = vertex.texcoord[2];
# les texcoord 3 contiennent la binormale
ATTRIB    binormal    = vertex.texcoord[3];
# les texcoord 4 contiennent la normale
ATTRIB    normal     = vertex.texcoord[4];
# les texcoord 1 contiennent les informations sur
# la lumière, c-a-d : sa position et l'inverse de
# son rayon dans la composante w
ATTRIB    lightInfo    = vertex.texcoord[1];
TEMP    vertexToLight;
TEMP    vertexToCam;
TEMP    temp;

# on projete le vecteur vers la lumière dans l'espace local
# du vertex
SUB    temp, lightInfo, vertex.position ;
DP3    vertexToLight.x, temp, tangent;
DP3    vertexToLight.y, temp, binormal;
DP3    vertexToLight.z, temp, normal;
MOV    vertexToLight.w, lightInfo.w;

# on projete le vecteur vers la camera dans l'espace local
# du vertex
SUB    temp, camPos, vertex.position;
DP3    vertexToCam.x, temp, tangent;
DP3    vertexToCam.y, temp, binormal;
DP3    vertexToCam.z, temp, normal;

# ici, je ne normalise pas les vecteurs calculé, ce sera
# fait dans le fragment program pour améliorer la qualité
# de l'affichage (plus de problèmes de normalisation dû à
# l'interpolation)

# on copie les informations calculées sur les sortie du
# vertex program.
MOV    result.texcoord[0], vertex.texcoord[0];
MOV    result.texcoord[1], vertexToCam;
MOV    result.texcoord[2], vertexToLight;
MOV    result.color, vertex.color;
END

Et maintenant, voilà le fragment program qui calcul le résultat final de notre ajout de lumière.

Fragment program de calcul de la lumière.
Sélectionnez

# fragment program gérant l'affichage de la lumière
# ce fragment program calcul l'éclairage avec reflets
# spéculaires et avec du parallax mapping.
# @param texture[0] : normal map
# @param texture[1] : diffuse map
# @param texture[2] : height map
# @param texture[3] : gloss map
# @param texture[4] : attenuation map
# @param fragment.texcoord[0] : coordonnées de texture du polygone
# @param fragment.texcoord[1] : vecteur du fragment vers la camera
# @param fragment.texcoord[2] : vecteur de fragment vers la lumière
# @param fragment.color : la couleur de la lumiere
!!ARBfp1.0 OPTION ARB_precision_hint_fastest;
PARAM    power = {8,0,0,0};
# la texture de normal map
TEMP    normal;
# la texture d'attenuation
TEMP    attenuation;
# la texture de diffuse
TEMP    diffuse;
# la texture de gloss
TEMP    gloss;
# la texture de height map
TEMP    height;
# les coordonnées de textures modifiées par
# le parallax mapping
TEMP    newTexcoord;
# le vecteur allant du fragment vers la lumière
TEMP    toLight;
# la couleur final du fragment
TEMP    final;
# le résultat du produit scalaire entre la normale
# et le vecteur vers la lumière
TEMP    NdotL;
# vecteur temporaire
TEMP    temp;
# le vecteur de demi angle pour le spéculaire
TEMP    halfAngle;
# le vecteur du fragment vers la camera
TEMP    toCamera;
# la composante spéculaire de la lumière
TEMP    specular;

# on normalise le vecteur fragment->lumiere
# note : en l'absence d'extension permettant d'effectuer
# la normalisation d'un vecteur en une instruction, il faut
# utiliser la séquence suivante pour normaliser un vecteur
# (ici on normalise fragment.texcoord[2])
DP3    temp, fragment.texcoord[2], fragment.texcoord[2];
RSQ    temp, temp.x;
MUL    toLight, fragment.texcoord[2], temp;

# on normalise le vecteur fragment->camera
DP3    temp, fragment.texcoord[1], fragment.texcoord[1];
RSQ    temp, temp.x;
MUL    toCamera, fragment.texcoord[1], temp;

# on calcul les nouvelles coordonnées de textures pour le parallax mapping
TEX    height, fragment.texcoord[0], texture[2], 2D;
MAD    height, height, 0.04, -0.02;
MAD    newTexcoord, height, toCamera, fragment.texcoord[0];

# on calcule l'attenuation
# on a texcoord[2] qui contient le vecteur vertex->lumiere
# on divise le vecteur par le rayon de la lumiere (w contient l'inverse du rayon)
# et on ajoute 0.5 pour partir du centre de la texture
# cf : calcul d'attenuation dans les tutoriaux sur le light mapping.
MAD    attenuation, fragment.texcoord[2], fragment.texcoord[2].w, {0.5,0.5,0.5,0};
TEX    attenuation, attenuation, texture[4], 3D;

# on charge la normal map
TEX    normal, newTexcoord, texture[0], 2D;

# on remap la normal dans [0..1] au lieu de [0.5..1]
MAD    normal, normal, 2, -1;

# on renormalise la normal pour maximiser la qualité
# d'affichage (plus d'interpolation, tout les vecteurs
# sont bien normalisé)
DP3    temp, normal, normal;
RSQ    temp, temp.x;
MUL    normal, normal, temp;

# on effectue le produit scalaire entre la
# normale à la surface et le vecteur transformé
# vers la lumière.
DP3    NdotL, normal, toLight;
MAX    NdotL, NdotL, 0;
MUL_SAT    final, NdotL, fragment.color;

# calcul de la composante spéculaire
# on calcul le vecteur de demi angle normalisé
ADD    halfAngle, toCamera, toLight;
DP3    temp, halfAngle, halfAngle;
RSQ    temp, temp.x;
MUL    halfAngle, halfAngle, temp;

# on effectue le calcul du produit scalaire entre la
# normale à la surface et le vecteur de demi angle.
# on s'assure que la composante speculaire n'est pas
# inferieure à 0, ce qui entrainerai des problèmes lors
# de l'addition avec le rest du calcul. En effet, la
# composante spéculaire peut ajouter de la luminosité à un
# fragment mais ne doit pas en retirer.
DP3    specular, normal, halfAngle;
MAX    specular, specular, 0;

# on met le resultat a la puissante en parametre
POW    specular, specular.x, power.x;

# on multiplie par la couleur de la lumiere
MUL    specular, specular, fragment.color;

# on recupere les textures restantes
TEX    diffuse, newTexcoord, texture[1], 2D;
TEX    gloss, newTexcoord, texture[3], 2D;

# on termine l'equation d'eclairage
# on multiplie la composante speculaire par le gloss
MUL    specular, specular, gloss;

# on multiplie le bump par la texture de diffuse et
# on ajoute la composante spéculaire
MAD    final, final, diffuse, specular;

# on attenue le tout
MUL    final, final, attenuation;

# et on envoi le resultat du calcul en temps que couleur
# finale.
MOV    result.color, final;
END

Si vous n'êtes pas familier avec les vertex/fragment programs ARB, ceux-ci peuvent vous sembler incompréhensibles, et pourtant ils ne font que des calculs assez simples à comprendre. Si vraiment vous ne comprenez pas, vous pouvez toujours aller regarder une doc sur les instructions des programs ARB.

Il faut bien se rendre compte que l'ensemble des calculs du fragment program sont effectué pour chaque pixel, même si le pixel est finalement noir. On obtient donc un fragment program relativement coûteux en terme de fill rate (1) . On peut réduire ce coût en ne renormalisant pas la normal map et en effectuant les calculs de normalisation des vecteurs vers la lumière et la camera au niveau du vertex program, mais c'est au dépend de la qualité, et on retombe sur les problèmes d'interpolation si on utilise pas de cube map de normalisation comme c'est le cas ici.

II-4. Le rendu final.

Maintenant que nous avons toutes les informations nécessaires, nous pouvons voir comment effectuer le rendu final de la scène. Le code nécessaire est assez semblable à celui du tutoriel précédent, mis a part qu'on a plus à activer l'extension env_dot3 pour l'unité de texture de la normal map, qu'on doit activer par contre les vertex/fragment programs et qu'on n'a pas à effectuer de deuxième passe pour avoir des lumières colorées. On ajoute aussi l'utilisation de la height map et de la gloss map, ce qui fait que nous utilisons maintenant 5 unités de textures pour notre rendu (et nous n'utilisons plus de cube map de normalisation). Le code de rendu de la scène donne donc :

Code de rendu de notre scène.
Sélectionnez

// on configure la premiere unite de texture :
// elle contient la texture de la scene
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, textureId);
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);

// on effectue le rendu de la lumiere ambiante
model->initLighting(ambiant);

// on doit reconfigurer les unites de textures
// la première unite de texture contient la normal map
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, normalMapId);

// la deuxieme contient la texture de diffuse
glActiveTextureARB(GL_TEXTURE1_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,textureId );

// la troisieme contient la height map
glActiveTextureARB(GL_TEXTURE2_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,heightMapId );

// la quatrieme contient la gloss map
glActiveTextureARB(GL_TEXTURE3_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,glossMapId );

// la cinquieme contient la texture d'attenuation
glActiveTextureARB(GL_TEXTURE4_ARB);
glEnable(GL_TEXTURE_3D_EXT);
glBindTexture(GL_TEXTURE_3D_EXT,lightmap );

// on doit activer le blending additif pour pouvoir utiliser
// ce mode d'eclairage
glEnable(GL_BLEND);
glBlendFunc(GL_ONE,GL_ONE);
glDepthMask(GL_FALSE);

// on active les vertex/fragment program
glEnable(GL_VERTEX_PROGRAM_ARB);
glBindProgramARB(GL_VERTEX_PROGRAM_ARB,vertexProgramId);
glEnable(GL_FRAGMENT_PROGRAM_ARB);
glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB,fragmentProgramId);

// on ajoute nos lumieres
model->addLightCubeMap(light1);
model->addLightCubeMap(light2);

// on desactive les vertex/fragment program
glDisable(GL_VERTEX_PROGRAM_ARB);
glDisable(GL_FRAGMENT_PROGRAM_ARB);

// on desactive la cinquieme unite de texture
glActiveTextureARB(GL_TEXTURE4_ARB);
glDisable(GL_TEXTURE_3D_EXT);
// on desactive la quatrieme unite de texture
glActiveTextureARB(GL_TEXTURE3_ARB);
glDisable(GL_TEXTURE_2D);
// on desactive la troisieme unite de texture
glActiveTextureARB(GL_TEXTURE2_ARB);
glDisable(GL_TEXTURE_2D);
// on desactive la seconde unite de texture
glActiveTextureARB(GL_TEXTURE1_ARB);
glDisable(GL_TEXTURE_2D);
// on desactive la premiere unite de texture
glActiveTextureARB(GL_TEXTURE0_ARB);
glDisable(GL_TEXTURE_CUBE_MAP_ARB);

glDepthMask(GL_TRUE);
glDisable(GL_BLEND);

Et voila le résultat final en image.

Image non disponible
Le rendu final de la scène.

III. Réflexions sur la gestion de la lumière.

Nous avons vu au cours de cette série de tutoriaux comment gérer la lumière de plusieurs façons différentes. Cela va de la plus simple, avec l'éclairage par vertex, à des plus compliquée comme nous venons de le voir. Comme je le disais dans la conclusion du tutoriel précédent, l'utilisation d'une méthode d'éclairage ou d'une autre influe non seulement sur les performances du moteur 3D, mais aussi sur la quantité de ressources à fournir pour les artistes. En effet, avec un système complexe, le créateur de textures aura à crée :

  • Une texture de diffuse standard. Elle correspond à la texture généralement crée pour un système d'éclairage simple. Ce n'est donc pas un surcoût ici.
  • Une height map utilisée pour gérer l'effet de parallax mapping.
  • Une normal map. Bien que généralement crée à partir de la height map, on peut parfois souhaiter avoir une normal map différente. C'est sans doute la texture la plus importante d'un material. Une normal map de mauvaise qualité, et l'ensemble du système de gestion de la lumière en subit les conséquences.
  • Une gloss map qui permet de nuancer l'effet de l'éclairage spéculaire. C'est une des textures les plus difficiles à régler. Une gloss map qui atténue trop l'éclairage spéculaire, l'effet perd de son intérêt, au contraire, si la gloss map ne l'atténue pas assez, on se retrouve avec des surface qui ressembles à du plastique ou du métal trop brillant.
  • Une texture émissive. Bien que je ne l'aie pas traitée dans ce tutoriel, on peut souhaiter avoir certaines parties de notre texture toujours éclairée. Par exemple un bouton lumineux ou un écran d'ordinateur émettent leur propre lumière. Pour cela on utilise une texture émissive qui est affichée durant la première passe de l'algorithme de rendu de la lumière (le rendu de la lumière ambiante).

Nous avons donc ici de nombreuses textures à créer pour nos artistes, ce qui peut considérablement alourdir le processus de création d'un jeu vidéo pour une équipe d'amateurs.

Un autre point important que je n'est pas traité ici est l'optimisation du rendu des lumières. Nos lumières étant des lumières sphériques ponctuelles, on peut effectuer de nombreuses optimisations avant de les afficher. En dehors des algorithmes de partitionnement (BSP, octree, portals), la première optimisation qui vient à l'esprit est de vérifier si la face est bien orientée vers la lumière avant de la rendre. En effet, il est inutile de dessiner une face si on sait que l'ensemble de ses vertex tournent le dos à la lumière. Si ceci peut être coûteux pour l'éclairage par vertex (le coût de vérification par face sera supérieur au gain de vitesse), cette optimisation peut être très intéressant pour les systèmes plus coûteux en terme de fill rate, c'est-à-dire, quand le dessin d'un pixel est lent (ce qui est notre cas dans ce tutoriel). On peut aussi exclure de l'affichage toutes les faces qui sont trop éloignées de la lumière pour être éclairées (ceci se fait simplement à l'aide d'une équation de plan par polygone).

Une autre optimisation simple à mettre en oeuvre consiste à utiliser un rectangle de clipping autour de la lumière. Cette optimisation n'est utilisable que pour les rendus de lumières en multi passe, elle n'est donc pas applicable à l'éclairage par vertex. Elle consiste tout simplement à utiliser la possibilité des cartes graphiques à utiliser un rectangle de clipping pour réduire la zone de dessin autorisée. Si, en calculant les extremum de notre lumière, on en déduit un rectangle de clipping qui englobe entièrement notre lumière, on peut ainsi réduire l'affichage des grand polygones touchés par la lumières, et ainsi sauvegarder du fill rate encore une fois.
Un avantage de cette optimisation est qu'elle est aussi très utile pour optimiser le rendu des ombres utilisant l'algorithme des shadow volumes.

Comme je l'ai dit dans le premier tutoriel, je n'ai traité que des lumières sphériques ponctuelles directes. On peut imaginer des systèmes d'éclairage plus complexes gérant les spots de lumières via des projections de textures, et des lumières directionnelles infinies. De même, on peut espérer voir arriver dans l'industrie d'ici quelques temps l'utilisation de l'illumination globale qui permet de gérer les sources de lumières indirectes. Ceci est encore à l'heure actuelle un sujet de recherche important, mais les premiers résultats sont déjà impressionnants.

Un autre point important qui n'est pas traité dans cette série est la gestion de l'occlusion de la lumière, via des algorithmes de génération d'ombres portées. En effet, dans ces tutoriaux, la lumière traverse les objets pour aller éclairer les autres objets. Ceci est embêtant pour le réalisme de notre scène. Il peut donc être intéressant d'avoir des ombres portées sur les autres objets, et pour cela utiliser des techniques comme les shadow volumes ou les shadow buffers (aussi appelée shadow maps).

IV. Conclusions.

Ca y est, c'est fini. Si nous avons vu au cours de cette série de tutoriaux comment gérer la lumière de plusieurs manières, il ne faut pas oublier que le principal argument pour le faire dans un moteur 3D amateur est bien de pouvoir dépasser la limite du nombre maximum de lumières hardware supportées par les cartes graphiques. Il est donc conseillé de choisir la méthode la plus adaptée au moteurs que vous souhaitez réaliser. Il n'est pas forcement utile de mettre en place un système d'éclairage par pixel pour un moteur simple ou pour un jeu de stratégie par exemple, alors qu'une bonne gestion de l'éclairage pourra être très importante dans un FPS. Le principal est que maintenant vous avez le choix de la technique à utiliser dans votre moteur.

VI. Liens

Vous pouvez télécharger les sources de ce tutoriel ici [http]

Image non disponible Tutoriel 5 : éclairage par pixel standard. Retour au menu. Image non disponible
(1)
Le fill rate désigne le taux de remplissage de l'écran. Une méthode consommatrice en fill rate est une méthode qui va soit remplir fortement l'écran (c'est par exemple le cas des shadow volumes), ou bien dont le dessin des pixels est très consommateur en ressources comme c'est le cas ici.

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2006 Michel de VERDELHAN. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -

Developpez.com


ASM Mode Reel

Les tutoriels Nehe Vu dans ... http://nehe.developpez.com/

Image non disponible

Table des matières Plier Déplier

  • Tutoriels 1-10
    • I. Les tutoriels
      • I-1. Introduction
      • I-2. Premier triangle et premier quadrilatère
      • I-3. Coloration
      • I-4. Rotations
      • I-5. Première pyramide et premier cube
      • I-6. Les textures
      • I-7. Les filtres et l'éclairage
      • I-8. Transparences
      • I-9. Déplacer des bitmaps dans un monde 3D
      • I-10. Chargement et déplacement dans un monde 3D
  • Tutoriels 11-20
    • II. Les tutoriels
      • II-1. Effet de drapeau
      • II-2. Liste d'affichage
      • II-3. Les polices Bitmap
      • II-4. Les polices vectorielles
      • II-5. Application de texture sur les polices vectorielles
      • II-6. Effet de brouillard réaliste
      • II-7. Texture 2D de police
      • II-8. Quadriques
      • II-9. Moteur de particules utilisant les chaînes de triangles
      • II-10. Masquage
  • Les autres tutoriels
    • III. Contributions
      • II.1. Remerciements

Les tutoriels Nehe sont la base et la référence de la programmation OpenGL. Nous proposons ici une traduction française de ces tutoriels afin de faciliter leur lecture. Sur cette page, nous proposons les premiers tutoriels de cette série. Les 20 premiers articles sont traduits et les derniers attendent des traducteurs....

L'auteur

Jeff Molofee ( NeHe )

L'article

Publié le 23 mars 2007 - Mis à jour le 19 décembre 2012

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2007 Nehe Gamedev.net. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


NeHe Productions - Everything OpenGL
Nehe
HOME TWITTER FACEBOOK RSS ATOM FORUM
HOW TO INITIALIZE WEBGL
by gregsidelnikov, posted at Oct. 6, 2015, 7 p.m.

So, we've all been fiddling with OpenGL in a desktop environment. And some of us have probably already heard of WebGL, but never got a chance to work with it.

This led me to record How to Initialize WebGL in 11 Minutes. After watching this tutorial, you would have your basic blank "3D canvas" box cleared to black background.

Hope this helps someone out getting ready to transition to WebGL.

CONTINUE READING
0 Comments

FALLOUT SOFTWARE TUTORIALS UPDATED!
by kazade, posted at July 9, 2014, 8:25 p.m.

Greg Sidelnikov of FalloutSoftware.com has applied updates to his OpenGL tutorials. An entirely new article Normal Mapping Tutorial has been added providing several insights on what the pinkish/bluish colors actually represent. The difference between Immediate Mode (glBegin, glEnd) and the modern Vertex Buffer Object technique has been added to the Drawing OpenGL Primitives via VBO tutorial. Better diagrams have been improved in OpenGL Light tutorial. Greg is also planning to start adding GLSL fragment and vertex shader tutorials to the site soon. If this piques your interest you can subscribe to the tutorial newsletter on the site. Greg has been hosting OpenGL tutorials since 2003.

CONTINUE READING
0 Comments

WATCH HOW VALVE AND NVIDIA PORTED SOURCE TO LINUX (AND GL!)
by gamedev, posted at April 9, 2013, 2:19 p.m.

There is a really interesting video from the recent GDC 2013 conference that goes into detail about how Valve and Nvidia ported the Source engine to the Linux platform. The majority of the talk focuses on the port from D3D to OpenGL. There is nothing but glowing praise for OpenGL throughout the talk. Definitely worth checking out!

CONTINUE READING
0 Comments

MESA 9.0 RELEASED WITH OPENGL 3.1 SUPPORT
by kazade, posted at Oct. 9, 2012, 9:54 a.m.

Mesa, the Open Source OpenGL implementation has released a new major version which includes support for OpenGL 3.1. The release announcement can be found here.

Although Mesa works on multiple platforms, including Windows, it's on open source operating systems like Linux where it is most important. On Linux, Mesa provides hardware accelerated OpenGL when running open-source graphics drivers. This means that if your graphics card is supported, you will get hardware-accelerated OpenGL 3.1 out-of-the-box on operating systems such as Ubuntu and Debian, even when running from a Live CD or bootable USB stick.

CONTINUE READING
1 Comments

NEW OPENGL TUTORIAL SITE
by kazade, posted at Oct. 8, 2012, 2:12 p.m.

Damien wrote in to tell us about his OpenGL tutorial website which focuses on OpenGL 3.3. Apparently this site was referenced during SIGGRAPH and in various books. It's definitely worth taking a look! You can find it here.

CONTINUE READING
5 Comments

KHRONOS RELEASES OPENGL 4.3 AND OPENGL ES 3.0
by kazade, posted at Aug. 6, 2012, 3:03 p.m.

As is the tradition at SIGGRAPH, Khronos just announced the OpenGL 4.3 specification alongside its mobile-focused counter-part OpenGL ES 3.0. One of the more interesting announcements is the inclusion of a new royalty-free texture compression extension called ASTC.

As usual you can find the latest specifications on the registry.

The release announcements for the day can be found on the Khronos website.

CONTINUE READING
0 Comments

KAZMATH MOVED TO GITHUB, LOADS OF FIXES
by kazade, posted at April 27, 2012, 7:15 a.m.

This is just a heads up to anyone using my 3D math library "kazmath". Kazmath was originally written for Beginning OpenGL Game Programming II but has been continually improved and updated since then. It's now a fairly large library being used by quite a few people. I've recently moved development of kazmath completely from Launchpad over to GitHub. You can find the repository here: https://github.com/Kazade/kazmath

Kazmath is BSD licensed so free to use in both open and closed projects. Feel free to report bugs, and if you improve the library, just send me a pull request on GitHub :)

CONTINUE READING
1 Comments

0 A.D HITS ALPHA 9
by kazade, posted at March 18, 2012, 7:50 a.m.

Open source, and OpenGL powered strategy game 0 A.D has released a new version. Alpha 9 brings a new Roman Republic faction, a new combat system, a new trading system, improved AI and new animations.

If you are a fan of historical RTS's then you definitely should check this out. 0 A.D is free to download for Linux, Mac and Windows from the WildFire Games website

If you are interested in helping out with the project, you can find details on the Get Involved page.

CONTINUE READING
0 Comments

GOOD OPENGL ES RESOURCE FOR ANDROID!
by carstenhaubold, posted at March 4, 2012, 9:23 a.m.

Kevin sent us an email about his site teaching OpenGL ES 2 for Android which looks really cool. If you're an Android developer or plan to be, be sure to have a look!

http://www.learnopengles.com/

CONTINUE READING
2 Comments

COMPILE PROBLEMS IN WIN7 WITH VISUAL STUDIO 2010
by carstenhaubold, posted at Feb. 28, 2012, 7:33 a.m.

Encaitar wrote us an email about compile problems of the old lessons under Windows 7 due to the choice of a character set. As this might interest quite a lot of you, I just show you what he wrote:

The issue is the default in VC10 char* to LPCWSTR conversion. The gamedev forums do mention the issue. This code can be added as one of the many conversions at the bottom of the tutorial page or the solution of changing the properties in Visual Studio could be included in the text. StackOverFlow (http://stackoverflow.com/questions/3924926/cannot-convert-parameter-1-from-char-to-lpcwstr) has a question about this issue with a nice screen shot of the option to be changed.
As the same issue is present across tutorials I would suggest explaining to a Visual Studio user the source of the problem and the Character Set = Not Set option listed above.
Thank you for that hint!
So watch out for this specific error whenever you compile one of the legacy tutorials, and I'll add a note about this to the lessons!

CONTINUE READING
0 Comments

Older Posts

Community

  • NeHe Forums
  • OpenGL Forums

Desktop Tutorials

  • Prep: The Graphics Pipeline

Mobile Tutorials

  • iOS Lesson 01 - Setting up GL ES
  • iOS Lesson 02 - First Triangle

Legacy Tutorials

  • Lessons 01 - 05
  • Lessons 06 - 10
  • Lessons 11 - 15
  • Lessons 16 - 20
  • Lessons 21 - 25
  • Lessons 26 - 30
  • Lessons 31 - 35
  • Lessons 36 - 40
  • Lessons 41 - 45
  • Lessons 46 - 48

Articles

  • None available

Legacy Articles

  • Contest: Halloween 2000
  • Contest: Christmas 2000
  • Contest: Apocalypse 2001
  • Contest: The Fellowship Of The Ring 2002
  • Contest: Halloween 2002
  • Contest: Mini-Demo 2003
  • Contest: The Two Towers 2003
  • Contest: Creative Labs Europe
  • Contest: Winamp GL 2005
  • Old NeHe Projects
  • Articles 01 - 05
  • Articles 06 - 10
  • Articles 11 - 15

Beginning OpenGL Game Programming II

  • SimpleGLXWindow Class
  • Latest Source Files

Useful Resources

  • How To Initialize WebGL
  • Learn OpenGL ES - for Android

© 1997-2012 Gamedev.net. All rights reserved.
NeHe™ and NeHe Productions™ are trademarks of GameDev.net, LLC
OpenGL® is a registered trademark of Silicon Graphics Inc.


ASM Mode Reel

Table des matières

  • I. Les tutoriels
    • I-1. Introduction
    • I-2. Premier triangle et premier quadrilatère
    • I-3. Coloration
    • I-4. Rotations
    • I-5. Première pyramide et premier cube
    • I-6. Les textures
    • I-7. Les filtres et l'éclairage
    • I-8. Transparences
    • I-9. Déplacer des bitmaps dans un monde 3D
    • I-10. Chargement et déplacement dans un monde 3D

I. Les tutoriels

I-1. Introduction

L'ouverture d'une fenêtre
L'ouverture d'une fenêtre

Dans ce tutoriel, je vais vous apprendre à mettre en place et utiliser OpenGL dans un environnement Windows. Le programme que vous allez créer dans ce tutoriel va ouvrir une fenêtre vide OpenGL, passer de plein écran à un mode fenêtré et attendre que l'utilisateur appuye sur la touche Echap ou sur la fermeture de la fenêtre. Si cela ne semble pas être grand chose, ce programme sera le point de départ de tous les autres tutoriels.

C'est important de savoir comment OpenGL fonctionne, quelles sont les étapes dans la création d'une fenêtre OpenGL et comment écrire un code facile à la compréhension. Vous pouvez télécharger le code source à la fin de ce tutoriel mais je vous recommande de le lire au moins une fois avant de commencer la programmation OpenGL.

Voici le lien vers ce tutoriel : Introduction.

I-2. Premier triangle et premier quadrilatère

Premier triangle et premier quadrilatère
Premier triangle et premier quadrilatère

Utilisant le code du premier tutoriel, nous allons ajouter du code pour créer un triangle et un carré dans la fenêtre. Ce n'est pas encore un moteur 3D mais toute création OpenGL passe par ces deux primitives. La compréhension du code de rendu de ces deux formes géométriques en trois dimensions est donc primordiale. Suivez donc ce tutoriel avec intérêt !

Nous verrons comment fonctionnent les trois axes X, Y et Z, mais aussi comment faire des translations en 3D. A la fin, vous devrez être capable de placer un objet où vous le voulez.

Voici le lien vers ce tutoriel : Premier triangle et premier quadrilatère.

I-3. Coloration

Coloration

En continuant le tutoriel précédent, nous allons voir comment ajouter de la couleur aux polygones. Nous verrons deux sortes de colorations. La coloration unie et le mélange de couleur. Si on regarde l'image, vous pouvez voir que le triangle mélange les couleurs par rapport au carré qui possède une couleur unie. Ajouter de la couleur permet vraiment de rendre les polygones plus intéressants.

Voici le lien vers ce tutoriel : Coloration.

I-4. Rotations

Rotations

Ce quatrième tutoriel va présenter les rotations en 3D. Nous allons faire pivoter un triangle selon l'axe des Y et un quadrilatère selon l'axe des X. C'est facile de dessiner des polygones basiques à l'écran mais les faire bouger rend tout le programme plus intéressant.

Voici le lien vers ce tutoriel : Rotations.

I-5. Première pyramide et premier cube

Première pyramide et premier cube
Première pyramide et premier cube

Maintenant que nous savons initialiser une fenêtre, dessiner des triangles et des carrés, les faire tourner sur eux-mêmes, il est temps de les transformer en objets 3D.

Nous allons donc transformer le triangle en pyramide et le carré en cube. Ce dernier aura une couleur unie pour chaque face et la pyramide utilisera un mélange de couleurs.

Voici le lien vers ce tutoriel : Première pyramide et premier cube.

I-6. Les textures

Les textures

Dans ce tutoriel, nous présentons comment utiliser des textures pour rendre les polygones nettement plus intéressants. Nous allons reprendre le code du premier tutoriel puisqu'il est plus facile de partir d'un code qui ne fait qu'ouvrir une fenêtre vide qu'un code qui fait tourner différents objets 3D.

Voici le lien vers ce tutoriel : Les textures.

I-7. Les filtres et l'éclairage

Les filtres et l'éclairage
Les filtres et l'éclairage

Ce tutoriel est assez important et donc assez long ! Si vous ne vous sentez pas à l'aise avec les six premiers, relisez les avant de continuer ! Nous allons voir comment utiliser des filtres sur les textures, comment utiliser le clavier et mettre en place des lumières !

Voici le lien vers ce tutoriel : Les filtres et l'éclairage.

I-8. Transparences

La transparence

Ce tutoriel continue le précédent en ajoutant une autre partie importante d'un code OpenGL : la transparence. Cela nous permet de dessiner des vitres, de l'eau et bien d'autres éléments.

Voici le lien vers ce tutoriel : Transparence.

I-9. Déplacer des bitmaps dans un monde 3D

Déplacer des bitmaps dans un monde 3D
Déplacer des bitmaps dans un monde 3D

Une fois que nous savons afficher un cube transparent pivotant, il serait intéressant de déplacer des images BMP dans un univers 3D en enlevant des parties des images qui seront considérées comme transparentes. Ce tutoriel fait cela en reprenant toutes les idées des tutoriels précédents. Soyez sûr de tout comprendre avant de continuer car le niveau de celui-ci est un peu plus élevé.

Voici le lien vers ce tutoriel : Déplacer des bitmaps dans un monde 3D.

I-10. Chargement et déplacement dans un monde 3D

Chargement et déplacement dans un monde 3D
Chargement et déplacement dans un monde 3D

Voici un tutoriel qui présente un code intéressant : charger un fichier de données et représenter un petit monde en trois dimensions. De plus, il permet de se déplacer dans ce monde avec une idée intéressante pour faire croire qu'on se balade vraiment à l'intérieur, je vous laisse découvrir.

Voici le lien vers ce tutoriel : Chargement et déplacement dans un monde 3D.


Introduction

Introduction

  • I. Contributions
  • II. Tutoriel
    • II-A. Introduction
    • II-B. Inclusions et variables globales
    • II-C. La fonction ReSizeGLScene
    • II-D. La fonction InitGLScene
    • II-E. La fonction DrawGLScene
    • II-F. La fonction KillGLScene
    • II-G. La fonction CreateGLWindow
    • II-H. La fonction WndProc
    • II-I. La fonction WinMain
    • II-J. Conclusion
  • III. Téléchargements
  • IV. Remerciements
  • V. Liens

Bienvenue aux tutoriels OpenGL. Je suis un mec ordinaire avec une passion pour OpenGL! La première fois que j'ai entendu parlé d'OpenGL était lorsque 3Dfx avait fourni leur pilote OpenGL pour la Voodoo 1. J'ai tout de suite su que je devais apprendre à programmer en OpenGL. Malheureusement, c'était très difficile de trouver des informations à propos d'OpenGL dans les livres ou sur internet. J'ai passé des heures tentant de faire fonctionner du code et encore plus de temps à demander de l'aide par mail et par IRC. J'ai découvert que les personnes qui comprenaient les entrailles d'OpenGL se considéraient comme des élites et n'avaient aucune volonté de partager leur connaissance. C'était très frustrant!

II. Tutoriel

J'ai créé cette série de tutoriels pour que les personnes intéressées dans l'apprentissage de l'OpenGL aient quelque chose à se mettre sous la dent. Dans chacun de mes tutoriels, je tente d'expliquer, avec autant de détails que possible, ce que chaque ligne de code fait. Je tente de garder le code simple (pas de code MFC à apprendre). Même un vrai débutant en C++ et en OpenGL devrait pouvoir regarder le code présenté et avoir une idée assez précise de ce qui s'y passe. Si vous êtes déjà un bon programmeur OpenGL, ces tutoriels n'auront probablement pas grande chose à vous apprendre mais, si vous débutez, alors je pense que ces tutoriels auront beaucoup à offrir.

Ce tutoriel a été entièrement réécrit en Janvier 2000. Ce tutoriel va vous apprendre à mettre en place une fenêtre OpenGL. La fenêtre peut être mise en plein écran ou prendre la taille, la résolution et le format pixel que vous souhaitez. Le code est très flexible et peut être utilisé pour tous vos projets OpenGL. Tous les miens sont basés sur celui-ci ! J'ai écris ce code pour qu'il soit flexible et puissant à la fois. Toutes les erreurs ont été, en principe, corrigées. Il ne devrait pas avoir de fuites de mémoire et le code est facile à lire et à modifier. Merci à Fredric Echols pour ses modifications du code.

II-A. Introduction

Je vais commencer ce tutoriel en montrant directement le code. La première chose que vous devez faire est créer un projet sous Visual C++. Si vous ne savez pas comment faire cela, vous ne devriez pas tenter d'apprendre OpenGL mais plutôt le fonctionnement de Visual C++ (ou votre EDI préféré). Certaines versions de VC++ ont besoin que le type bool soit changé en BOOL, true doit être changé en TRUE et false doit être changé en FALSE. En faisant cela, j'ai même réussi à compiler le code sous Visual C++ 4.0 et 5.0 sans aucun problème.

Après avoir créé une nouvelle application Win32 - pas une application console - dans Visual C++, you devrez lier les bibliothèques OpenGL. Dans Visual C++, allez à Projet->Propriétés->Propriétés de configuration->Editeur des liens. Dans Entrées/Bibliothèques, ajoutez OpenGL32.lib GLu32.lib and GLaux.lib. Une fois effectué, appuyez sur OK. Vous pouvez maintenant écrire un programme Windows OpenGL.

Beaucoup de compilateurs ne définissent pas CDS_FULLSCREEN. Si vous recevez un message d'erreur qui parle de CDS_FULLSCREEN, vous devez ajouter cette ligne de code au début de votre programme : #define CDS_FULLSCREEN 4.

Lorsque ces premiers tutoriels ont été écrits, GLAUX était très utilisé. Mais, au fil du temps, GLAUX n'a plus été maintenu par les développeurs. Beaucoup de ces tutoriels utilisent encore GLAUX. Si votre compilateur ne supporte pas GLAUX ou que vous ne voulez pas l'utiliser, télécharger le code de remplacement à partir de la page d'accueil http://nehe.gamedev.net.

II-B. Inclusions et variables globales

Les premières 4 lignes incluent les fichiers d'en-tête pour chaque bibliothèque dont on aura besoin. Voici ces lignes :

Inclusions
Sélectionnez
#include <windows.h>  // Fichier d'entete pour Windows
#include <gl\gl.h>    // Fichier d'entete pour OpenGL32
#include <gl\glu.h>   // Fichier d'entete pour GLu32
#include <gl\glaux.h> // Fichier d'entete pour GLaux

Ensuite, vous aurez besoin de mettre en place toutes les variables que vous allez utiliser dans votre programme. Ce programme va créer une fenêtre OpenGL vide, donc nous n'aurons pas encore besoin de beaucoup de variables. Les quelques variables que nous allons utiliser ici sont très importantes et vous allez les utiliser dans presque tous les programmes OpenGL que vous écrirez.

La première ligne met en place un contexte de rendu. Tout programme OpenGL est lié à un tel contexte. Un contexte de rendu (RC) permet de lier les appels OpenGL au contexte de fenêtre. Le contexte de rendu OpenGL est défini comme étant hRC. Pour que votre programme puisse dessiner dans une fenêtre, vous devrez créer un contexte de fenêtre, ceci est fait dans la seconde ligne. Le contexte de fenêtre (DC pour Device Context) est défini comme étant hDC. Le DC connecte la fenêtre vers le GDI (L'interface graphique matériel). Le RC connecte OpenGL vers le DC.

Dans la troisième ligne, la variable hWnd va contenir l'identifiant assigné à notre fenêtre par le système d'exploitation Windows. Finalement, la quatrième ligne crée une instance pour notre programme.

Variables globales
Sélectionnez
HGLRC hRC=NULL;          // Contexte de rendu permanent
HDC hDC=NULL;         // Contexte GDI
HWND hWnd=NULL;          // Contient un identifiant de fenêtre
HINSTANCE hInstance;  // Contient une instance de l'application

La première ligne de la section du code qui suit met en place un tableau que nous allons utiliser pour surveiller l'état des touches de clavier. Il y a beaucoup de solutions pour gérer le clavier, mais c'est cette solution que j'utilise. C'est fiable et cela peut gérer l'appui de plusieurs touches en même temps.

La variable active sera utilisée pour savoir si notre fenêtre a été minimisée. Si la fenêtre a été minimisée, nous pouvons, par exemple, suspendre l'exécution du programme ou même quitter le programme. Je préfère suspendre le programme pour qu'il n'utilise pas les ressources processeurs lorsqu'il est minimisé.

La variable fullscreen est assez explicite. Si le programme est en plein écran, la variable vaudra TRUE, sinon le programme est en mode fenêtre et donc fullscreen vaudra FALSE. C'est important de définir cette variable en global pour que chaque fonction puisse savoir si le programme est en plein écran.

Autres variables globales
Sélectionnez
bool keys[256];           // Tableau utilisé pour la gestion du clavier
bool active=TRUE;      // Fenêtre active mis à TRUE par défaut
bool fullscreen=TRUE;  // Variable pour le plein écran mis à TRUE par défaut

Nous allons maintenant déclarer la fonction WndProc. Nous définissons ce prototype parce que la fonction CreateGLWindow fait un appel à WndProc mais elle est déclarée avant. En C, si nous voulons faire un appel à une fonction qui est définie après, nous devons définir le prototype au début du programme. Donc, dans le code qui suit, on définit le prototype de WndProc.

Prototype de WndProc
Sélectionnez
LRESULT    CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);  // Declaration de WndProc

II-C. La fonction ReSizeGLScene

Le travail de la prochaine section de code est de remettre la scène OpenGL lorsque la fenêtre est redimensionnée (en supposant que vous êtes en mode fenêtre et non plein écran). Même si vous ne pouvez pas redimensionner la fenêtre (par exemple, si le programme est en plein écran), cette fonction sera tout de même appelée lorsque le programme commence pour mettre en place la perspective. La scène OpenGL sera redimensionnée par rapport à la taille de la fenêtre qui la contient.

Fonction ReSizeGLScene
Sélectionnez
/* Redimensionne et initialise la fenêtre GL */
GLvoid ReSizeGLScene(GLsizei width, GLsizei height)
{
    if(height==0)   /* Evite une division par zero */
    {
        height=1;    /* Transforme la hauteur à 1 */
    }
    glViewport(0, 0, width, height);   // Remet le Viewport à jour

Les lignes qui initialisent la perspective de la fenêtre. Ceci veut dire que plus les choses sont loin, plus elles deviendront petites. Ceci crée une scène plus réaliste. La perspective est calculée avec un angle de 45 degrés basé sur la largeur et la hauteur de la fenêtre. Le 0.1f et 100.0f sont la profondeur la plus proche et la plus lointaine qu'on peut dessiner dans la scène.

glMatrixMode(GL_PROJECTION) indique que le code qui suit va affecter la matrice de projection. Cette matrice est responsable de la gestion de la perspective de la scène. glLoadIdentity() est similaire à une remise à zéro. Cet appel remet la matrice à son état de départ. Après glLoadIdentity() a été appelé, on met la perspective de la scène en place. glMatrixMode(GL_MODELVIEW) indique que les prochaines transformations vont affecter la matrice de modélisation. Cette matrice est l'endroit où on met l'information pour les objets. Enfin, on remet la matrice de modélisation à zéro. Ne vous inquiétez pas si vous ne comprenez pas tout cela, ce sera de nouveau expliqué dans les prochains tutoriels. Sachez seulement que ce bout de code doit être fait si vous voulez avoir une jolie perspective.

Mise en place de la perspective
Sélectionnez
    glMatrixMode(GL_PROJECTION);    // Choisir la matrice de projection
    glLoadIdentity();                // Remettre a zero de la matrice de projection

    // Calculer le ratio pour la perspective de la fenêtre
    gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);
    glMatrixMode(GL_MODELVIEW);     // Choisir la matrice de modélisation
    glLoadIdentity();               // Remettre a zero la matrice de modélisation
}

II-D. La fonction InitGLScene

Dans la prochaine section de code, nous initialisons OpenGL. Nous définissons la couleur qui sera utilisée pour vider la scène, nous mettons en place un tampon pour la profondeur, mettre en place un joli lissage, etc. Cette fonction ne sera pas appelée tant que la fenêtre OpenGL n'a pas été créée. Cette fonction retourne un entier mais, puisque ce code ne risque pas encore grand chose, nous n'allons pas nous en soucier.

Fonction d'initialisation
Sélectionnez
int InitGL(GLvoid)  // Toute l'initialisation de OpenGL se trouve ici
{

La prochaine ligne permet d'avoir un joli rendu. Ce rendu permet d'avoir un bon dégradé. J'expliquerai comment obtenir un bon dégradé dans un autre tutoriel.

Utilisation de glShadeModel
Sélectionnez
    glShadeModel(GL_SMOOTH);  // Permet un joli ombrage

La ligne qui suit permet de choisir la couleur de fond lorsqu'on l'efface. Si vous ne savez pas comment fonctionne le système de couleur, je vais l'expliquer rapidement. L'intervalle des couleurs va de 0.0f à 1.0f. 0.0f étant le plus foncé et 1.0f étant le plus clair. Le premier paramètre après glClearColor est l'intensité de la couleur rouge, le second est l'intensité de la couleur verte et le troisième est l'intensité pour la couleur bleue. Si la valeur est proche de 1.0f, la couleur sera plus intense pour cette couleur. Le dernier nombre est la valeur Alpha. Lorsque nous nous occupons d'effacer l'écran, nous ne nous intéressons pas de cette dernière valeur. Laissons cette valeur à 0.0f. J'expliquerai son utilité dans un autre tutoriel.

On crée différentes couleurs en mélangeant les trois couleurs primaires (rouge, vert, bleu). Donc, si vous appelez glClearColor(0.0f,0.0f,1.0f,0.0f);, vous allez effacer l'écran avec une couleur bleue claire. Si vous appelez glClearColor(0.5f,0.0f,0.0f,0.0f), vous effacerez l'écran vers un rouge moyen. Pour avoir un fond blanc, il suffit de mettre toutes les couleurs à 1.0f. Pour avoir un fond noir, les trois couleurs doivent être mises à 0.0f.

Couleur de fond
Sélectionnez
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);  // Fond noir

Les trois prochaines lignes font intervenir le tampon de profondeur. Considérez le tampon de profondeur comme étant des niveaux de couches sur l'écran. Ce tampon garde les profondeurs des objets qui sont affichés. Nous n'allons pas utiliser le tampon de profondeur dans ce programme, mais presque chaque programme OpenGL qui fait un rendu 3D utilise le tampon de profondeur. Ce tampon permet de dessiner un objet seulement s'il est devant un autre objet. Ce tampon est très important pour le rendu OpenGL.

Mise en place du test de profondeur
Sélectionnez
    glClearDepth(1.0f);       // Mis en place du tampon de profondeur
    glEnable(GL_DEPTH_TEST);  // Mis en place du test de profondeur
    glDepthFunc(GL_LEQUAL);      // Le type de test de profondeur

Ensuite, on explique à OpenGL que nous voulons la meilleure perspective qui peut être utilisé. Ceci provoque un coût de performance, mais nous allons améliorer un peu mieux la vue en perspective.

Mis en place des calculs de perspective
Sélectionnez
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);  // Très jolis calculs de perspective

Finalement on retourne TRUE. Si nous voulons savoir si l'initialisation a eu des problèmes, on pourra tester le retour de cette fonction. Vous pourrez ajouter votre code et retourner FALSE si une erreur est survenue. Pour le moment, nous n'avons pas besoin de nous y intéresser.

La fin de la fonction
Sélectionnez
    return TRUE;  // L'initialisation s'est bien passé
}

II-E. La fonction DrawGLScene

C'est dans cette section que nous allons mettre tout le code de rendu. Tout ce que vous voulez afficher dans la fenêtre ira dans cette section de code. Chaque tutoriel qui va suivre celui-ci va ajouter du code dans cette section du programme. Si vous avez déjà une compréhension d'OpenGL, vous pouvez tenter de créer des formes de base après l'appel glLoadIdentity() et avant de retourner TRUE. Si vous êtes un(e) débutant(e) en programmation OpenGL, regardez les tutoriels qui vont suivre. Pour le moment, tout ce que nous allons faire, c'est mettre la couleur de base en place, réinitialiser le tampon de profondeur et réinitialiser la scène. Nous ne dessinerons rien pour le moment.

Le retour de TRUE dit à notre programme qu'il n'y pas eu de problèmes. Si vous voulez que le programme s'arrête pour une raison ou une autre, ajouter un return FALSE dans le code pour dire au programme que le code de rendu a échoué.

La fonction DrawGLScene
Sélectionnez
int DrawGLScene(GLvoid)  // C'est ici qu'on fait tout le dessin
{
  // Effacer l'ecran et le tampon de profondeur
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glLoadIdentity();  // Remettre à zéro la matrice de modélisation
  return TRUE;       // Tout s'est bien passé
}

II-F. La fonction KillGLScene

La prochaine section de code est appelée juste avant que le programme s'achève. Le travail de la fonction KillGLWindow() est de relâcher le contexte de rendu, le contexte de fenêtre et l'identifiant de fenêtre. On a ajouté beaucoup de vérifications d'erreurs. Si le programme est incapable de détruire n'importe quelle partie de la fenêtre, une boîte de message va apparaître, expliquant ce qui va échouer. Ceci va rendre le débuggage de votre code beaucoup plus facile.

La fonction KillGLWindow
Sélectionnez
GLvoid KillGLWindow(GLvoid) // Fermer la fenêtre proprement
{

La première chose que nous allons faire dans KillGLWindow() est de vérifier si nous sommes en plein écran. Si c'est le cas, nous allons retourner vers le bureau. Nous devons détruire la fenêtre avant d'enlever le mode plein écran, mais sur certaines cartes, si nous détruisons la fenêtre AVANT d'enlever le mode plein écran, le bureau va devenir corrompu. C'est pour cela que nous devons enlever ce mode plein écran d'abord. Ceci empêchera le bureau d'être corrompu et fonctionne bien avec les cartes Nvidia et 3dfx!

Code de plein écran
Sélectionnez
  if(fullscreen)    // Sommes-nous en mode plein-écran?
    {

Nous utilisons ChangeDisplaySettings(NULL,0) pour nous remettre la configuration du bureau. Passer NULL comme premier paramètre et 0 comme le second paramètre force Windows à utiliser les valeurs qui seront stockées dans un registre Windows (la résolution par défaut, le nombre de bits de profondeur, la fréquence, etc). Ceci remettra les paramètres du bureau en place. Ensuite, nous pourrons de nouveau rendre le curseur visible.

Code pour le plein écran
Sélectionnez
    ChangeDisplaySettings(NULL,0);  // Pour remettre le bureau en place
    ShowCursor(TRUE);               // Remettre la visibilité de la souris
    }

Le code ci-dessous vérifie si nous avons un contexte de rendu (hRC). Si ce n'est pas le cas, le programme va sauter cette section de code et vérifier si nous avons un contexte de fenêtre.

Test sur le contexte de rendu
Sélectionnez
  if(hRC)  // Est-ce que nous avons un contexte rendu?
    {

Si nous avons un contexte de rendu, le code qui suit va vérifier si nous sommes capables de le libérer (détaché le hRC du hDC). Remarquez comment je vérifie pour les erreurs. Je suis en train de dire au programme de tenter de le libérer (avec wglMakeCurrent(NULL,NULL)), ensuite je vérifie si la libération a été une réussite ou non.

Libération du DC et du RC
Sélectionnez
    if(!wglMakeCurrent(NULL,NULL))  // Sommes-nous capables de libérer le DC et le RC?
      {

Si nous sommes incapables de libérer les deux contextes DC et RC, une boîte de message va apparaître un message d'erreur pour nous laisser savoir que les contextes n'ont pas être libérés. L'utilisation de NULL veut dire que la boîte de dialogue n'a pas de fenêtre parent. La chaîne de caractères qui suit sera le texte qui apparaît dans la boîte de dialogue. "SHUTDOWN ERROR" est le texte qui apparaît dans le titre de la boîte de dialogue. Ensuite nous avons MB_OK, cela veut dire que nous voulons une boîte de dialogue avec un bouton appelé "OK". MB_ICONINFORMATION fait apparaître un petit icône dans la boîte (ce qui le fait ressortir un peu plus).

Affichage d'une boîte de dialogue
Sélectionnez
      MessageBox(NULL,"Release Of DC And RC Failed.",
           "SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
      }

Ensuite nous allons tenter de détruire le contexte de rendu. Si cela échoue, un message d'erreur va apparaître.

Destruction du contexte de rendu
Sélectionnez
    if(!wglDeleteContext(hRC))     // Sommes-nous capables de detruire le RC?
      {

Si nous sommes incapables de détruire le rendu de contexte, le code qui suit va faire apparaître une autre boîte de dialogue qui nous dira que la libération du RC a échoué. hRC a été mis à NULL.

Boîte de dialogue
Sélectionnez
        MessageBox(NULL,"Release Rendering Context Failed.",
          "SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
      }
    hRC=NULL;   // Mettre RC à NULL
    }

Maintenant nous allons vérifier si notre programme possède un contexte de fenêtre et, si c'est le cas, nous allons le libérer. Si nous sommes incapables de libérer le contexte de fenêtre, une autre boîte de message va apparaître et hDC sera mis à NULL.

Libération du contexte de fenêtre
Sélectionnez
  if(hDC && !ReleaseDC(hWnd,hDC))  // Sommes-nous capable de liberer le DC
    {
    MessageBox(NULL,"Release Device Context Failed.",
        "SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
        hDC=NULL;    // Mettre DC à NULL
    }

Maintenant, nous allons vérifier s'il y a un contexte de fenêtre, et tenter de le libérer utilisant DestroyWindow(hWnd). Si nous étions incapables de détruire la fenêtre, une boîte de message va apparaître et hWnd seront mis à NULL.

Destruction de la fenêtre
Sélectionnez
  if(hWnd && !DestroyWindow(hWnd))     // Sommes-nous capables de détruire la fenêtre?
    {
    MessageBox(NULL,"Could Not Release hWnd.",
              "SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
    hWnd=NULL;             // Mettre hWnd à NULL
    }

La dernière chose à faire est de désinscrire notre classe de fenêtre. Ceci nous permet de fermer la fenêtre et de rouvrir une autre fenêtre sans recevoir un message d'erreur "Class de fenêtre déjà inscrit".

Désinscription de la classe de fenêtre
Sélectionnez
  // Sommes-nous capables de désinscrire cette classe
  if(!UnregisterClass("OpenGL",hInstance))
    {
    MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
    hInstance=NULL;    // Mettre hInstance à NULL
    }
}

II-G. La fonction CreateGLWindow

La prochaine section de code crée notre fenêtre OpenGL. J'ai passé beaucoup de temps à décider si nous devions créer une fenêtre plein écran qui ne nécessite pas beaucoup de code supplémentaire ou un code qui sera facilement modifiable par un utilisateur. J'ai décidé que la seconde option est un meilleur choix. Je reçois souvent ces questions : Comment créer une fenêtre au lieu de plein écran? Comment faire pour changer le titre de la fenêtre ? Comment changer la résolution ou le format des pixels de la fenêtre ? Le code qui suit permet de faire ceci! Donc c'est une bonne leçon et écrire les programmes OpenGL sera plus facile pour vous!

Comme vous le voyez, la fonction retourne un booléen (TRUE ou FALSE), il prend aussi 5 paramètres : titre de la fenêtre, largeur de la fenêtre, hauteur de la fenêtre, nombre de bits (16/24/32), et finalement le drapeau plein écran (si mis à TRUE) ou en mode fenêtre (si mis à FALSE). Nous retournons une valeur de booléen qui nous dira si la création de la fenêtre a réussi.

Fonction de création de fenêtre
Sélectionnez
BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)
{

Lorsqu'on demande à Windows de nous trouver un format pixel qui est compatible avec celui que nous voulons, le numéro du mode que Windows choisi sera stocké dans la variable PixelFormat.

Variable pour le format des pixels
Sélectionnez
GLuint PixelFormat;    // Contiendra les résultats après avoir trouvé un format adéquat

Nous allons utiliser wc pour contenir la classe de fenêtre. La classe fenêtre contiendra l'information de notre fenêtre. En changeant les différents champs dans la classe, nous pouvons changer comment la fenêtre va apparaître et se comporte. Chaque fenêtre appartient à une classe de fenêtre. Avant de créer une fenêtre, vous devez inscrire une classe à la fenêtre.

Variable pour la classe de fenêtre
Sélectionnez
WNDCLASS wc;           // La structure d'une classe de fenêtre

dwExStyle et dwStyle peuvent stocker l'information du style de la fenêtre normale et étendue. J'utilise des variables pour stocker les styles pour pouvoir changer les styles dépendant du type de fenêtre que j'ai besoin de créer (Une fenêtre sans bordure pour une application plein écran ou une fenêtre avec bordure pour un mode fenêtré).

Variables pour le style de fenêtre
Sélectionnez
DWORD dwExStyle;      // Style etendu de fenetre
DWORD dwStyle;        // Style de fenêtre normal

Les 5 lignes qui suivent récupèrent le valeurs des positions haut, bas, gauche et droit d'un rectangle. Nous allons utiliser les valeurs pour ajuster la fenêtre pour que l'endroit sur lequel on dessine soit exactement à la bonne résolution. Normalement, si nous créons une fenêtre 640x480, les bordures de la fenêtre vont occuper un peu de notre résolution.

Position de la fenêtre
Sélectionnez
RECT WindowRect;                // Rectangle pour la position Haut gauche / Bas droit

  WindowRect.left=(long)0;        // Mettre la valeur Gauche à 0
  WindowRect.right=(long)width;   // Mettre la valeur Droite pour la largeur voulue
  WindowRect.top=(long)0;         // Mettre la valeur Haut à 0
  WindowRect.bottom=(long)height; // Mettre la valeur Bas pour la hauteur voulue

Dans la ligne de code qui suit nous initialisons la variable globale fullscreen à fullscreenflag.

Initialisation de la variable fullscreen
Sélectionnez
  fullscreen=fullscreenflag;    // Initialisation la variable fullscreen

Dans la prochaine section de code, nous allons récupérer l'instance de notre fenêtre puis nous déclarons la classe de fenêtre.

Le style CS_HREDRAW et CS_VREDRAW force la fenêtre à se redessiner lorsque la fenêtre est redimensionné. CS_OWNDC crée un contexte de fenêtre privé pour la fenêtre. Ceci veut dire que le DC n'est pas partagé entre les applications. WndProc est la fonction qui surveille les messages pour notre programme. Nous n'avons pas besoin de données supplémentaires pour la fenêtre donc nous mettons les deux champs Extra à 0. Ensuite, nous mettons en place l'instance de la fenêtre. Après, nous mettons hIcon à NULL disant que nous ne voulons pas d'icône de la fenêtre, et pour le pointeur souris nous allons utiliser un pointeur standard. La couleur de fond n'est pas importante (nous le ferons avec OpenGL). Nous ne voulons pas de menu avec cette fenêtre donc nous le mettons à NULL et le nom de la classe peut être n'importe quel nom. J'utiliserai "OpenGL" pour une raison de simplicité.

Mis en place de hInstance et wc
Sélectionnez
  hInstance           = GetModuleHandle(NULL);   // Récupére une instance pour la fenêtre

  // Redessine au fur et a mesure et possède propre DC pour la fenêre
  wc.style         = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
  wc.lpfnWndProc   = (WNDPROC) WndProc;          // WndProc s'occupera des messages
  wc.cbClsExtra       = 0;                       // Pas de données supplémentaire
  wc.cbWndExtra       = 0;                          // Pas de données supplémentaire
  wc.hInstance     = hInstance;               // Mettre l'instance en place
  wc.hIcon         = LoadIcon(NULL, IDI_WINLOGO);  // Charger l'icône de base
  wc.hCursor       = LoadCursor(NULL, IDC_ARROW);  // Mettre en place le curseur de souris
  wc.hbrBackground = NULL;                      // Pas d'image de fond nécessaire pour GL
  wc.lpszMenuName  = NULL;                    // Nous ne voulons pas de menu
  wc.lpszClassName = "OpenGL";                  // Mettre en place le nom de la classe

Nous allons maintenant inscrire la classe. Si quelque chose échoue, un message d'erreur va apparaître. Cliquer "OK" dans la boîte terminera le programme.

Inscription de la classe
Sélectionnez
  if(!RegisterClass(&wc))  // Tenter d'inscrire une classe de fenêtre
    {
    MessageBox(NULL,"Failed To Register The Window Class.","ERROR",MB_OK|MB_ICONEXCLAMATION);
    return FALSE;          // Quitter et retourner FALSE
    }

Maintenant, nous allons vérifier si le programme va s'exécuter en plein écran ou en mode fenêtré. Si c'est en plein écran, nous allons tenter de se mettre en plein écran.

Code pour le cas plein écran
Sélectionnez
  if(fullscreen)    // Est-ce qu'on est en plein écran?
    {

La prochaine section de code est quelque chose qui pose des problèmes à beaucoup de personnes : passer en mode plein écran. Il y a quelque points importants que vous devez garder à l'esprit lorsqu'on passe en mode plein écran. Soyez sûrs que la largeur et l'hauteur que vous utilisez en plein écran sont les mêmes que la largeur et l'hauteur que vous allez utiliser pour votre fenêtre. Mais, encore plus important, il faut se mettre en plein écran avant de créer la fenêtre. Dans ce code, vous n'avez pas besoin de vous inquiéter de la largeur et la hauteur, le plein écran et la taille de la fenêtre sont tous les deux à la taille voulue.

Code pour le mode de la fenêtre
Sélectionnez
DEVMODE dmScreenSettings;                               // Mode de la fenêtre
memset(&dmScreenSettings,0,sizeof(dmScreenSettings));   // Verifier que la memoire est mis a zero
dmScreenSettings.dmSize=sizeof(dmScreenSettings);  // Taille de la structure Devmode
dmScreenSettings.dmPelsWidth  = width;   // Largeur de la fenetre
dmScreenSettings.dmPelsHeight = height;     // Hauteur de la fenetre
dmScreenSettings.dmBitsPerPel = bits;    // Nombre de bits par pixel
dmScreenSettings.dmFields     = DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;

Dans le code ci-dessus, nous avons créé de la place pour stocker nos paramètres vidéos. Nous allons mettre en place la largeur, la hauteur et le nombre de bits que nous voulons. Dans le code qui suit, nous tentons de mettre le programme en plein écran. Nous stockons toutes les informations (largeur, hauteur et nombre de bits) dans dmScreenSettings. Dans la ligne qui suit, ChangeDisplaySettings tente de passer aux paramètres qui sont compatibles avec ceux dans dmScreenSettings. J'utilise le paramètre CDS_FULLSCREEN lorsque nous changeons de mode parce c'est supposé enlever la barre des tâches, de plus cela garde la fenêtre au même endroit lorsqu'on passe en mode fenêtre au mode plein écran.

Mis en place du mode choisi
Sélectionnez
// Tenter de mettre le mode choisi et récupéré les résultats.
// REMARQUE : CSD_FULLSCREEN enlève la barre des tâches
if(ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
  {

Si le mode n'a pas pu être mis en place, le code qui suit va être exécuté. Si un mode compatible plein écran n'a pas été trouvé, une boîte de dialogue va offrir deux solutions... Mettre en mode fenêtré ou quitter le programme.

Cas échéant
Sélectionnez
// Si le mode n'a pas été compatible, on offre deux solutions :
//   Quitter ou se mettre en mode fenêtre
if(MessageBox(NULL,
    "The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Use Windowed Mode Instead?",
    "NeHe GL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
  {

Si l'utilisateur décide d'utiliser un mode fenêtré, la variable plein écran devient FALSE, et le programme continuera son exécution.

Fin du if et début du else
Sélectionnez
  fullscreen=FALSE;  //Choisir le mode fenêtré
}
else
{

Si l'utilisateur décide de quitter, une boîte de dialogue fera savoir que le programme va se terminer. FALSE sera retourné pour dire au programme que la fenêtre n'a pas été créée correctement. Le programme va ensuite se terminer.

Affichage d'une boîte de dialogue
Sélectionnez
    // Afficher une boite de message pour dire a l'utilisateur que le programme se terminera
    MessageBox(NULL,"Program Will Now Close.","ERROR",MB_OK|MB_ICONSTOP);
    return FALSE;     // Quitter et retourner FALSE
    }
  }
}

Puisque le code du plein écran peut avoir échoué et l'utilisateur peut avoir décider de continuer le programme dans une fenêtre. Pour le savoir, on va vérifier la valeur de la variable fullscreen.

Test sur l'état du plein écran
Sélectionnez
if(fullscreen)        // Sommes-nous toujours en plein écran?
  {

Si nous sommes toujours en mode plein écran, nous allons mettre le style étendu à WS_EX_APPWINDOW qui force une fenêtre vers la barre de tâches une fois que notre fenêtre est visible. Pour le style de la fenêtre, nous allons créer une fenêtre WS_POPUP. Ce genre de fenêtre n'a pas de bordure, ce qui est parfait pour le mode plein écran.

Finalement, on enlève le pointeur de souris. Si votre programme n'est pas interactif, il est toujours plus intéressant d'enlever le pointeur de souris en plein écran. C'est votre décision.

Style étendu et normal de la fenêtre et enlever la souris
Sélectionnez
  dwExStyle=WS_EX_APPWINDOW; // Window Extended Style
  dwStyle=WS_POPUP;          // Windows Style
  ShowCursor(FALSE);         // Hide Mouse Pointer
  }
else
  {

Si nous utilisons une fenêtre au lieu du mode plein écran, nous allons ajouter WS_EX_WINDOWEDGE au style étendu. Ceci va donner à la fenêtre un style plus 3D. Pour le style, nous allons utiliser WS_OVERLAPPEDWINDOW au lieu de WS_POPUP. WS_OVERLAPPEDWINDOW va créer une fenêtre avec un titre, une bordure, un menu et des boutons de minimisation et maximisation.

Style normal et étendu de la fenêtre
Sélectionnez
  dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;   // Style etendu de la fenetre
  dwStyle=WS_OVERLAPPEDWINDOW;                    // Style de la fenetre
}

La ligne qui suit ajuste notre fenêtre dépendant du style de fenêtre que nous sommes en train de créer. L'ajustement créera la résolution voulue. Normalement, les bordures vont recouvrir des parties de la fenêtre. En utilisant la fonction AdjustWindowRectEx, aucune partie de la scène OpenGL va être recouverte par les bords. A la place, la fenêtre sera légèrement agrandie. En plein écran, cet appel n'a aucune conséquence

Mise en place de la taille de la fenêtre
Sélectionnez
// Ajuste la fenetre a la fenetre a la taille voulue
AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);

Dans la prochaine section de code, nous allons créer une fenêtre et vérifier si elle a été créée correctement. Nous allons passé tous les paramètres nécessaires à la fonction CreateWindowEx. Les paramètres sont : le style étendu que nous voulons utiliser, le nom de classe (qui doit être le même que celui que vous avez donné à la classe Fenêtre), le titre de la fenêtre, le style de la fenêtre, la position en haut et à gauche de la fenêtre (0,0 est une bonne idée), la largeur et la hauteur de la fenêtre, on ne veut pas d'une fenêtre parent et nous n'avons pas de menu (donc ces deux derniers paramètres sont mis à NULL), nous passons l'instance de la fenêtre et enfin nous passons NULL.

Remarquez que nous avons inclus les styles WS_CLIPSIBLINGS et WS_CLIPCHILDREN avec le style de la fenêtre que nous avons décidé d'utiliser. WS_CLIPSIBLINGS et WS_CLIPCHILDREN sont tous les deux nécessaires pour travailler correctement. Ces styles empêchent d'autres fenêtres de dessiner au-dessus ou dans notre fenêtre OpenGL.

Création de la fenêtre
Sélectionnez
if(!(hWnd=CreateWindowEx(    dwExStyle,  // Style etendu pour la fenetre
    "OpenGL",               // Nom de la classe
    title,                  // Titre de la fenetre
    WS_CLIPSIBLINGS |       // Style de la fenetre necessaire
    WS_CLIPCHILDREN |       // Style de la fenetre necessaire
    dwStyle,                // Style de la fenetre choisi
    0, 0,                   // Position des fenetres
    WindowRect.right-WindowRect.left,    // Calcul de la largeur ajustee
    WindowRect.bottom-WindowRect.top,    // Calcul de la hauteur ajustee
    NULL,                   // Pas de fenetre parent
    NULL,                   // Pas de menu
    hInstance,              // Instance
    NULL)))                 // Ne rien passer a WM_CREATE

Ensuite, nous vérifions si notre fenêtre s'est créée correctement. Si notre fenêtre a été créée, hWnd va contenir le descripteur de fenêtre. Si la fenêtre n'a pas été créée alors le code ci-dessous va afficher une boîte de dialogue avant de quitter le programme.

En cas d'échec
Sélectionnez
{
  KillGLWindow();    // Remis à zéro de l'affichage
  MessageBox(NULL,"Erreur dans la création de la fenêtre.",
          "ERROR",MB_OK|MB_ICONEXCLAMATION);
  return FALSE;         // Retourner FALSE
}

La prochaine section de code décrit le format du pixel. Nous devons choisir un format qui supporte OpenGL, l'utilisation d'un double tampon et l'utilisation du RGBA (rouge, vert, bleu et un canal alpha). On essaie de trouver un format de pixel qui convient par rapport au nombre de bits qu'on a décidé (16, 24 ou 32 bits). Finalement, nous mettons en place un tampon-Z de 16 bits. Les paramètres qui restent ne sont soit pas utilisés, soit pas importants (à part pour le tampon stencil et le tampon d'accumulation).

Format de pixel
Sélectionnez
static    PIXELFORMATDESCRIPTOR pfd=    // pfd dit aux fenetres quel format nous interesse
  {
  sizeof(PIXELFORMATDESCRIPTOR),    // Taille de ce descripteur de format pixel
        1,                          // Numero de version
        PFD_DRAW_TO_WINDOW |        // Format doit fonctionner avec la fenetre
        PFD_SUPPORT_OPENGL |        // Format doit fonctionner avec OpenGL
        PFD_DOUBLEBUFFER,           // Format doit fonctionner le double tampon
        PFD_TYPE_RGBA,              // Demander un format RGBA
        bits,                       // Choisir le nombre de bits par pixel
        0, 0, 0, 0, 0, 0,           // Bits de couleur ignorés
        0,                          // Pas de tampon Alpha
        0,                          // Bit de decalage ignore
        0,                          // Pas de tampon d'accumulation
        0, 0, 0, 0,                 // Bits d'accumulation ignores
        16,                         // 16Bit du tampon Z (Tampon de profondeur)
        0,                          // Pas de tampon Stencil
        0,                          // Pas de tampon auxiliaire
        PFD_MAIN_PLANE,             // Couche de dessin de base
        0,                          // Reserve
        0, 0, 0                     // Masques de couche ignores
  };

S'il n'y a pas eu d'erreurs lors de la création de la fenêtre, nous allons tenter de récupérer un contexte OpenGL. Si nous ne pouvons pas avoir un contexte, un message de dialogue donnera un message et le programme se terminera.

Récupération d'un contexte de fenêtre
Sélectionnez
if(!(hDC=GetDC(hWnd)))      // Avons-nous un contexte de fenetre?
  {
  KillGLWindow();            // Detruire la fenetre
  MessageBox(NULL,"Can't Create A GL Device Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
  return FALSE;             // Retourner FALSE
  }

Si nous avons réussi à avoir un contexte de fenêtre pour notre fenêtre OpenGL, nous allons tenter d'avoir un format de pixel qui correspond à celui décrit plus haut. Si Windows ne peut pas en trouver, une boîte de dialogue va apparaître et le programme quittera.

Choix du format de pixel
Sélectionnez
// Est-ce que la fenetre a trouve un format de pixel compatible?
if(!(PixelFormat=ChoosePixelFormat(hDC,&pfd)))
  {
  KillGLWindow();            // Detruire la fenetre
  MessageBox(NULL,"Can't Find A Suitable PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
  return FALSE;              // Retourner FALSE
  }

Si Windows a trouvé un format pixel compatible, nous allons tenter de l'utiliser. Si ce format ne peut pas être utilisé, un message d'erreur va apparaître et le programme va se terminer (retourner FALSE).

Mis en place du format de pixel
Sélectionnez
// Pouvons-nous mettre en place le format de Pixels ?
if(!SetPixelFormat(hDC,PixelFormat,&pfd))
  {
  KillGLWindow();    // Detruire la fenetre
  MessageBox(NULL,"Can't Set The PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
  return FALSE;      // Retourner FALSE
  }

Si le format pixel a été mis en place correctement nous allons tenter d'avoir un contexte de rendu. Si nous n'y arrivons pas, un message d'erreur sera affiché et le programme quittera (retourner FALSE).

Création d'un contexte de rendu
Sélectionnez
if(!(hRC=wglCreateContext(hDC)))  // Sommes-nous capable d'avoir un contexte de rendu ?
  {
  KillGLWindow();     // Detruire la fenetre
  MessageBox(NULL,"Can't Create A GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
  return FALSE;          // Retourner FALSE
  }

Si nous n'avons pas eu d'erreurs, nous avons réussi à avoir un contexte de fenêtre et un contexte de rendu, tout ce qu'il reste à faire c'est rendre le contexte de rendu actif. Si nous n'arrivons pas à rendre le contexte de rendu actif, un message de dialogue apparaîtra et le programme quittera (retournant FALSE).

Activer le contexte de rendu
Sélectionnez
if(!wglMakeCurrent(hDC,hRC))    // Tenter d'activer le contexte de rendu
  {
  KillGLWindow();        // Detruire la fenetre

  MessageBox(NULL,"Can't Activate The GL Rendering Context.",
    "ERROR",MB_OK|MB_ICONEXCLAMATION);

  return FALSE;          // Retourner FALSE
  }

Si tout s'est bien passé, notre fenêtre OpenGL a été créée et nous allons montrer la fenêtre, la mettre en premier plan (lui donner plus de priorité) et la rendre active. Ensuite, nous appellerons ReSizeGLScene avec, comme paramètre, la largeur et hauteur de l'écran pour mettre en place la perspective OpenGL.

Code de mise en place
Sélectionnez
ShowWindow(hWnd,SW_SHOW);      // Montrer la fenetre
SetForegroundWindow(hWnd);     // Mettre une priorite plus elevee
SetFocus(hWnd);                // Rendre le clavier actif pour cette fenetre
ReSizeGLScene(width, height);  // Mettre en place la perspective GL

Finalement, nous allons appeler InitGL(), où nous allons mettre en place l'éclairage, les textures et tout ce qu'il faut pour l'initialisation. Vous pouvez faire des tests d'erreurs dans cette fonction. Vous pourrez retourner TRUE (si tout se passe bien) ou FALSE (si quelque chose ne passe pas bien). Par exemple, si vous charger les textures dans InitGL et avez eu une erreur, vous voudrez peut-être que le programme s'arrête. Si vous retourner FALSE à partir de la fonction InitGL, le code ci-dessous va le voir et va pouvoir se quitter.

Si l'initialisation OpenGL échoue
Sélectionnez
if(!InitGL())         // Initialise notre nouvelle fenêtre GL
    {
    KillGLWindow();    // Detruire la fenetre
    MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION);
    return FALSE;      // Retourner FALSE
    }

Si nous sommes arrivés jusqu'ici, on peut supposer que la création de la fenêtre a réussie. Nous pouvons retourner VRAI à WinMain() pour lui dire qu'il n'y a pas eu d'erreurs. Ceci va l'empêcher de quitter le programme.

En cas de succès
Sélectionnez
  return TRUE;  // Succes
}

II-H. La fonction WndProc▲

C'est ici que tous les messages sont gérés. Lorsque nous avons inscrit la classe de fenêtre, nous lui avons dit d'appeler cette fonction pour gérer les messages de fenêtre.

Prototype de WndProc
Sélectionnez
LRESULT CALLBACK WndProc(    HWND    hWnd,  // Identifiant de cette fenetre
                UINT    uMsg,              // Message pour cette fenetre
                WPARAM    wParam,               // Information supplementaire
                LPARAM    lParam)               // Information supplementaire
{

Le code qui teste la valeur uMsg avec un switch. uMsg contient le type du message que nous allons devoir gérer.

Un switch sur les types de messages
Sélectionnez
    switch (uMsg)  // Verifier pour des messages Windows
    {

Si uMsg vaut WM_ACTIVATE, on vérifie si notre fenêtre est encore active. Si notre fenêtre a été minimisée, la variable active vaudra FALSE. Si la fenêtre est active, la variable active vaudra TRUE.

Le cas WM_ACTIVATE
Sélectionnez
case WM_ACTIVATE:       // Est-ce que le message est WM_ACTIVATE
  {
  if(!HIWORD(wParam))  // Est-ce que la fenetre est minimisee?
    {
    active=TRUE;        // La fenetre est active
    }
  else
    {
    active=FALSE;       // La fenetre n'est pas active
    }
  return 0;             // On retourne a la boucle evenementielle
  }

Si le message est égal à WM_SYSCOMMAND (commande système), nous allons comparer wParam avec les différentes possibilités de ce type de message. Si wParam vaut SC_SCREENSAVE ou SC_MONITORPOWER, soit un écran de veille est en train de tenter de démarrer, soit l'écran est en train de rentrer dans un mode d'économie d'énergie. En retournant 0, on évite que ces deux événements se produisent.

Le cas WM_SYSCOMMAND
Sélectionnez
case WM_SYSCOMMAND:       // Intercepte les commandes systeme
  {
  switch (wParam)         // Verifie les appels systemes
    {
    case SC_SCREENSAVE:   // Est-ce que l'ecran de veille commence?
    case SC_MONITORPOWER: // Est-ce que l'ecran tente de commencer l'economisation d'energie
      return 0;           // On empeche que cela se passe
    }
  break;                  // On sort
}

Si uMsg vaut WM_CLOSE, la fenêtre va se fermer. On va envoyer un message pour quitter que la boucle de la fonction main va récupérer. La variable done sera mis à TRUE, la boucle globale s'arrêtera et le programme se terminera.

Le cas WM_CLOSE
Sélectionnez
case WM_CLOSE:          // Est-ce qu'on a recu un message de fermeture?
  {
  PostQuitMessage(0);   // Envoyer un message pour quitter
  return 0;             // Sortir de la fonction
  }

Si une touche du clavier est en train d'être appuyée, on peut découvrir quelle est la touche en regardant wParam. On mettra la case correspondante du tableau keys à TRUE. Cela me permet de vérifier dans le reste du code quelles touches sont appuyées. Ceci me permet aussi de gérer l'appui simultané de plusieurs touches.

Le cas WM_KEYDOWN
Sélectionnez
case WM_KEYDOWN:       // Est-ce qu'on appuie sur une touche
  {
  keys[wParam] = TRUE; // Si c'est le cas, on met à jour la case correspondante
  return 0;            // Sortir de la fonction
  }

Si une touche a été relâchée, on va savoir quelle est cette touche en regardant wParam. On va donc mettre la case correspondante du tableau keys à FALSE. Comme cela, nous allons pouvoir vérifier dans le reste du code si une touche est encore appuyée ou non. Chaque touche est représentée par un nombre entre 0 et 255. Par exemple, lorsque j'appuie sur la touche représentée par 40, keys[40] va valoir TRUE. Lorsque je relâche cette touche, cette case deviendra FALSE. C'est comme cela qu'on pourra gérer le clavier.

Le cas WM_KEYUP
Sélectionnez
case WM_KEYUP:             // Est-ce qu'une touche a ete relachee?
  {
  keys[wParam] = FALSE;    // La case associee devient FALSE
  return 0;                   // Sortir de la fonction
  }

Lorsqu'on redimensionne la fenêtre, uMsg va éventuellement valoir WM_SIZE. Nous pourrons utiliser les valeurs LOWORD et HIWORD de lParam pour récupérer les nouvelles dimensions de la fenêtre. Nous allons donc passer ces dimensions à ReSizeGLScene. La scène OpenGL est donc redimensionnée aux bonnes dimensions.

Le cas WM_SIZE
Sélectionnez
  case WM_SIZE:            // Redimensionner la fenêtre
    {
    ReSizeGLScene(LOWORD(lParam),HIWORD(lParam));  // LoWord=Largeur, HiWord=Hauteur
    return 0;           // Sortir de la fonction
    }
}

Tous les messages qui nous intéressent pas seront passés à la fonction DefWindowProc pour que Windows puissent s'en occuper.

Tous les autres messages
Sélectionnez
  // Passer tous les messages qui ne sont pas geres à DefWindowProc
  return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

II-I. La fonction WinMain▲

Ceci est le point d'entrée à notre application Windows. C'est ici que nous créons la fenêtre, gère les messages et regarde pour l'interaction de l'utilisateur.

La fonction WinMain
Sélectionnez
int WINAPI WinMain(    HINSTANCE    hInstance,  // Instance
            HINSTANCE    hPrevInstance,      // Instance precedante
            LPSTR        lpCmdLine,          // Parametres de ligne de commande
            int        nCmdShow)               // L'etat d'affichage de la fenetre
{

On met en place deux variables. msg doit être utilisé pour vérifier s'il n'y a pas de messages en attente qui doivent être gérés. La variable done commence avec la valeur FALSE. Ceci veut dire que notre programme n'a pas fini d'être exécuté. Du moment que done demeure à FALSE, le programme va continuer. Dès qu'il passera à TRUE, le programme va quitter.

Variables locales
Sélectionnez
MSG    msg;          // Structure d'un message Windows
BOOL done=FALSE;  // Variable pour gerer la boucle infinie

Cette portion de code est entièrement facultative. Il affiche une boîte de dialogue pour vous demander d'exécuter le programme en plein écran. Si l'utilisateur clique sur NON, la variable fullscreen passe à FALSE (la valeur par défaut est TRUE) et le programme se lancera dans une fenêtre.

Interaction avec l'utilisateur
Sélectionnez
// Demander a l'utilisateur quel mode d'execution (fenetre ou plein ecran)
if(MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?",
    "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
  {
  fullscreen=FALSE;    // Mode fenêtré
  }

C'est comme ceci que nous créons une fenêtre OpenGL. Nous passons le titre, la largeur, la hauteur, le nombre de bit par pixel et le mode de fenêtrage à la fonction CreateGLWindow. C'est tout! Je suis assez content de la simplicité de ce code. Si la fenêtre n'a pas été codé pour une raison ou une autre, FALSE sera retourné et notre programme pourra quitter (avec return 0).

Création de la fenêtre
Sélectionnez
  // Creer notre fenetre OpenGL
  if(!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen))
    {
    return 0;  // Quitter si la fenetre n'a pas ete creee
    }

C'est ici le début de notre boucle. Tant que done vaut FALSE, la boucle continuera.

Boucle globale
Sélectionnez
  while(!done)  // Boucle qui s'execute tant que done=TRUE
    {

La première chose que nous devons vérifier s'il y a des messages qui attendent. En utilisant la fonction PeekMessage, nous pouvons vérifier pour des messages sans arrêter notre programme. Beaucoup de programmes utilisent GetMessage. Ceci fonctionne aussi, mais avec GetMessage, votre programme ne fait rien jusqu'à la réception d'un message.

On vérifie si un message est en attente
Sélectionnez
if(PeekMessage(&msg,NULL,0,0,PM_REMOVE))  // Y-a-t-il un message en attente?
  {

Dans la prochaine portion de code, nous vérifions si un message WM_QUIT a été envoyé. Si le message courant est WM_QUIT causé par PostQuitMessage(0), la variable done est mise à TRUE. Ceci provoquera la fin du programme.

Allons-nous quitter?
Sélectionnez
if(msg.message==WM_QUIT)   // Avons-nous recu un message pour quitter?
  {
  done=TRUE;                // Si c'est le cas, on met done a TRUE
  }
else                        // Sinon gere les messages
  {

Si ce n'est pas un message pour quitter, nous traduisons le message, puis nous l'envoyons avec la fonction DispatchMessage pour que WndProc ou Windows puissent s'en occuper.

Traduire le message et l'envoyer
Sélectionnez
    TranslateMessage(&msg);   // Traduire le message
    DispatchMessage(&msg);    // Envoyer le message
    }
  }
else                          // S'il n'y a pas de message
  {

S'il n'y avait pas de messages, nous allons dessiner la scène OpenGL. La première ligne de code ci-dessous vérifie si la fenêtre est active. Si la touche Echap a été appuyée alors la variable done est mise à TRUE, provoquant la fin du programme.

Si la fenêtre est active
Sélectionnez
// Dessine la scéne. Verifier si Echap a ete appuye
if(active)               // Est-ce que le programme est actif?
  {
  if(keys[VK_ESCAPE])    // Est-ce que Echap a ete appuye?
    {
    done=TRUE;            // ESC signale la fin du programme
    }
  else                    // On ne quitte pas encore, on va dessiner
    {

Si le programme est actif et echap n'a pas été appuyé, nous allons dessiner la scène et échanger les tampons (En utilisant un double tampon, nous obtiendrons animation sans scintillement). En utilisant un double tampon, nous dessinons la scène sur un écran invisible. Lorsque nous échangeons les tampons, la scène que nous voyons devient l'écran invisible et la scène qui était invisible devient visible. En faisant comme cela, nous ne voyons jamais la scène en train d'être dessiné. L'image apparaît directement entière.

Dessin de la scène
Sélectionnez
  DrawGLScene();     // Dessine la scene
  SwapBuffers(hDC);  // Echange les tampons (Double tampon)
  }
}

La prochaine section de code nous permet de passer entre le mode plein écran et le mode fenêtré.

Passage entre les modes fenêtré et plein écran
Sélectionnez
  if(keys[VK_F1])            // Est-ce que F1 est appuye?
    {
    keys[VK_F1]=FALSE;        // On passe la touche a FALSE
    KillGLWindow();           // Fermer la fenetre OpenGL
    fullscreen=!fullscreen;   // Passer entre le plein ecran / mode fenetre

    // Recréer la fenetre OpenGL
    if(!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen))
      {
      return 0;                 // Si la fenetre n'a pas ete creee
      }
    }
  }
}

Si la variable done n'est plus FALSE, le programme quitte. Nous fermons correctement la fenêtre OpenGL pour que tout soit libéré et nous quittons le programme.

Fermer la fenêtre
Sélectionnez
  // Fermeture
  KillGLWindow();      // Fermer la fenêtre
  return (msg.wParam); // Quitter le programme
}

II-J. Conclusion▲

Dans ce tutoriel, j'ai tenté d'expliquer, avec autant de détail que possible, chaque étape nécessaire pour mettre en place et créer un programme OpenGL en plein écran. Ce programme quittera si la touche ESC a été appuyée et fait attention si la fenêtre est active ou non.

Jeff Molofee ( NeHe )

III. Téléchargements▲

Compte tenu du nombre de versions de code sources pour les tutoriels nehe, nous les laissons en anglais. En principe, si vous avez compris le code présenté dans ce tutoriel (et les tutoriels antérieurs), vous n'aurez pas de mal à le comprendre :

  • ASM (Conversion par Foolman)
  • BeOS (Conversion par Rene Manqueros)
  • Borland C++ Builder 6 (Conversion par Christian Kindahl)
  • C# (Conversion par Joachim Rohde)
  • Code Warrior 5.3 (Conversion par Scott Lupton)
  • CygWin (Conversion par Stephan Ferraro)
  • D (Conversion par Familia Pineda Garcia)
  • Delphi (Conversion par Michal Tucek)
  • Dev C++ (Conversion par Dan)
  • Game GLUT (Conversion par Milikas Anastasios)
  • Irix (Conversion par Lakmal Gunasekara)
  • Java (Conversion par Jeff Kirby)
  • Java/SWT (Conversion par Victor Gonzalez)
  • JOGL (Conversion par Kevin J. Duling)
  • LCC Win32 (Conversion par Robert Wishlaw)
  • Linux (Conversion par Richard Campbell)
  • Linux GLX (Conversion par Mihael Vrbanec)
  • Linux SDL (Conversion par Ti Legget)
  • LWJGL (Conversion par Mark Bernard)
  • Mac OS (Conversion par Anthony Parker)
  • Mac OS X/Cocoa (Conversion par Bryan Blackburn)
  • MASM (Conversion par Nico (Scalp))
  • Pelles C (Conversion par Pelle Orinius)
  • Perl (Conversion par Cora Hussey)
  • Power Basic (Conversion par Angus Law)
  • Python (Conversion par John Ferguson)
  • Scheme (Conversion par Jon DuBois)
  • Solaris (Conversion par Lakmal Gunasekara)
  • Visual Basic (Conversion par Ross Dawson)
  • Visual Fortran (Conversion par Jean-Philippe Perois)
  • Visual Studio
  • VB.Net CsGL (Conversion par X)
  • Visual Studio NET (Conversion par Grant James)


Premier triangle et premier quadrilatère

  • I. Contributions
  • II. Tutoriel
  • III. Téléchargements
  • IV. Remerciements
  • V. Liens

Dans le premier tutoriel, je vous ai appris comment créer une fenêtre OpenGL. Dans ce tutoriel, je vais vous montrer comment créer des triangles et des rectangles. Nous allons dessiner un triangle en utilisant GL_TRIANGLES et un carré avec GL_QUADS.

I. Contributions

Nous recherchons toujours des traducteurs pour finir ce projet de traduction des articles de NeHe. Votre aide serait appréciée ! Vous êtes intéressé ? Alors contactez-nous à l'adresse suivante : nehe@redaction-developpez.com .

II. Tutoriel

À partir du code du premier tutoriel, nous allons juste modifier la procédure DrawGLScene(). Je vais entièrement réécrire son code. Si vous vous basez sur la dernière leçon, vous pouvez remplacer la procédure DrawGLScene() par le code suivant, ou juste rajouter les lignes de codes qui n'existent pas dans le précédent tutoriel.

Début de la fonction DrawGLScene
Sélectionnez
int DrawGLScene(GLvoid)         // C'est ici que nous ferons tout le dessin
{
    // Vide l'écran et le Z-Buffer (Depth Buffer : contient les informations de profondeur de chaque pixel )
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();         // Repositionne la caméra

Quand vous faites un appel à glLoadIdentity(), en fait vous revenez au centre de l'écran avec l'axe des X qui va de gauche à droite, l'axe des Y de haut en bas, et l'axe des Z qui va de l'intérieur vers l'extérieur de l'écran.

Le centre d'un écran défini par OpenGL est le point 0.0f sur les axes X et Y. A la gauche du centre, on trouvera les valeurs négatives. A la droite les valeurs positives. En allant vers le haut, ça sera positif, et en allant vers le bas ça sera négatif. En se dirigeant vers l'intérieur, c'est à dire en s'éloignant de l'observateur, il y aura les valeurs négatives, et en se rapprochant de l'observateur on rencontrera des valeurs positives.

glTranslatef(x, y, z) permet de bouger respectivement tout au long des axes X, Y et Z. La ligne de code ci-dessou permet de bouger de 1.5 unités sur l'axe des X vers la gauche. On ne bouge pas du tout sur l'axe des Y (0.0f), et on bouge vers l'intérieur de l'écran de 6.0 unités. Quand vous translatez, vous ne bougez pas à partir du centre de l'écran, mais bien à partir de là où vous êtes actuellement sur l'écran.

Translation
Sélectionnez
    // Bouge de 1.5 unités vers la gauche et de 6.0 unités vers l'intérieur de l'écran
    glTranslatef(-1.5f,0.0f,-6.0f);

Maintenant que nous avons bougé vers la moitié gauche de l'écran, et que nous avons bougé vers l'intérieur de l'écran de -6.0f, et que nous pouvons voir entièrement notre scène, nous allons créer le Triangle. glBegin(GL_TRIANGLES) signifie que nous voulons commencer à dessiner un triangle, et glEnd() indique à OpenGL que nous avons finis de créer le triangle. Si vous voulez 3 points, utilisez GL_TRIANGLES (dessiner des triangles est assez rapide sur la plupart des cartes graphiques) Si vous voulez 4 points, utilisez GL_QUADS pour vous simplifier la vie. D'après ce que j'ai entendu, au final, la plupart des cartes graphiques dessinent les objets sous forme de triangles de toute façon. Et si vous avez plus de 4 points, utilisez GL_POLYGON.

Dans notre petit programme, on dessine un seul triangle. Si nous voulions dessiner un second triangle, nous pourrions rajouter 3 lignes de codes (correspondant à 3 points) juste après nos trois premières lignes. Ainsi, nos six lignes de codes se trouveraient entre glBegin(GL_TRIANGLES) et glEnd(). Il n'y a aucune différence entre ce code et un code qui sépare le rendu des triangles avec les appels glBegin(GL_TRIANGLES) et glEnd() autour de chaque groupe de 3 points. Ceci s'applique également pour les quadrilatères. Si vous savez que vous ne dessinez que des quadrilatères, vous pouvez inclure le second groupe de points juste après vos quatre premières lignes. Pour les polygones (GL_POLYGON), c'est différent, ils peuvent être composés d'autant de points que vous voulez, donc le nombre de lignes que vous avez entre glBegin(GL_POLYGON) et glEnd() n'a pas d'importance.

La première ligne après glBegin définit le premier point de notre polygone. Le premier nombre de la fonction glVertex est pour l'axe des X, le second nombre est pour l'axe des Y, et le troisième pour l'axe des Z. Donc dans notre première ligne de code, nous ne bougeons pas sur l'axe des X. Nous bougeons d'une unité sur l'axe des Y, et nous ne bougeons pas sur l'axe des Z. Ceci nous donne le point du haut de notre triangle. Le second glVertex bouge vers la gauche d'une unité sur l'axe des X, et d'une unité vers le bas sur l'axe des Y. Ceci nous donne le point en bas à gauche de notre triangle. Le troisième glVertex bouge vers la droite d'une unité, et vers le bas d'une unité. Ceci nous donne le point en bas à droite de notre triangle. glEnd() indique à OpenGL qu'il n'y a pas de point supplémentaire. Le triangle rempli va s'afficher.

Dessiner un triangle
Sélectionnez
    glBegin(GL_TRIANGLES);        // Dessiner un Triangle
        glVertex3f( 0.0f, 1.0f, 0.0f);    // Point du haut
        glVertex3f(-1.0f,-1.0f, 0.0f);    // Point en bas à gauche
        glVertex3f( 1.0f,-1.0f, 0.0f);    // Point en bas à droite
    glEnd();                        // Le dessin du Triangle est fini

Maintenant que nous avons un triangle d'affiché sur la moitié gauche de l'écran, nous allons bouger vers la partie droite de l'écran pour afficher le carré. Pour ce faire nous allons encore utiliser la fonction glTranslate. Cette fois ci, nous devons bouger vers la droite, donc X doit être une valeur positive. Comme nous avons déjà bougé à gauche de 1.5 unités, pour retourner au centre, nous devons nous déplacer de 1.5 unités vers la droite. Après avoir rejoint le centre, nous avons encore à bouger de 1.5 unités à droite du centre. Donc au total, nous devons faire un déplacement de 3.0 unités vers la droite.

Translation vers la droite
Sélectionnez
    glTranslatef(3.0f,0.0f,0.0f);     // Bouge de 3.0f unités vers la droite

Maintenant nous allons créer le carré. Pour ce faire nous utiliserons GL_QUADS. Un quadrilatère est un polygone à 4 cotés. Parfait pour créer un carré. Le code pour créer un carré est très similaire au code pour dessiner un triangle. La seule différence est que l'on utilise GL_QUADS au lieu de GL_TRIANGLES, et que nous rajoutons un appel supplémentaire à glVertex3f pour le quatrième point du carré. Nous allons dessiner les points du carré dans l'ordre en haut à gauche, puis en haut à droite, ensuite en bas à droite, et enfin en bas à gauche (sens des aiguilles d'une montre). En dessinant dans ce sens, le carré sera dessiné comme une face arrière. Dans notre cas, cela voudra dire que nous verrons l'arrière de notre carré. Les objets dessinés dans le sens inverse des aiguilles d'une montre nous ferons face. Pour l'instant ce n'est pas important, mais plus tard vous devez y faire attention.

Dessiner un quadrilatère
Sélectionnez
    glBegin(GL_QUADS);            // Dessin d'un quadrilatère
        glVertex3f(-1.0f, 1.0f, 0.0f);    // Haut Gauche
        glVertex3f( 1.0f, 1.0f, 0.0f);    // Haut Droite
        glVertex3f( 1.0f,-1.0f, 0.0f);    // Bas Droite
        glVertex3f(-1.0f,-1.0f, 0.0f);    // Bas Gauche
    glEnd();                // Fin du dessin du rectangle
    return TRUE;                        // On sort de la fonction
}

Enfin, on change le code pour passer d'une application fenêtrée à une application plein écran de sorte que le titre en haut de la fenêtre soit le bon.

Pensez également à changer le titre sous // Créer votre fenêtre OpenGL (juste en dessous). Autrement le mode plein écran aura un titre différent du mode fenêtré.

Gestion du clavier
Sélectionnez
    if (keys[VK_F1])            // Est ce que F1 a été pressé ?
        {
        keys[VK_F1]=FALSE;        // Si Oui, on le repositionne à False
        KillGLWindow();            // On détruit notre fenêtre courante
        fullscreen=!fullscreen;        // On change de mode

    // Recréer votre fenêtre OpenGL ( Modifié )
    if (!CreateGLWindow("Le premier tutoriel de NeHe sur les polygones",640,480,16,fullscreen))
        {
        return 0;        // On quitte si la fenêtre ne s'est pas créée
        }
    }

Markus Knauer ajoute: Dans le livre ("OpenGL Programming Guide: The Official Guide to Learning OpenGL, Release 1", J. Neider, T. Davis, M. Woo, Addison-Wesley, 1993) le paragraphe suivant explique clairement ce que Nehe veut dire quand il fait référence aux unités pour les mouvements sous OpenGL :

[Je dis] pouces et millimètres - Est-ce que ceci à réellement à voir avec OpenGL ? La réponse est, en un mot, non. La projection et les autres transformations se font sans unité. Si vous voulez penser que le plan de devant ou du fond est situé à 1.0 et 20.0 mètres, pouces, kilomètres, c'est comme vous voulez. La seule chose qui compte c'est de vous y tenir en respectant les unités.

Dans ce tutoriel j'ai essayé de vous expliquer avec le plus de détails possibles, chacune des étapes nécessaires pour dessiner des polygones et des quadrilatères à l'écran en utilisant OpenGL. Si vous avez des commentaires ou des questions envoyez moi un e-mail. Si vous pensez que j'ai mal commenté quelque chose ou que le code choisi pourrait être amélioré dans certaines parties faites-le moi savoir. Je veux faire les meilleurs tutoriels OpenGL que je peux. Je serai ravi de vos retours.

Enfin, voici une petite image de ce que vous devez voir :

Image de l'application
Image de l'application

Jeff Molofee ( NeHe )

III. Téléchargements▲

Compte tenu du nombre de versions de codes sources pour les tutoriels nehe, nous les laissons en anglais. En principe, si vous avez compris le code présenté dans ce tutoriel (et les tutoriels antérieurs), vous n'aurez pas de mal à le comprendre :

  • ASM (Conversion par Foolman)
  • BeOS (Conversion par Rene Manqueros)
  • Borland C++ Builder 6 (Conversion par Christian Kindahl)
  • C# (Conversion par Joachim Rohde)
  • Code Warrior 5.3 (Conversion par Scott Lupton)
  • CygWin (Conversion par Stephan Ferraro)
  • D (Conversion par Familia Pineda Garcia)
  • Delphi (Conversion par Michal Tucek)
  • Dev C++ (Conversion par Dan)
  • Game GLUT (Conversion par Milikas Anastasios)
  • GLUT (Conversion par Andy Restad)
  • Irix (Conversion par Lakmal Gunasekara)
  • Java (Conversion par Jeff Kirby)
  • Java/SWT (Conversion par Victor Gonzalez)
  • Jedi-SDL (Conversion par Dominique Louis)
  • JOGL (Conversion par Kevin J. Duling)
  • LCC Win32 (Conversion par Robert Wishlaw)
  • Linux (Conversion par Richard Campbell)
  • Linux GLX (Conversion par Mihael Vrbanec)
  • Linux SDL (Conversion par Ti Leggett)
  • LWJGL (Conversion par Mark Bernard)
  • Mac OS (Conversion par Anthony Parker)
  • Mac OS X/Cocoa (Conversion par Bryan Blackburn)
  • MASM (Conversion par Nico (Scalp))
  • Pelles C (Conversion par Pelle Orinius)
  • Perl (Conversion par Cora Hussey)
  • Power Basic (Conversion par Angus Law)
  • Python (Conversion par Travis Wells)
  • QT/C++ (Conversion par Popeanga Marian)
  • REALBasic (Conversion par Thomas J. Cunningham)
  • Ruby (Conversion par Ben Goodspeed)
  • Scheme (Conversion par Jon DuBois)
  • Solaris (Conversion par Lakmal Gunasekara)
  • Visual Fortran (Conversion par Jean-Philippe Perois)
  • Visual Studio
  • VB.NET CsGL (Conversion par X)
  • Visual Studio NET (Conversion par Grant James)


Coloration

  • Coloration

    • I. Contributions
    • II. Tutoriel
    • III. Téléchargements
    • IV. Remerciements
    • V. Liens

    Dans la leçon précédente, je vous ai montré comment afficher des triangles ou des polygones à l'écran. Dans la présente leçon, nous verrons 2 possibilités de coloration pour un triangle ou un polygone. La coloration "à plat" permettra au polygone d'être uniformément colorié. La coloration "par lissage" permettra de mêler, pour chaque point du triangle, les 3 couleurs de base afin d'obtenir un harmonieux mélange de couleurs.

    II. Tutoriel

    En se basant sur le code de la leçon précédente, nous allons enrichir la fonction DrawGLScene. Je vais entièrement réécrire le code la fonction ci-après. Si vous comptez modifier la dernière leçon, vous pouvez remplacer la fonction DrawGLScene() par le code suivant, ou juste rajouter les lignes de codes suivantes qui étaient absentes du précédent tutoriel.

    Début de la fonction DrawGLScene
    int DrawGLScene(GLvoid)                                // C'est ici que tous les tracés sont réalisés
{
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // efface l'écran et le buffer de profondeur
   glLoadIdentity();                                   // Réinitialisation de la matrice Current Modelview
   glTranslatef(-1.5f,0.0f,-6.0f);                     // Déplacement: gauche 1.5 , intérieur Six Unités
   glBegin(GL_TRIANGLES);                              // tracé des Triangles

    Si vous vous souvenez de la leçon précédente, il s'agit là de la portion de code permettant de dessiner le triangle dans la moitié gauche de l'écran. Dans la ligne de code suivante, nous utilisons pour la première fois la commande glColor3f(r,g,b). Les 3 paramètres entre les parenthèses correspondent aux valeurs de l'intensité du rouge, du vert et du bleu. Ces paramètres ont un domaine de valeur variant de 0.0f à 1.0f. Cela fonctionne comme pour les valeurs de couleur utilisées lors de l'effacement de l'écran.

    Nous règlons la couleur sur rouge (rouge complet, pas de composante verte ou bleue). La ligne de code qui suit va définir le premier sommet (angle supérieur du triangle) qui sera tracé en utilisant la couleur courante: le rouge. Tout ce qui sera tracé ensuite le sera en rouge, jusqu'à ce que nous changions de couleur.

    Reglage de couleur et mis en place d'un point
          glColor3f(1.0f,0.0f,0.0f);        // Réglage de la couleur : rouge
      glVertex3f( 0.0f, 1.0f, 0.0f);    // Déplacement d'une unité vers le haut depuis le centre

    Nous venons de définir le premier sommet sur l'écran en précisant sa couleur (rouge); Maintenant, et avant de définir le deuxième sommet, nous allons changer la couleur et prendre le vert. Ainsi, le second sommet qui sera le sommet gauche du triangle apparaitra en vert.

    Reglage de couleur et mis en place d'un point
          glColor3f(0.0f,1.0f,0.0f);        // Réglage de la couleur : vert
      glVertex3f(-1.0f,-1.0f, 0.0f);    // Déplacement d'une unité vers le bas et la gauche

    Au tour du troisième et dernier sommet. Avant de le tracer, nous changeons de couleur pour le bleu. Cela représentera le sommet de droite de notre triangle. Dès que la commande glEnd() aura été exécutée, le polygone sera rempli. Cependant, à cause de l'utilisation d'une couleur différente pour chaque sommet, au lieu d'obtenir un remplissage mono-couleur, chacune des couleurs va diffuser depuis son sommet, rencontrer les autres au centre et se mélanger. C'est la coloration par lissage.

    Fin du dessin du triangle
          glColor3f(0.0f,0.0f,1.0f);          // Réglage de la couleur : bleu
      glVertex3f( 1.0f,-1.0f, 0.0f);       // Déplacement d'une unité vers la droite et en bas
   glEnd();                                // fin du tracé du Triangle
   glTranslatef(3.0f,0.0f,0.0f);           // depuis le point de droite, déplacement de 3 unités vers la droite

    Maintenant, nous allons tracer un carré et le colorier uniformément en bleu. Il est important de se souvenir que tout ce qui est tracé après la définition d'une couleur le sera dans cette couleur. Chaque projet que vous créerez par la suite utilisera la coloration d'une manière ou d'une autre. Même dans les scènes où tout est à base de textures, glColor3f pourra quand même être utilisée pour modifier la couleur de ces textures. Nous aborderons ce point plus tard.

    Revenons à notre carré d'une seule couleur. Tout ce que nous avons à faire est de déclarer la couleur (le bleu dans notre exemple), puis de tracer ce carré. La couleur bleue sera utilisée pour chaque sommet puisque nous n'aurons pas demandé à OpenGL le changement de couleur entre chaque sommet. Le résultat est... un carré tout bleu ! A nouveau, le carré (polygone) est dessiné dans le sens des aiguilles d'une montre, signifiant que nous avons commencé le tracé en regardant l'arrière du carré.

    Dessin d'un carré bleu
       glColor3f(0.5f,0.5f,1.0f);         // unique définition de la couleur bleue
   glBegin(GL_QUADS);                 // début du tracé
      glVertex3f(-1.0f, 1.0f, 0.0f);  // à gauche et en haut d'une unité
      glVertex3f( 1.0f, 1.0f, 0.0f);  // à droite et en haut d'une unité
      glVertex3f( 1.0f,-1.0f, 0.0f);  // à droite et en bas d'une unité
      glVertex3f(-1.0f,-1.0f, 0.0f);  // à gauche et en bas d'une unité
   glEnd();                           // le tracé est complet
   return TRUE;                       // on continue
}

    Enfin, modification du code pour basculer du mode fenêtré vers le mode plein écran pour que le titre de la fenêtre soit correct.

    Gestion du clavier
    if (keys[VK_F1])            // Appui sur la touche F1 ?
   {
   keys[VK_F1]=FALSE;       // si oui, annulation de la touche enfoncée
   KillGLWindow();          // suppression de la fenêtre courante
   fullscreen=!fullscreen;  // bascule entre les mode plein écran et fenêtré

   // Re-création de notre fenêtre OpenGL ( Modifiée )
   if (!CreateGLWindow("Le Tutoriel sur la Couleur de NeHe",640,480,16,fullscreen))
      {
      return 0;             // Abandon si la fenêtre n'a pas été créée
      }
   }

    Dans cette partie, j'ai essayé d'expliquer de façon détaillée, comment ajouter la coloration "à plat" ou par lissage aux polygones openGL. A vous d'expérimenter: essayez de changer les valeurs des composantes rouge, vert et bleu. Regardez quelles couleurs peuvent être obtenues. Si vous avez des commentaires ou des interrogations, merci de m'envoyer un mail. Si vous pensez qu'un commentaire est incorrect ou que certaines portions du code peuvent être améliorées, faites le moi savoir. Je désire produire le meilleur tutoriel possible sur OpenGL. Je suis donc intéressé par votre retour d'expérience.

    Jeff Molofee ( NeHe )

    III. Téléchargements

    Compte tenu du nombre de versions de codes sources pour les tutoriels nehe, nous les laissons en anglais. En principe, si vous avez compris le code présenté dans ce tutoriel (et les tutoriels antérieurs), vous n'aurez pas de mal à le comprendre :

    • ASM (Conversion par Foolman)
    • BeOS (Conversion par Rene Manqueros)
    • Borland C++ Builder 6 (Conversion par Christian Kindahl)
    • C# (Conversion par Joachim Rohde)
    • Code Warrior 5.3 (Conversion par Scott Lupton)
    • CygWin (Conversion par Stephan Ferraro)
    • D (Conversion par Familia Pineda Garcia)
    • Delphi (Conversion par Michal Tucek)
    • Dev C++ (Conversion par Dan)
    • Euphoria (Conversion par Evan Marshall)
    • Game GLUT (Conversion par Milikas Anastasios)
    • GLUT (Conversion par Andy Restad)
    • Irix (Conversion par Lakmal Gunasekara)
    • Java (Conversion par Jeff Kirby)
    • Java/SWT (Conversion par Victor Gonzalez)
    • Jedi-SDL (Conversion par Dominique Louis)
    • JOGL (Conversion par Kevin J. Duling)
    • LCC Win32 (Conversion par Robert Wishlaw)
    • Linux (Conversion par Richard Campbell)
    • Linux GLX (Conversion par Mihael Vrbanec)
    • Linux SDL (Conversion par Ti Leggett)
    • LWJGL (Conversion par Mark Bernard)
    • Mac OS (Conversion par Anthony Parker)
    • Mac OS X/Cocoa (Conversion par Bryan Blackburn)
    • MASM (Conversion par Nico (Scalp))
    • Pelles C (Conversion par Pelle Orinius)
    • Perl (Conversion par Cora Hussey)
    • Power Basic (Conversion par Angus Law)
    • Python (Conversion par John Ferguson)
    • REALBasic (Conversion par Thomas J. Cunningham)
    • Scheme (Conversion par Jon DuBois)
    • Solaris (Conversion par Lakmal Gunasekara)
    • VB.NET CsGL (Conversion par X)
    • Visual Basic (Conversion par Ross Dawson)
    • Visual Fortran (Conversion par Jean-Philippe Perois)
    • Visual Studio
    • Visual Studio NET (Conversion par Grant James)


Rotations

  • I. Contributions
  • II. Tutoriel
  • III. Téléchargements
  • IV. Remerciements
  • V. Liens

Dans la leçon précédente, je vous ai montré comment ajouter de la couleur aux triangles ou aux polygones. Dans cette leçon, nous verrons comment faire tourner ces objets colorés autour d'un axe.

I. Contributions

Nous recherchons toujours des traducteurs pour finir ce projet de traduction des articles de NeHe. Votre aide serait appréciée ! Vous êtes intéressé ? Alors contactez-nous à l'adresse suivante : nehe@redaction-developpez.com .

II. Tutoriel

En se basant sur le code de la leçon précédente, nous allons y apporter quelques aménagements. Je vais entièrement réécrire le code ci-dessous, facilitant ainsi la détection de ce qui a été ajouté ou remplacé.

Commençons par ajouter les 2 variables permettant de garder la trace de la rotation pour chaque objet. Nous ferons cela au début de notre programme, à la suite des autres variables. Vous remarquerez 2 nouvelles lignes après 'bool fullscreen=TRUE;'. Ces lignes initialisent 2 variables numériques à virgule flottante que nous pourrons utiliser pour "accrocher" les objets sur l'écran avec une grande précision. Les variables numériques à virgule flottante admettent des valeurs décimales. Ceci signifie que nous ne serons pas limité à l'emploi de valeurs entières (1, 2, 3, etc...) pour les angles, nous pourrons aussi utiliser 1.1, 1.7, 2.3,ou même 1.015 pour une meilleure précision. Vous constaterez que ces valeurs décimales sont incontournables dans la programmation OpenGL. Ces nouvelles variables sont nommées:

  • rtri pour la rotation du triangle
  • rquad pour la rotation du carré

Initialisation et variables globales
Sélectionnez
#include    <windows.h>     // Fichier Header pour Windows
#include    <gl\gl.h>       // Fichier Header pour la librairie OpenGL32
#include    <gl\glu.h>      // Fichier Header pour la librairie GLu32
#include    <gl\glaux.h>    // Fichier Header pour la librairie GLaux

HDC       hDC=NULL;         // Contexte de peripherique GDI prive
HGLRC     hRC=NULL;         // Contexte de rendu permanent
HWND      hWnd=NULL;        // Identificateur pour notre fenetre
HINSTANCE hInstance;        // Identificateur pour l'instance de l'application
bool      keys[256];        // Tableau utilise par la routine du clavier
bool      active=TRUE;      // Indicateur de fenetre active
bool      fullscreen=TRUE;  // Indicateur de plein-ecran a vrai par defaut
GLfloat   rtri;             // Angle pour le Triangle ( NOUVEAU )
GLfloat   rquad;            // Angle pour le carré ( NOUVEAU )

Maintenant, il nous faut modifier le code de DrawGLScene(). Je vais réecrire complètement la procédure; il vous sera ainsi plus facile de repérer les modifications apportées au code initial. J'indiquerai pourquoi les instructions sont modifiées, ainis que le rôle de chaque nouvelle ligne. La portion de code suivante est reprise, sans modification, de la leçon précédente.

Debut de la fonction DrawGLScene
Sélectionnez
int DrawGLScene(GLvoid)                 // C'est ici que tous le rendu sont realises
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  // efface l'ecran et le buffer de profondeur
    glLoadIdentity();                   // Reinitialisation de la vue
    glTranslatef(-1.5f,0.0f,-6.0f);     // Deplacement: gauche 1.5 , interieur Six Unites

La ligne de code suivante est nouvelle. glRotatef(Angle,Xvector,Yvector,Zvector) est utilisée pour faire pivoter un objet autour d'un axe. Vous serez amener faire un usage intensif de cette commande... Angle est un nombre, habituellement stocké dans une variable, qui représente le déplacement apporté à l'objet. Les paramètres Xvector, Yvector et Zvector représentent le vecteur mis en oeuvre dans la rotation. Par exemple, le triplet (1,0,0) décrit un vecteur de déplacement d'une unité le long de l'axe des X et vers la droite. Le triplet (-1,0,0,) décrit son opposé: le déplacement se fera vers la gauche.

C'est D. Michael Traub qui m'a fourni l'explication ci-dessus pour ces paramètres Xvector, Yvector and Zvector.

Pour une meilleure compréhension d'une rotation suivant ces axes X, Y et Z, je vais développer ces notions et prendre des exemples

L'axe des X : imaginez que vous travaillez avec une scie circulaire de table. La lame va de gauche à droite (comme pour l'axe des x en OpenGL). Les dents vont donc faire des rotations autour de l'axe X et on aura l'impression que la lame coupe dans le bon sens ou dans le sens contraire. Lorsqu'on fait une rotation sur l'axe des X en OpenGL, cela aura le même effet.

L'axe des Y - Imaginez-vous debout en plein milieu d'un champ. Une énorme tornade arrive droit sur vous. Le centre de la tornade (axe de rotation) va du ciel au sol (en haut et en bas, exactement comme l'axe Y d'OpenGL). La poussière et les débris pris dans la tornade tournent autour de cet axe Y (centre de la tornade) de gauche à droite ou de droite à gauche. Faire défiler un objet sur l'axe Y d'OpenGL permet d'avoir le même effet.

L'axe des Z - Imaginez vous regardant un ventilateur de face. Le centre du ventilateur point vers vous et derrière vous comme s'il vous traversait (exactement comme l'axe z d'OpenGL). Les pales du ventilateur tournent autour de l'axe Z (centre du ventilateur) dans le sens des aiguilles d'une montre (ou dans le sens inverse). C'est la même chose que de faire tourner un objet selon cet axe Z avec OpenGL.

Alors, dans la ligne de code qui suit, si rtri vaut 7, on observera une rotation de 7 unités autour de l'axe Y (de gauche à droite). Vous pouvez expérimenter à votre convenance: par exemple changez le 0.0f en 1.0f, et le 1.0f en 0.0f pour observer la rotation du triangle sur les axes X et Y en même temps.

Il est important de noter que les rotations sont indiquées en degrés. Si rtri a une valeur de 10, la rotation sera de 10 degrés suivant l'axe des Y.

Rotation du triangle
Sélectionnez
glRotatef(rtri,0.0f,1.0f,0.0f);              // Rotation du Triangle sur l'axe Y ( NOUVEAU )

La portion de code suivante n'a pas changé. Elle dessine un triangle aux couleurs mélangées par lissage. Le triangle sera tracé sur le coté gauche de l'écran. On lui appliquera une rotation suivant l'axe Y afin de le faire pivoter de gauche à droite.

Rendu du triangle
Sélectionnez
glBegin(GL_TRIANGLES);               // Debut du tracé du Triangle
    glColor3f(1.0f,0.0f,0.0f);       // Reglage de la couleur : rouge
    glVertex3f( 0.0f, 1.0f, 0.0f);   // Definition du sommet haut
    glColor3f(0.0f,1.0f,0.0f);       // Reglage de la couleur : vert
    glVertex3f(-1.0f,-1.0f, 0.0f);   // Definition du sommet gauche
    glColor3f(0.0f,0.0f,1.0f);       // Reglage de la couleur : bleu
    glVertex3f( 1.0f,-1.0f, 0.0f);   // Definition du sommet droit
glEnd();                             // Fin du trace

Vous constaterez dans le code ci-dessous, l'ajout d'un appel à glLoadIdentity(). Cela permet de réinitialiser la vue. Si nous ne le faisions pas, un déplacement d'un objet après sa rotation aboutirait à des résultats imprévisibles. En effet, comme l'axe aura bougé, il ne pointe plus dans la direction que l'on pense. Ainsi, un déplacement à gauche suivant l'axe X peut aboutir à déplacement vers le bas, ou vers le haut, selon la rotation appliquée préalablement sur chaque axe. Essayez sans cet appel à glLoadIdentity() pour voir ce que je veux dire...

Une fois la scène réinitialisée, X va de gauche à droite, Y de bas en haut et Z d'avant en arrière, nous pouvons préparer le déplacement. Vous noterez que le déplacement à droite est de 1.5 unité au lieu de 3.0 dans la précédente leçon. En effet, à la réinitialisation de l'écran, le point de référence est repositionné au centre de l'écran. De fait, on n'est plus positionné à 1.5 unité à gauche du centre, on est revenu au point "zéro". En conséquence, pour aller à 1.5 unité à droite du point zéro, le trajet {gauche->centre} ayant déjà été réalisé, le déplacement restant se limite à 1.5 unité vers la droite. Au total, le déplacement aura bien été de 3.0, dont 1.5 à votre charge.

Après le déplacement aux nouvelles coordonnées sur le coté droit de l'écran, on effectue la rotation du polygone suivant l'axe des X. Cela provoque le défilement vertical du carré.

Debut du rendu pour le carre
Sélectionnez
glLoadIdentity();                 // Reinitialisation de la matrice Current Modelview
glTranslatef(1.5f,0.0f,-6.0f);    // Deplacement: gauche 1.5 , intérieur Six Unites
glRotatef(rquad,1.0f,0.0f,0.0f);  // Pivote le carré sur l'axe des X ( NOUVEAU )

Cette portion du code est inchangée. Elle trace un carré bleu. Ce carré sera dessiné sur la partie droite de l'écran, à l'endroit où il pivotera.

Rendu du carre
Sélectionnez
glColor3f(0.5f,0.5f,1.0f);          // Choisissons une belle teinte bleutee
glBegin(GL_QUADS);                  // Debut du trace du carre
    glVertex3f(-1.0f, 1.0f, 0.0f);  // Angle superieur gauche
    glVertex3f( 1.0f, 1.0f, 0.0f);  // Angle superieur droit
    glVertex3f( 1.0f,-1.0f, 0.0f);  // Angle inferieur droit
    glVertex3f(-1.0f,-1.0f, 0.0f);  // Angle inferieur gauche
glEnd();                            // Fin du trace

Les 2 lignes suivantes sont nouvelles. Pensez à rtri et à rquad comme à des containers. Au début du programme nous les avons créé (GLfloat rtri, et GLfloat rquad). Après leur construction, il n'y a rien dans ces containers. Le première de ces nouvelles lignes AJOUTE 0.2 dans le container concerné. Ainsi, à chaque fois que nous évaluerons la valeur dans le container rtri après le passage dans cette section du code, elle aura été augmentée de 0.2. De la même manière, la valeur du container rquad aura diminué de 0.15. Diminuer plutôt qu'augmenter la valeur d'un container provoque l'inversion du sens de déplacement.

Essayez de changer le signe + en - dans cette ligne, et observez comment l'objet se déplace dans l'autre direction. Essayez aussi de changer les valeurs (0.2 en 1.0 par exemple): plus la valeur est importante et plus le déplacement est rapide; et inversement.

Gestion de la rotation
Sélectionnez
rtri+=0.2f;     // Augmente la variable de rotation du triangle ( NOUVEAU )
rquad-=0.15f;   // Diminue la variable de rotation du carre ( NOUVEAU )
return TRUE;    // On continue
}

Enfin, modification du code pour basculer du mode fenêtré vers le mode plein écran pour que le titre de la fenêtre soit correct.

Gestion de la touche F1
Sélectionnez
if (keys[VK_F1])             // Appui sur la touche F1 ?
{
    keys[VK_F1]=FALSE;       // Si oui, annulation de la touche enfoncee
    KillGLWindow();          // Suppression de la fenetre courante
    fullscreen=!fullscreen;  // Bascule entre les mode plein ecran et fenetre
    // Re-creation de notre fenetre OpenGL ( Modifiée )
    if (!CreateGLWindow("Le Tutoriel sur la Rotation de NeHe",640,480,16,fullscreen))
    {
        return 0;            // Quitter si la fenetre n'a pas ete creee
    }
}

Dans cette partie, j'ai tenté d'expliquer avec le plus de détails possible, comment faire pivoter les objets autour d'un axe. A vous d'expérimenter: tentez de déplacer les objets sur l'axe Z, sur l'axe Y, sur les 3 axes :-) Si vous avez des commentaires ou des interrogations, merci de m'envoyer un mail. Si vous pensez qu'un commentaire est incorrect ou que certaines portions du code peuvent être améliorées, faites le moi savoir. Je désire produire le meilleur tutoriel possible sur OpenGL. Je suis donc intéressé par votre retour d'expérience.

Jeff Molofee ( NeHe )

III. Téléchargements

Compte tenu du nombre de versions de codes sources pour les tutoriels nehe, nous les laissons en anglais. En principe, si vous avez compris le code présenté dans ce tutoriel (et les tutoriels antérieurs), vous n'aurez pas de mal à le comprendre :

  • ASM (Conversion par Foolman)
  • BeOS (Conversion par Rene Manqueros)
  • Borland C++ Builder 6 (Conversion par Christian Kindahl)
  • C# (Conversion par Sabine Felsinger)
  • Code Warrior 5.3 (Conversion par Scott Lupton)
  • CygWin (Conversion par Stephan Ferraro)
  • D (Conversion par Familia Pineda Garcia)
  • Delphi (Conversion par Michal Tucek)
  • Dev C++ (Conversion par Dan)
  • Euphoria (Conversion par Evan Marshall)
  • Game GLUT (Conversion par Milikas Anastasios)
  • Genu (Conversion par Louis-Charles Dumais)
  • GLUT (Conversion par Andy Restad)
  • Irix (Conversion par Lakmal Gunasekara)
  • Java (Conversion par Jeff Kirby)
  • Java/SWT (Conversion par Victor Gonzalez)
  • Jedi-SDL (Conversion par Dominique Louis)
  • JOGL (Conversion par Kevin J. Duling)
  • LCC Win32 (Conversion par Robert Wishlaw)
  • Linux (Conversion par Richard Campbell)
  • Linux GLX (Conversion par Mihael Vrbanec)
  • Linux SDL (Conversion par Ti Leggett)
  • LWJGL (Conversion par Mark Bernard)
  • Mac OS (Conversion par Anthony Parker)
  • Mac OS X/Cocoa (Conversion par Bryan Blackburn)
  • MASM (Conversion par Nico (Scalp))
  • Pelles C (Conversion par Pelle Orinius)
  • Perl (Conversion par Cora Hussey)
  • Power Basic (Conversion par Angus Law)
  • Python (Conversion par John Ferguson)
  • REALBasic (Conversion par Thomas J. Cunningham)
  • Scheme (Conversion par Jon DuBois)
  • Solaris (Conversion par Lakmal Gunasekara)
  • VB.NET CsGL (Conversion par X)
  • Visual Basic (Conversion par Ross Dawson)
  • Visual Fortran (Conversion par Jean-Philippe Perois)
  • Visual Studio
  • Visual Studio NET (Conversion par Grant James)


Première pyramide et premier cube

  • I. Contributions
  • II. Tutoriel
  • III. Téléchargements
  • IV. Remerciements
  • V. Liens

A partir du précédent tutoriel, nous allons maintenant créer de vrais objets 3D, plutôt que des objets 2D dans un monde 3D. Nous allons effectuer cela en ajoutant des côtés à gauche, à droite et à l'arrière du triangle ainsi que des côtés à gauche, à droite, à l'arrière, au-dessus et en-dessous du carré. Par cette opération, le triangle deviendra une pyramide - plus précisément un tétraèdre - et le carré un cube.

I. Contributions

Nous recherchons toujours des traducteurs pour finir ce projet de traduction des articles de NeHe. Votre aide serait appréciée ! Vous êtes intéressé ? Alors contactez-nous à l'adresse suivante : nehe@redaction-developpez.com .

II. Tutoriel

On va mélanger les couleurs sur la pyramide, créant un objet coloré tandis que pour le carré, on colorera chaque face avec une couleur différente.

La fonction DrawGLScene
Sélectionnez
int DrawGLScene(GLvoid)             // C'est ici que nous ferons le dessin
{
   // Efface l'ecran, le tampon de couleur et le tampon de profondeur
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glLoadIdentity();                // Reinitialise la vue
   glTranslatef(-1.5f,0.0f,-6.0f);  // Se deplace vers la gauche vers l'interieur de l'ecran
   glRotatef(rtri,0.0f,1.0f,0.0f);  // Tourne la pyramide sur son axe Y
   glBegin(GL_TRIANGLES);           // Debut du dessin de la pyramide

Certains d'entre vous auront repris le code du tutoriel précédent et fabriqué leurs propres objets 3D. Une des questions que je me posais fréquemment était "Comment cela se fait-il que mes objets ne tournent pas sur leurs axes ? Il semble qu'ils tournent partout sur l'écran". Afin de faire tourner votre objet autour d'un axe, celui-ci doit être créé autour de cet axe. Rappelez-vous que le centre de tout objet doit être en 0 selon X, 0 selon Y et 0 selon Z.

Le code suivant va créer la pyramide autour d'un axe central. Le sommet de la pyramide est à une distance de 1 du centre tout comme la base de la pyramide. Le sommet est au milieu du plan, les sommets de la base sont à une distance de 1 du centre.

Remarquez que tous les triangles sont dessinés dans le sens trigonométrique. Ceci est important et sera expliqué dans un prochain tutoriel; pour l'instant, sachez simplement que c'est une bonne habitude de dessiner dans le sens horaire ou trigonométrique, mais que vous ne devriez pas mélanger les 2, à moins d'avoir de solides arguments.

On commence par dessiner la face avant. Puisque toutes les faces partagent le sommet supérieur, on va le dessiner en rouge sur tous les triangles. La couleur des points du bas sera alternée. La face avant aura un point gauche vert et un point droit bleu. Puis le triangle sur le côté droit aura un point gauche bleu et un point droit vert. En alternant les 2 couleurs du bas sur chaque face, on aura un point commun au bas de chaque face.

Le rendu de la première face de la pyramide
Sélectionnez
      glColor3f(1.0f,0.0f,0.0f);      // Rouge
      glVertex3f( 0.0f, 1.0f, 0.0f);  // Haut du triangle de face
      glColor3f(0.0f,1.0f,0.0f);      // Vert
      glVertex3f(-1.0f,-1.0f, 1.0f);  // Bas gauche du triangle de face
      glColor3f(0.0f,0.0f,1.0f);      // Bleu
      glVertex3f( 1.0f,-1.0f, 1.0f);  // Bas droit du triangle de face

Maintenant, au tour de la face droite. Remarquez que les 2 points du bas sont dessinés à une distance de 1 à la droite du centre, et le sommet est dessiné à une distance de 1 sur l'axe des y, et au milieu de l'axe des x, ce qui incline la face depuis le point central en haut vers le coin droit du bas de l'écran.

Remarquez que le point gauche est cette fois-ci dessiné en bleu. De cette manière, il sera de la même couleur que le coin droit de la face avant. Ainsi on aura un dégradé de bleu depuis ce coin vers la face avant et vers celle de droite.

Remarquez aussi que les 3 faces restantes sont inclues dans le même glBegin(GL_TRIANGLES) et glEnd() que la première face. Puisque nous faisons cet objet entièrement avec des triangles, OpenGL saura que chaque triplet de points que nous tracerons définira un triangle. Une fois que 3 points sont tracés, s'il y a 3 points suivants, il considèrera qu'il y a un nouveau triangle à tracer. Si vous ajoutez 4 points au lieu de 3, OpenGL va tracer les 3 premiers comme un triangle et considèrera le 4ème comme le départ d'un nouveau triangle. Il ne dessinera pas un quadrilatère, donc soyez sûr que vous n'ajoutez pas de points supplémentaires par accident.

Le rendu de la deuxième face de la pyramide
Sélectionnez
      glColor3f(1.0f,0.0f,0.0f);           // Rouge
      glVertex3f( 0.0f, 1.0f, 0.0f);       // Haut du triangle (Droit)
      glColor3f(0.0f,0.0f,1.0f);           // Bleu
      glVertex3f( 1.0f,-1.0f, 1.0f);       // Gauche du triangle (Droit)
      glColor3f(0.0f,1.0f,0.0f);           // Vert
      glVertex3f( 1.0f,-1.0f, -1.0f);      // Droite du triangle (Droit)

Passons à la face arrière, avec à nouveau un changement de couleur. Le point gauche est vert puisque le coin commun avec la face droite est vert.

Le rendu de la troisième face de la pyramide
Sélectionnez
      glColor3f(1.0f,0.0f,0.0f);      // Rouge
      glVertex3f( 0.0f, 1.0f, 0.0f);  // Haut du triangle (Arrière)
      glColor3f(0.0f,1.0f,0.0f);      // Vert
      glVertex3f( 1.0f,-1.0f, -1.0f); // Gauche du triangle (Arrière)
      glColor3f(0.0f,0.0f,1.0f);      // Bleu
      glVertex3f(-1.0f,-1.0f, -1.0f); // Droite du triangle (Arrière)

Enfin, nous dessinons la face gauche. On échange les couleurs une dernière fois. Le point gauche est bleu et correspond au point droit de la face arrière. Le point droit est vert et correspond au point gauche de la face avant.

Nous avons fini de dessiner la pyramide. Comme celle-ci ne tourne que selon l'axe Y, nous ne verrons jamais la face du dessous, donc il n'y a pas besoin de dessiner cette face de la pyramide. Si vous désirez tester, essayez d'ajouter un fond en utilisant un quadrilatère, puis tournez sur l'axe X pour voir si vous l'avez fait correctement. Assurez-vous que les couleurs utilisées à chaque coin du quadrilatère correspondent aux couleurs utilisées aux 4 sommets inférieurs de la pyramide.

Le rendu de la quatrième face de la pyramide
Sélectionnez
      glColor3f(1.0f,0.0f,0.0f);       // Rouge
      glVertex3f( 0.0f, 1.0f, 0.0f);   // Haut du triangle (Gauche)
      glColor3f(0.0f,0.0f,1.0f);       // Bleu
      glVertex3f(-1.0f,-1.0f,-1.0f);   // Gauche du triangle (Gauche)
      glColor3f(0.0f,1.0f,0.0f);       // Vert
      glVertex3f(-1.0f,-1.0f, 1.0f);   // Droite du triangle (Gauche)
   glEnd();                            // Fin du dessin de la pyramide

Maintenant, nous allons dessiner le cube. Il est constitué de 6 quads. Chacun des quads est dessiné dans le sens trigonométrique, ce qui signifie que le premier point sera le point en haut à droite, puis le point en haut à gauche, le point en bas à gauche et enfin le point en bas à droite. Lorsque nous dessinerons la face arrière, il semblera que nous dessinons dans le sens horaire, mais rappelez-vous que si nous étions derrière le cube en le regardant, le côté gauche est en fait le vrai coté droit, et inversement.

Remarquez que nous plaçons le cube un peu plus loin dans l'écran dans cette leçon. Ainsi, la taille du cube apparaîtra plus proche de celle de la pyramide. Si elle n'était déplacée que de 6 unités dans l'écran, elle apparaîtrait bien plus grande que la pyramide, et une partie pourrait être rognée par les côtés de l'écran. Vous pouvez modifier ce paramètre pour voir son influence sur le déplacement et la taille du cube. Ceci est dû à la perspective. Les objets à une certaine distance doivent apparaître plus petits.

Le début du rendu du cube
Sélectionnez
   glLoadIdentity();
   glTranslatef(1.5f,0.0f,-7.0f);    // Déplacement à droite dans l'écran
   glRotatef(rquad,1.0f,1.0f,1.0f);  // Tourne le cube selon X, Y & Z
   glBegin(GL_QUADS);                // Début du dessin du cube

Nous commençons par dessiner le dessus du cube. On va monter d'une unité depuis le centre du cube. Remarquez que l'axe Y est toujours à 1. Nous allons maintenant dessiner un quadrilatère sur le plan Z, vers l'intérieur de l'écran. Nous commençons par tracer le point en haut à droite sur la face supérieure du cube. Ce point sera à une unité vers la droite et une unité vers l'intérieur de l'écran. Le second point sera à une unité vers la gauche et vers l'intérieur de l'écran. Maintenant, nous devons dessiner le bas du quadrilatère vers l'extérieur. À cet effet, à la place de prendre une unité vers l'intérieur de l'écran, on prendra une unité vers l'extérieur. Logique, non ?

Le rendu de la première face du cube
Sélectionnez
      glColor3f(0.0f,1.0f,0.0f);      // Couleur verte
      glVertex3f( 1.0f, 1.0f,-1.0f);  // Haut droit du quadrilatere (Haut)
      glVertex3f(-1.0f, 1.0f,-1.0f);  // Haut gauche du quadrilatere (Haut)
      glVertex3f(-1.0f, 1.0f, 1.0f);  // Bas gauche du quadrilatere (Haut)
      glVertex3f( 1.0f, 1.0f, 1.0f);  // Bas droit du quadrilatere (Haut)

La face du dessous est dessinée exactement de la même manière, mais comme c'est la face du dessous, elle est dessinée une unité vers le bas depuis le centre du cube. Remarquez que la valeur en Y est toujours égale à -1. Si nous étions sous le cube, regardant vers le quadrilatère qui constitue le fond, vous remarqueriez que le sommet en haut à droite est le sommet le plus proche de la personne qui regarde, donc à la place de dessiner les points éloignés en premier, nous dessinons le point le plus proche pour commencer, puis le point à gauche le plus proche puis nous rentrons dans l'écran pour dessiner les points restants.

Si vous ne faites vraiment pas attention à l'ordre dans lequel le polygone est dessiné - horaire ou trigonométrique -, vous copiez simplement le même code pour le quadrilatère du dessus, vous le déplacez en y=-1 et cela marchera, mais ignorer l'ordre du quadrilatère peut causer des résultats bizarres une fois que vous voulez faire des choses plus exotiques telles que l'application de textures.

Le rendu de la deuxième face du cube
Sélectionnez
      glColor3f(1.0f,0.5f,0.0f);      // Couleur orange
      glVertex3f( 1.0f,-1.0f, 1.0f);  // Haut droit du quadrilatere (Bas)
      glVertex3f(-1.0f,-1.0f, 1.0f);  // Haut gauche du quadrilatere (Bas)
      glVertex3f(-1.0f,-1.0f,-1.0f);  // Bas gauche du quadrilatere (Bas)
      glVertex3f( 1.0f,-1.0f,-1.0f);  // Bas droit du quadrilatere (Bas)

Maintenant, ,nous allons tracer le quadrilatère de la face avant. On se déplace d'une unité vers l'écran depuis le centre pour tracer cette face. Remarquez que la valeur selon Z vaut toujours 1. Dans la pyramide, cela n'était pas toujours le cas. Au sommet, il valait 0. Si vous essayez de changer la valeur en la mettant à 0, vous constaterez que le point que vous avez bougé se déplace dans l'écran. Ce n'est pas vraiment ce que nous voudrions faire maintenant.

Le rendu de la troisième face du cube
Sélectionnez
       glColor3f(1.0f,0.0f,0.0f);       // Couleur rouge
       glVertex3f( 1.0f, 1.0f, 1.0f);   // Haut droit du quadrilatere (Avant)
       glVertex3f(-1.0f, 1.0f, 1.0f);   // Haut gauche du quadrilatere (Avant)
       glVertex3f(-1.0f,-1.0f, 1.0f);   // Bas gauche du quadrilatere (Avant)
       glVertex3f( 1.0f,-1.0f, 1.0f);   // Bas droit du quadrilatere (Avant)

La face arrière est un quadrilatère identique à la face avant, mais placé plus à l'arrière de l'écran. Remarquez que la valeur selon Z vaut maintenant toujours -1. De même, l'ordre des sommets du quadrilatère est inversé.

Le rendu de la troisième face du cube
Sélectionnez
      glColor3f(1.0f,1.0f,0.0f);      // Couleur jaune
      glVertex3f( 1.0f,-1.0f,-1.0f);  // Haut droit du quadrilatere (Arriere)
      glVertex3f(-1.0f,-1.0f,-1.0f);  // Haut gauche du quadrilatere (Arriere)
      glVertex3f(-1.0f, 1.0f,-1.0f);  // Bas gauche du quadrilatere (Arriere)
      glVertex3f( 1.0f, 1.0f,-1.0f);  // Bas droit du quadrilatere (Arriere)

Maintenant, il ne reste plus que deux autres quads à dessiner et nous aurons fini. Comme d'habitude, vous noterez que la valeur sur un axe est identique pour tous les points. Ici, ce sera sur l'axe X que les valeurs vaudront toujours -1. Ceci s'explique par le fait qu'on dessine à gauche du centre puisque ce sera la face gauche.

Le rendu de la quatrième face du cube
Sélectionnez
       glColor3f(0.0f,0.0f,1.0f);      // Couleur bleue
       glVertex3f(-1.0f, 1.0f, 1.0f);  // Haut droit du quadrilatere (Gauche)
       glVertex3f(-1.0f, 1.0f,-1.0f);  // Haut gauche du quadrilatere (Gauche)
       glVertex3f(-1.0f,-1.0f,-1.0f);  // Bas gauche du quadrilatere (Gauche)
       glVertex3f(-1.0f,-1.0f, 1.0f);  // Bas droit du quadrilatere (Gauche)

Ceci est la dernière face pour compléter le cube. La valeur sur l'axe X est toujours de 1. Le dessin se fait dans le sens trigonométrique. Si vous voulez, vous pouvez laisser de côté cette face et faire du cube une boîte

Si vous vous sentez prêt à expérimenter, vous pouvez essayer de changer la couleur des coins sur le cube pour faire des mélanges comme dans le cas de la pyramide. Vous pouvez voir un tel exemple en téléchargeant la première démo OpenGL d'Evil depuis la page web de nehe. Lancez-la et appuyez sur TAB. Vous verrez un joli cube colorié avec des couleurs défilant sur les faces.

Le rendu de la cinquième face du cube
Sélectionnez
      glColor3f(1.0f,0.0f,1.0f);      // Couleur violette
      glVertex3f( 1.0f, 1.0f,-1.0f);  // Haut droit du quadrilatere (Droite)
      glVertex3f( 1.0f, 1.0f, 1.0f);  // Haut gauche du quadrilatere (Droite)
      glVertex3f( 1.0f,-1.0f, 1.0f);  // Bas gauche du quadrilatere (Droite)
      glVertex3f( 1.0f,-1.0f,-1.0f);  // Bas droit du quadrilatere (Droite)
   glEnd();                           // Fin du dessin des quadrilateres

   rtri+=0.2f;                        // Incremente la variable de la rotation du triangle
   rquad-=0.15f;                      // Decremente la variable de rotation des quads

   return TRUE;                       // On continue
}

A la fin de ce tutoriel, vous devriez avoir une meilleure compréhension de la création des objets en 3D. Vous devez imaginer l'écran OpenGL comme une immense pièce sur un papier-graphe, avec plusieurs couches transparentes derrière, presque tel un cube géant constitué de points. Certains des points bougent de gauche à droite, d'autres de haut en bas, et d'autres s'éloignent dans le cube. Si vous pouvez visualiser la profondeur dans l'écran, vous ne devriez pas avoir de problème pour créer de nouveaux objets 3D.

Si vous avez des difficultés à comprendre l'espace 3D, ne soyez pas frustrés, cela peut se révéler difficile à saisir correctement dès le départ. Un objet tel que le cube est un bon exemple pour apprendre. Si vous avez remarqué, la face arrière est dessinée exactement comme la face avant, elle n'est juste plus dans l'écran. Jouez un peu avec le code, et si vous ne comprenez vraiment pas, envoyez-moi un email et j'essaierai de répondre à vos questions.

Jeff Molofee ( NeHe )

III. Téléchargements▲

Compte tenu du nombre de versions de codes sources pour les tutoriels nehe, nous les laissons en anglais. En principe, si vous avez compris le code présenté dans ce tutoriel (et les tutoriels antérieurs), vous n'aurez pas de mal à le comprendre :

  • ASM (Conversion par Foolman)
  • BeOS (Conversion par Rene Manqueros)
  • Borland C++ Builder 6 (Conversion par Christian Kindahl)
  • C# (Conversion par Sabine Felsinger)
  • Code Warrior 5.3 (Conversion par Scott Lupton)
  • CygWin (Conversion par Stephan Ferraro)
  • D (Conversion par Familia Pineda Garcia)
  • Delphi (Conversion par Michal Tucek)
  • Dev C++ (Conversion par Dan)
  • Euphoria (Conversion par Evan Marshall)
  • Game GLUT (Conversion par Milikas Anastasios)
  • GLUT (Conversion par Andy Restad)
  • Irix (Conversion par Lakmal Gunasekara)
  • Java (Conversion par Jeff Kirby)
  • Java/SWT (Conversion par Victor Gonzalez)
  • Jedi-SDL (Conversion par Dominique Louis)
  • JOGL (Conversion par Kevin J. Duling)
  • LCC Win32 (Conversion par Robert Wishlaw)
  • Linux (Conversion par Richard Campbell)
  • Linux GLX (Conversion par Mihael Vrbanec)
  • Linux SDL (Conversion par Ti Leggett)
  • LWJGL (Conversion par Mark Bernard)
  • Mac OS (Conversion par Anthony Parker)
  • Mac OS X/Cocoa (Conversion par Bryan Blackburn)
  • MASM (Conversion par Nico (Scalp))
  • Pelles C (Conversion par Pelle Orinius)
  • Perl (Conversion par Cora Hussey)
  • Power Basic (Conversion par Angus Law)
  • Python (Conversion par Tony Colston)
  • REALBasic (Conversion par Thomas J. Cunningham)
  • Scheme (Conversion par Jon DuBois)
  • Solaris (Conversion par Lakmal Gunasekara)
  • VB.NET CsGL (Conversion par X)
  • Visual Basic (Conversion par Ross Dawson)
  • Visual Fortran (Conversion par Jean-Philippe Perois)
  • Visual Studio
  • Visual Studio NET (Conversion par Grant James)


Les textures

  • Les textures

    • I. Contributions
    • II. Tutoriel
      • II-A. Inclusions et déclarations
      • II-B. La fonction LoadBMP
      • II-C. La fonction LoadGLTextures
      • II-D. La fonction InitGL
      • II-E. La fonction DrawGLScene
      • II-F. Conclusion
    • III. Téléchargements
    • IV. Remerciements
    • V. Liens

    Nous allons voir à quel point l'application de texture peut s'avérer avantageuse. Imaginons que vous vouliez faire voler un missile d'un bout à l'autre de l'écran. Avant ce tutoriel, nous aurions probablement créé le missile entièrement en polygones, avec des couleurs fantaisistes. Grâce à l'application de texture, vous pouvez prendre une vraie photo d'un missile, et faire "voler" l'image d'un côté à l'autre de l'écran. D'après vous lequel donnera le meilleur résultat ? Une photographie ou un objet formé de triangles et de carrés ? En utilisant l'application de texture, cela ne va pas seulement rendre mieux, mais votre programme tournera plus vite. La texture affichée du missile sera seulement un quadrilatère se déplaçant sur l'écran. Un missile formé de polygones pourrait être fait de centaines ou de milliers de polygones. Une seule texture affichée nécessitera alors beaucoup moins de puissance de calcul.

    I. Contributions

    Nous recherchons toujours des traducteurs pour finir ce projet de traduction des articles de NeHe. Votre aide serait appréciée ! Vous êtes intéressé ? Alors contactez-nous à l'adresse suivante : nehe@redaction-developpez.com .

    II. Tutoriel

    II-A. Inclusions et déclarations

    Commençons par ajouter cinq nouvelles lignes de code en-haut de la leçon une. La première nouvelle ligne est #include <stdio.h>. L'ajout de cet en-tête nous permettra de travailler avec les fichiers. Nous devons inclure cette ligne afin d'utiliser la fonction fopen() plus tard dans le code. Ensuite nous ajoutons trois nouvelles variables flottantes : xrot, yrot et zrot. Ces variables seront utilisées pour faire tourner le cube sur l'axe des X, l'axe des Y et l'axe des Z. La dernière ligne GLuint texture[1] met de côté l'espace mémoire pour une texture. Si vous voulez charger plus d'une texture, vous pouvez changer l'index 1 selon le nombre de textures que vous souhaitez charger.

    En-têtes et déclarations
    Sélectionnez
    #include <windows.h>        // En-tete pour Windows
#include <stdio.h>          // En-tete pour les entrees/sorties standards (NOUVEAU)
#include <gl\gl.h>          // En-tete pour la bibliotheque OpenGL32
#include <gl\glu.h>         // En-tete pour la biblioteque GLu32
#include <gl\glaux.h>       // En-tete pour la bibiotheque GLaux

HDC       hDC=NULL;         // Contexte prive du systeme GDI
HGLRC     hRC=NULL;         // Contexte de rendu permanent
HWND      hWnd=NULL;        // Contient notre descripteur de fenêtre
HINSTANCE hInstance;        // Sauve l'instance de l'application
bool      keys[256];        // Tableau utiliser pour les routines du clavier
bool      active=TRUE;      // Drapeau d'activation de la fenetre
bool      fullscreen=TRUE;  // Drapeau pour le mode plein ecran
GLfloat   xrot;             // Rotation en X (NOUVEAU)
GLfloat   yrot;             // Rotation en Y (NOUVEAU)
GLfloat   zrot;             // Rotation en Z (NOUVEAU)
GLuint    texture[1];       // Stockage d'une texture (NOUVEAU)
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Declaration de WndProc

    II-B. La fonction LoadBMP▲

    Maintenant juste après le code ci-dessus, et avant le ReSizeGLScene(), nous allons ajouter la section de code suivante. Le but de ce code est de charger un fichier bitmap. Si le fichier n'existe pas, NULL est retourné signifiant que la texture n'a pas pu être ouverte. Avant de commencer à expliquer le code, il y a quelques petites choses importantes que vous devez savoir sur les images que vous prévoyez d'utiliser comme textures. La hauteur et la largeur de l'image doivent être une puissance de deux. La largeur et la hauteur doivent être d'au moins 64 pixels, et pour des raisons de compatibilité, ne devraient pas être plus grandes que 256 pixels. Si l'image que vous voulez utiliser n'a pas 64, 128 ou 256 pixels en hauteur et en largeur, redimensionnez-la avec un programme de retouche d'image. Il existe des moyens pour contourner cette limitation, mais pour le moment nous nous collerons seulement aux tailles de texture standards.

    Voici l'image que nous allons charger :

    Image non disponible
    L'image qui sera la texture

    La première chose que nous faisons est de créer un descripteur de fichier. Un descripteur est une valeur utilisée pour identifier une ressource, de manière à ce que notre programme puisse y accéder. Pour commencer nous initialisons le descripteur à la valeur NULL.

    Fonction de chargement d'un BMP
    Sélectionnez
    AUX_RGBImageRec *LoadBMP(char *Filename)  // Charge une image bitmap
{
   FILE *File=NULL;                       // Descripteur de fichier

    Nous vérifions ensuite qu'un nom de fichier a vraiment été passé en paramètre. La personne peut avoir utilisé LoadBMP() sans spécifier de fichier à charger, nous sommes donc obligés de le contrôler. Nous n'allons pas essayer de charger rien du tout :).

    Test du nom de fichier
    Sélectionnez
       if (!Filename)    // Verifie qu'un nom de fichier a ete donne
    {
       return NULL;  // Si non, retourne NULL
    }

    Si un nom de fichier a été donné, il faut maintenant vérifier qu'il existe bien. La ligne ci-dessous tente d'ouvrir le fichier.

    Test d'existence du fichier
    Sélectionnez
       File=fopen(Filename,"r");  // Controle si le fichier existe

    Si nous n'arrivons pas à ouvrir le fichier, c'est qu'il n'existe probablement pas. Nous fermons le fichier avec fclose(File) puis nous retournons les données de l'image. auxDIBImageLoad(Filename) lit les données.

    Renvoie des données du bitmap
    Sélectionnez
       if (File)                              // Est-ce que le fichier existe ?
    {
       fclose(File);                      // Ferme le descripteur
       return auxDIBImageLoad(Filename);  // Ouvre le bitmap et renvoie un pointeur
    }

    Si nous n'avons pas pu ouvrir le fichier, nous renvoyons NULL. Cela signifie que le ficher n'a pas pu être ouvert. Plus tard dans le programme nous vérifierons que le fichier a bien été chargé. S'il ne l'était pas, nous quitterions le programme avec un message d'erreur.

    Retour en cas d'échec
    Sélectionnez
       return NULL;  // Retourne NULL si le chargement a echoue
}

    II-C. La fonction LoadGLTextures▲

    Ceci est la partie du code qui permet d'ouvrir un bitmap (en appelant le code ci-dessus) et qui le convertit en texture.

    Fonction de création de la texture
    Sélectionnez
    int LoadGLTextures()  // Ouvre un bitmap et le convertit en texture
{

    Nous définissons une variable appelée Status. Nous utiliserons cette variable pour savoir si nous avons pu charger le bitmap et créer une texture. Nous définissons par défaut Status à FALSE, ce qui signifie que rien n'a été ouvert ou créé.

    Variable de statut
    Sélectionnez
       int Status=FALSE;  // Indicateur du statut

    Maintenant nous créons une structure d'image pour y stocker notre bitmap. L'enregistrement va garder la taille, la largeur, et les données du bitmap.

    Structeur d'image
    Sélectionnez
       AUX_RGBImageRec *TextureImage[1];  // Cree une structure d'image

    Nous vidons cette structure juste pour être sûr qu'elle est vide

    Initialisation par défaut
    Sélectionnez
        memset(TextureImage,0,sizeof(void *)*1);  // Initialise le pointeur à NULL

    Maintenant nous chargeons le bitmap et le convertissons en texture. TextureImage[0]=LoadBMP("Data/NeHe.bmp") va solliciter le code de LoadBMP(). Le fichier Nehe.bmp du répertoire Data sera chargé. Si tout va bien, les données de l'image seront stockées dans TextureImage[0], Status sera mis à TRUE, et nous pourrons commencer à construire notre texture.

    Remplissage avec les données du BMP
    Sélectionnez
       // Charge le bitmpap, controle les erreurs et quitte si le bitmap n'a pas ete trouve
   if (TextureImage[0]=LoadBMP("Data/NeHe.bmp"))
   {
       Status=TRUE;  // Met la veriable Status à TRUE

    Maintenant que nous avons chargé les données de l'image dans TextureImage[0], nous pouvons créer une texture utilisant ces données. La première ligne glGenTextures(1, &texture[0]) dit à OpenGL que nous voulons générer un seul nom de texture (augmentez le nombre si vous voulez charger plus d'une texture). Rappelez-vous qu'au tout début de ce tutoriel nous avons créé la place pour une texture avec la ligne de code GLuint texture[1]. Bien que vous pensiez que la première texture serait stockée dans &texture[1] au lieu de &texture[0], ce n'est pas le cas. La première zone de stockage réelle est 0. Si nous voulions deux textures nous devrions utiliser GLuint texture[2] et la seconde texture serait stockée dans texture[1].

    La seconde ligne glBindTexture(GL_TEXTURE_2D, texture[0]) dit à OpenGL d'assimiler la texture nommée texture[0] à une cible de texture. Les textures 2D ont une hauteur (sur l'axe des Y) et une largeur (sur l'axe des X). La fonction principale de glBindTexture est d'assigner un nom de texture aux données de cette texture. Dans ce cas nous disons à OpenGL que la mémoire est disponible dans &texture[0]. Quand nous créons la texture, elle sera stockée dans la mémoire référencée dans &texture[0].

    Génération de la texture
    Sélectionnez
          glGenTextures(1, &texture[0]);  // Cree la texture
      // Génération typique de texture utilisant les donnees du bitmap
      glBindTexture(GL_TEXTURE_2D, texture[0]);

    Après nous créons la texture réelle. La ligne suivante dit à OpenGL que la texture sera une texture 2D (GL_TEXTURE_DD). Zéro représente les images du niveau de détail, qui est d'habitude laissé à 0. Trois est le nombre de composantes des données. Puisque l'image se compose de couleur rouge, verte, et bleue, il y a donc trois composantes. TextureImage[0]->sizeX est la largeur de la texture. Si vous en savez la taille, vous pouvez la renseigner ici, mais il est plus simple de laisser l'ordinateur la trouver pour vous. TextureImage[0]->sizeY est la hauteur de la texture. Zéro est le bord. Il est normalement laissé à zéro. GL_RGB indique à OpenGL que les données de l'image que nous utilisons sont composées de données rouge, verte, et bleue dans cet ordre. GL_UNSIGNED_BYTE signifie que les données qui composent l'image sont des bytes non signés, et enfin... TextureImage[0]->data indique à OpenGL où aller chercher les données de la texture. Dans ce cas il pointe sur les données stockées dans l'enregistrement TextureImage[0].

    Crée une texture 2D
    Sélectionnez
          // Genere la texture
      glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX,
         TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);

    Les deux lignes de code suivantes indiquent à OpenGL quel type de filtrage il faut utiliser, lorsque l'image est étirée sur l'écran ou est plus grande que la texture originale (GL_TEXTURE_MAG_FILTER), ou lorsqu'elle est plus petite (GL_TEXTURE_MIN_FILTER) sur l'écran que la texture réelle. J'utilise habituellement GL_LINEAR pour les deux cas. Cela rend une texture douce lorsqu'elle est éloignée ou proche de l'écran. Utiliser GL_LINEAR demande beaucoup de travail pour le GPU et la mémoire de la carte graphique, alors si votre système est lent, vous pouvez utiliser GL_NEAREST. Une texture filtrée avec GL_NEAREST apparaîtra pixellisée si elle est déformée. Vous pouvez aussi essayer une combinaison des deux. Filtrer lorsque l'image est proche, mais pas lorsqu'elle est éloignée.

    Filtrage
    Sélectionnez
          // Filtrage lineaire
      glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
      // Filtrage lineaire
      glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
  }

    Maintenant nous libérons toute la mémoire que nous avons utilisée pour stocker les données du bitmap. Nous vérifions que la structure du bitmap était bien stockée dans TextureImage[0]. Si elle l'était, nous contrôlons que le contenu même du bitmap était bien en mémoire. S'il l'était, nous le supprimons. Ensuite nous libérons la structure d'image en s'assurant que toute la mémoire utilisée soit bien libérée.

    Libération de la mémoire
    Sélectionnez
       if (TextureImage[0])                // Si la texture existe
    {
      if (TextureImage[0]->data)       // Si l'image de la texture existe
       {
         free(TextureImage[0]->data);  // Libere l'image de la texture
       }
      free(TextureImage[0]);           // Libere la structure de l'image
   }

    Pour finir, nous retournons le statut. Si tout était OK, la variable Status sera mise à TRUE. Si quelque chose a échoué, Status sera mis à FALSE.

    Renvoi
    Sélectionnez
       return Status;  // Renvoie le statut
}

    II-D. La fonction InitGL▲

    J'ai ajouté quelques lignes de code dans InitGL. Je reposte l'entière partie du code, il sera donc facile de voir le code que j'y ai ajouté et où je l'ai mis. La première ligne if (!LoadGLTextures()) lance notre fonction LoadGLTexture qui charge le bitmap et crée une texture depuis celui-ci. Si la fonction échoue pour une quelconque raison, la ligne suivante retournera FALSE. Si tout s'est passé correctement et que la texture a été créée, nous faisons l'application de texture (texture mapping). Si vous oubliez d'activer l'application de texture, votre objet apparaîtra normalement en blanc, ce qui n'est bien sûr pas ce que nous voulons.

    Initialisation avec une texture
    Sélectionnez
    int InitGL(GLvoid)                        // Toute la configuration d'OpenGL se met ici
{
   if (!LoadGLTextures())                 // Va dans notre routine de chargement de texture (nouveau)
    {
      // Si la la texture n'a pas ete chargee, retourne FALSE (nouveau)
      return FALSE;
    }
   glEnable(GL_TEXTURE_2D);               // Active l'application de texture (nouveau)
   glShadeModel(GL_SMOOTH);               // Active les ombres douces
   glClearColor(0.0f, 0.0f, 0.0f, 0.5f);  // Fond d'écran noir
   glClearDepth(1.0f);                    // Configuration du tampon de profondeur
   glEnable(GL_DEPTH_TEST);               // Active le test de profondeur
   glDepthFunc(GL_LEQUAL);                // Le type de test de profondeur a faire

   // Pour avoir des jolis calculs de perspective
   glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

   return TRUE;                           // L'initialisation etait OK
}

    II-E. La fonction DrawGLScene▲

    Nous allons maintenant dessiner un cube texturé. Vous pouvez remplacer le code de DrawGLScene avec le code ci-dessous, ou vous pouvez ajouter le nouveau code dans celui de la leçon une. Cette partie sera fortement commentée afin d'assurer une bonne compréhension. Les deux premières lignes glClear() and glLoadIdentity() sont dans le code de la leçon une. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) videra l'écran avec la couleur que nous avons sélectionnée dans InitGL(). Dans notre cas, il sera mis en noir. Le tampon de profondeur sera aussi vidé. La vue sera ensuite mise à zéro avec glLoadIdentity().

    Préparation
    Sélectionnez
    int DrawGLScene(GLvoid)  // C'est ici que nous faisons tout les rendus
{
   // Vide l'ecran et le tampon de profondeur
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glLoadIdentity();               // Remet a zero la matrice courante
   glTranslatef(0.0f,0.0f,-5.0f);  // Deplace dans l'ecran de 5 unites

    Les trois lignes suivantes feront tourner le cube sur l'axe des X, puis l'axe des Y, et enfin l'axe des Z. L'angle de rotation dépendra de la valeur des variables xrot, yrot et zrot.

    Rotation
    Sélectionnez
       glRotatef(xrot,1.0f,0.0f,0.0f);  // Rotation sur l'axe des X
   glRotatef(yrot,0.0f,1.0f,0.0f);  // Rotation sur l'axe des Y
   glRotatef(zrot,0.0f,0.0f,1.0f);  // Rotation sur l'axe des Z

    La ligne suivante sélectionne quelle texture nous voulons utiliser. Si vous voulez utiliser plus d'une texture dans votre scène, vous pouvez choisir la texture en utilisant glBindTexture(GL_TEXTURE_2D, texture[numéro de la texture]). Si vous voulez changer de texture, vous pouvez activer la nouvelle texture avec glBindTexture(). Une chose à savoir est que l'on ne peut pas utiliser cette fonction à l'intérieur de glBegin() et glEnd(), vous devez le faire avant ou après. Observez comment nous employons glBindTexture pour spécifier quelle texture créer et choisir une texture spécifique.

    Sélection de la texture
    Sélectionnez
       glBindTexture(GL_TEXTURE_2D, texture[0]);  // Selectionne notre texture

    Pour appliquer proprement une texture sur un quadrilatère, vous devez vous assurer que la partie supérieure droite de la texture soit appliquée sur la partie supérieure droite du quadrilatère, la partie supérieure gauche de la texture sur la partie supérieure gauche du quadrilatère, etc. Si les coins de la texture ne correspondent pas avec ceux du quadrilatère, l'image pourrait apparaître à l'envers, décalée, ou ne pas être affichée du tout.

    Le premier paramètre de glTexCoord2f est la coordonnée X. 0.0f est le côté gauche de la texture, 0.5f est le milieu et 1.0f est le côté droit. Le second paramètre de glTexCoord2f est la coordonnée Y. 0.0f est le bas de la texture, 0.5f le milieu et 1.0f le haut de la texture.

    Nous savons maintenant que la coordonnée en-haut à gauche de la texture est 0.0f en X et 1.0f en Y, et que le sommet en haut à gauche d'un quadrilatère est -1.0f en X et 1.0f en Y. Maintenant tout ce que vous avez à faire est de faire correspondre les trois autres coordonnées de texture avec les trois coins restants du quadrilatère.

    Essayez de jouer avec les valeurs de X et Y de glTextCoord2f. Changer le 1.0f en 0.5f dessinera seulement la moitié gauche d'une texture de 0.0f (gauche) à 0.5f (milieu de la texture). Changer 0.0f en 0.5f dessinera seulement la moitié droite d'une texture de 0.0f (milieu) à 1.0f (droite).

    Tracé du cube texturé
    Sélectionnez
       glBegin(GL_QUADS);
      // Face de devant
      // En-bas a gauche de la texture et du quadrilatere
      glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
      // En-bas a droite de la texture et du quadrilatere
      glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
      // En-haut a droite de la texture et du quadrilatere
      glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
      // En-haut a gauche de la texture et du quadrilatere
      glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);

      // Face arriere
      // En-bas a droite de la texture et du quadrilatere
      glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
      // En-haut a droite de la texture et du quadrilatere
      glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
      // En-haut a gauche de la texture et du quadrilatere
      glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
      // En-bas a gauche de la texture et du quadrilatere
      glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);

      // Face superieure
      // En-haut a gauche de la texture et du quadrilatere
      glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);
      // En-bas a gauche de la texture et du quadrilatere
      glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
      // En-bas a droite de la texture et du quadrilatere
      glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
      // En-haut a droite de la texture et du quadrilatere
      glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);

      // Face inferieure
      // En-haut a droite de la texture et du quadrilatere
      glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
      // En-haut a gauche de la texture et du quadrilatere
      glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
      // En-bas a gauche de la texture et du quadrilatere
      glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
      // En-bas a droite de la texture et du quadrilatere
      glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);

      // Face de droite
      // En-bas a droite de la texture et du quadrilatere
      glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
      // En-haut a droite de la texture et du quadrilatere
      glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);
      // En-haut a gauche de la texture et du quadrilatere
      glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);
      // En-bas a gauche de la texture et du quadrilatere
      glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);

      // Face de gauche
      // En-bas a gauche de la texture et du quadrilatere
      glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
      // En-bas a droite de la texture et du quadrilatere
      glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
      // En-haut a droite de la texture et du quadrilatere
      glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);
      // En-haut a gauche de la texture et du quadrilatere
      glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);

   glEnd();

    Maintenant nous incrémentons la valeur de xrot, yrot et zrot. Essayez de changer la valeur d'incrément de chaque variable pour faire tourner le cube plus ou moins vite, ou essayez de changer le signe de l'incrément pour le faire tourner dans l'autre sens.

    Rotation
    Sélectionnez
       xrot+=0.3f;   // Rotation sur l'axe des X
   yrot+=0.2f;   // Rotation sur l'axe des Y
   zrot+=0.4f;   // Rotation sur l'axe des Z
   return true;  // Tout va bien

    II-F. Conclusion▲

    Vous devriez maintenant avoir une meilleure compréhension de l'application de texture. Vous devriez être capable de texturer la surface dans n'importe quel quadrilatère avec une image de votre choix. Lorsque vous vous sentirez en confiance avec l'application de texture 2D, essayez d'ajouter six textures différentes au cube.

    L'application de texture n'est pas très difficile à comprendre une fois que vous aurez compris les coordonnées de texture. Alors amusez-vous bien à créer vos propres scènes texturées :).

    Enfin, voici une petite image de ce que vous devez voir :

    Image de l'application
    Image de l'application

    Jeff Molofee ( NeHe )

    III. Téléchargements▲

    Compte tenu du nombre de versions de codes sources pour les tutoriels nehe, nous les laissons en anglais. En principe, si vous avez compris le code présenté dans ce tutoriel (et les tutoriels antérieurs), vous n'aurez pas de mal à le comprendre :

    • Borland C++ Builder 6 (Conversion par Christian Kindahl)
    • C# (Conversion par Sabine Felsinger)
    • Code Warrior 5.3 (Conversion par Scott Lupton)
    • CygWin (Conversion par Stephan Ferraro)
    • D (Conversion par Familia Pineda Garcia)
    • Delphi (Conversion par Michal Tucek)
    • Dev C++ (Conversion par Dan)
    • Euphoria (Conversion par Evan Marshall)
    • Game GLUT (Conversion par Milikas Anastasios)
    • GLUT (Conversion par Kyle Gancarz)
    • Irix (Conversion par Lakmal Gunasekara)
    • Java (Conversion par Jeff Kirby)
    • Jedi-SDL (Conversion par Dominique Louis)
    • JOGL (Conversion par Kevin J. Duling)
    • LCC Win32 (Conversion par Robert Wishlaw)
    • Linux (Conversion par Richard Campbell)
    • Linux GLX (Conversion par Mihael Vrbanec)
    • Linux SDL (Conversion par Ti Leggett)
    • LWJGL (Conversion par Mark Bernard)
    • Mac OS (Conversion par Anthony Parker)
    • Mac OS X/Cocoa (Conversion par Bryan Blackburn)
    • MASM (Conversion par Nico (Scalp))
    • VC++/OpenIL (Conversion par Denton Woods)
    • Pelles C (Conversion par Pelle Orinius)
    • Power Basic (Conversion par Angus Law)
    • Python (Conversion par John Ferguson)
    • REALBasic (Conversion par Thomas J. Cunningham)
    • Scheme (Conversion par Brendan Burns)
    • Solaris (Conversion par Lakmal Gunasekara)
    • VB.NET CsGL (Conversion par X)
    • Visual Basic (Conversion par Ross Dawson)
    • Visual Basic (Conversion par Peter de Tagyos)
    • Visual Fortran (Conversion par Jean-Philippe Perois)
    • Visual Studio
    • Visual Studio NET (Conversion par Grant James)


Les filtres et l'éclairage

Les filtres et l'éclairage

  • I. Contributions
  • II. Tutoriel
    • II-A. Les inclusions et déclarations
    • II-B. La fonction LoadBMP
    • II-C. La fonction LoadGLTextures
    • II-D. La fonction InitGL
    • II-E. La fonction DrawGLScene
    • II-F. La fonction WinMain
    • II-G. Conclusion
  • III. Téléchargements
  • IV. Remerciements
  • V. Liens

Dans ce tutoriel, je vais vous apprendre comment utiliser trois différents filtres. Je vais vous expliquer comment bouger un objet en utilisant les touches du clavier et je vais aussi vous montrer comment rajouter de l'éclairage dans votre scène OpenGL. Beaucoup de choses seront vues dans ce tutoriel, donc si le précédent vous a causé des soucis, je vous conseille de l'étudier plus en profondeur. Il est important de bien comprendre les bases avant d'étudier le code qui suit.

II. Tutoriel

II-A. Les inclusions et déclarations

Nous allons modifier le code à partir de la première leçon une fois encore. Comme d'habitude, s'il y a des gros changements, je réécrirais la partie entière du code qui sera modifiée. Nous allons commencer en rajoutant quelques nouvelles variables au programme.

Inclusion des bibliothèques et déclaration des variables globales
Sélectionnez
#include <windows.h>   // Fichier en-tete pour Windows
#include <stdio.h>     // Fichier en-tete pour les entrees/sorties standards ( nouveau )
#include <gl\gl.h>     // Fichier en-tete pour la bibliotheque OpenGL32
#include <gl\glu.h>    // Fichier en-tete pour la bibliotheque GLu32
#include <gl\glaux.h>  // Fichier en-tete pour la bibliotheque GLaux

HDC       hDC=NULL;        // Contexte de peripherique GDI prive
HGLRC     hRC=NULL;        // Contexte de rendu permanent
HWND      hWnd=NULL;       // Contient l'identificateur de la fenetre
HINSTANCE hInstance;       // Contient l'identificateur de l'instance de l'application
bool      keys[256];       // Tableau utilise pour la gestion du clavier
bool      active=TRUE;     // Indicateur pour savoir si la fenetre est active
bool      fullscreen=TRUE; // Indicateur pour savoir si la fenetre est en plein ecran

Les lignes suivantes sont nouvelles. Nous allons rajouter trois variables booléennes. BOOL signifie que la variable pourra avoir seulement avoir comme valeur TRUE (vrai) ou FALSE (faux). Nous créons une variable appelée light (lumière) pour savoir si l'éclairage est actif ou non. Les variables lp et fp sont utilisées pour savoir si les touches 'L' ou 'F' ont été appuyées. Je vous expliquerai, plus tard dans le code, l'intérêt de ces variables. Pour l'instant, retenez juste qu'elles sont nécessaires.

Déclaration des drapeaux
Sélectionnez
BOOL light;  // Eclairage OUI / NON
BOOL lp;     // L appuyee?
BOOL fp;     // F appuyee?

Maintenant, nous allons déclarer cinq variables qui stockerons l'angle sur l'axe des X (xrot), l'angle sur l'axe des Y (yrot), la vitesse de rotation de la boîte sur l'axe des X (xspeed), et la vitesse de rotation de la boîte sur l'axe des Y (yspeed). Nous allons également créer une variable nommée z qui contiendra la profondeur d'écran de la boîte sur l'axe des Z.

Déclaration dse variables pour la rotation du cube
Sélectionnez
GLfloat xrot;     // Rotation sur l'axe des X
GLfloat yrot;     // Rotation sur l'axe des Y
GLfloat xspeed;   // Vitesse de rotation sur l'axe des X
GLfloat yspeed;   // Vitesse de rotation sur l'axe des Y
GLfloat z=-5.0f;  // Profondeur dans l'écran

Maintenant nous allons définir un tableau qui servira pour créer la lumière. Nous allons utiliser deux types différents d'éclairage. Le premier type d'éclairage, est appelé éclairage ambiant. L'éclairage ambiant est la lumière qui ne provient pas d'une direction en particulier. Tous les objets de votre scène seront ainsi éclairés par l'éclairage ambiant. Le second type de lumière est appelé lumière diffuse. La lumière diffuse provient d'une source lumineuse et va venir se réfléchir sur la surface d'un objet de votre scène. Toutes les surfaces d'un objet sur lesquelles la lumière viendra directement, seront très lumineuses, et les zones où la lumière se fera rare seront plus sombres. Ceci permet de créer un effet sympa d'ombre sur les cotés de votre boîte.

La lumière est créée de la même manière qu'une couleur. Si le premier nombre est 1.0f, et que les deux suivants sont égaux à 0.0f, nous obtiendrons une lumière rouge. Si le troisième nombre est 1.0f, et que les deux premiers sont égaux à 0.0f, nous allons avoir une lumière bleue. Le dernier nombre correspond à la valeur alpha de la couleur. Pour l'instant, nous le laisserons à 1.0f.

Donc, dans la ligne ci-dessous, nous allons mettre une couleur blanche pour l'éclairage ambiant avec une intensité moyenne (0.5f). Comme tous les nombres sont égaux à 0.5f, nous obtiendrons une lumière qui est à mi chemin entre le noir complet, et l'éclairage total. Rouge, bleu et vert mélangés avec la même valeur permet de créer une ombre depuis le noir (0.0f) jusqu'au blanc (1.0f). Sans éclairage ambiant, les zones sans lumière diffuse, apparaîtront très foncés.

Valeurs de l'éclairage ambiant
Sélectionnez
// Valeurs de l'eclairage ambient (nouveau)
GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f };

Dans la ligne de code suivante, nous allons enregistrer la valeur pour une superbe lumière. Toutes les valeurs sont fixées à 1.0f. Ce qui veut dire que la lumière éclairera de la manière la plus forte possible. Une lumière diffuse comme celle-ci éclairera le devant de votre boîte joliment.

Valeurs pour la lumière diffuse
Sélectionnez
// Valeurs pour la lumiere diffuse (nouveau)
GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f };

Enfin, nous enregistrons la position de notre lumière. Les trois premiers nombres sont les mêmes que ceux utilisés dans la fonction glTranslate. Le premier nombre correspond aux mouvements de gauche à droite sur le plan des X, le second nombre pour bouger de haut en bas sur le plan des Y, et le troisième nombre est pour bouger d'avant en arrière sur le plan des Z. Comme nous voulons que notre lumière éclaire directement le devant de notre boîte, nous n'avons pas à bouger ni à gauche, ni à droite, donc la première valeur est égale à 0.0f (pas de mouvement sur les X), nous n'avons pas à bouger ni en haut, ni en bas, donc la seconde valeur est égale à 0.0f. Pour la troisième valeur, nous voulons être sûr que la lumière sera toujours devant la boîte. Donc nous allons positionner la lumière hors de l'écran, derrière l'observateur. Si on imagine que la vitre de votre écran d'ordinateur correspond à 0.0f sur le plan des Z. On met la lumière à 2.0f sur le plan des Z. Si nous pouvions voir la lumière, elle serait en train de flotter juste devant la vitre de votre écran. En faisant ceci, la seule façon pour que la lumière soit derrière la boîte c'est que la boîte soit aussi devant votre écran. Et bien sûr, si votre boîte n'est pas derrière la vitre de l'écran, vous ne la verriez pas, et donc la position de la lumière aurait pu être quelconque. Non ?

Il n'y a pas de façon simple d'expliquer le troisième paramètre. Vous devez juste savoir que -2.0f est plus proche de vous que -5.0f. Et que -100.0f sera très loin dans l'écran. Une fois que vous êtes arrivé à 0.0f, l'image sera très grande, elle remplira entièrement l'écran. Une fois que vous partez dans les valeurs positives, l'image n'apparaîtra plus à l'écran, en effet elle sera derrière l'écran. C'est que ce que je veux dire par en dehors de l'écran. L'objet est toujours là, mais simplement vous ne pouvez plus le voir.

Laissez le dernier nombre à 1.0f. Cela indique à OpenGL que la lumière sera de type ponctuel (spot). Vous en saurez plus dans un prochain tutoriel.

Position de la lumière
Sélectionnez
GLfloat LightPosition[]= { 0.0f, 0.0f, 2.0f, 1.0f };  // Position de la lumière ( nouveau )

La variable filter ci-dessous sert à mémoriser l'indice de la texture que l'on souhaite afficher. La première texture (texture 0) est faite en utilisant GL_NEAREST (sans adoucissement). La seconde texture (texture 1) utilise un filtre de type GL_LINEAR qui lisse un peu l'image de sortie. La troisième texture (texture 2) utilise des textures "mipmap", qui permet de créer de jolie texture. La variable filter sera égale à 0, 1 ou 2 selon la texture que l'on veut utiliser. Nous commencerons avec la première texture.

GLuint texture[3] créé un tableau pour stocker nos trois différentes textures. Les textures seront mises dans texture[0], texture[1] et texture[2].

Stockage des textures
Sélectionnez
GLuint  filter;         // Quel filtre on utilise
GLuint  texture[3];     // Tableau des 3 textures
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);  // Declaration de WndProc

Maintenant nous allons charger une image, et créer trois textures à partir d'elle. Ce tutoriel utilise la bibliothèque glaux pour charger l'image, donc assurez vous bien d'avoir cette bibliothèque d'incluse avant d'essayer de compiler le code. Je sais que Delphi et Visual C++ possèdent cette librairie. Je ne suis pas sûr pour les autres langages. Je vais juste vous expliquer ce que font les nouvelles lignes de code, si vous voyez une ligne non commentée, et que vous ne voyez pas ce qu'elle fait, regardez dans le tutoriel #06. Il explique le chargement et la construction de texture depuis une image Bitmap d'une manière plus détaillée.

II-B. La fonction LoadBMP▲

Juste après le précédent code, et avant la fonction ReSizeGLScene(), nous allons rajouter la portion de code suivante. C'est le même code que celui utilisé dans la leçon 6 pour charger des fichiers Bitmap. Rien n'a changé. Si vous n'êtes pas sûr de ce que font ces lignes, relisez le tutorial 6. Il explique ce code en détail.

Chargement d'une image
Sélectionnez
AUX_RGBImageRec *LoadBMP(char *Filename)  // Charger une image Bitmap
{
   FILE *File=NULL;                       // Descripteur de fichier

   // On s'assure qu'un nom de fichier a bien ete passe
   if (!Filename)
   {
      return NULL;            // Sinon on retourne NULL
   }
   File=fopen(Filename,"r");  // On verifie que le fichier existe
   if (File)                  // Il existe ?
   {
      fclose(File);           // On ferme le descripteur
      return auxDIBImageLoad(Filename); // On charge l'image, et on retourne un pointeur
   }
   return NULL;               // Si le chargement a echoue, on retourne NULL
}

II-C. La fonction LoadGLTextures▲

Ici c'est la portion de code qui charge l'image (en appelant la fonction précédente) et qui la convertit en trois textures. La variable Status est utilisée pour savoir à tout moment si la texture s'est correctement chargée et créée.

Convertir les images en textures
Sélectionnez
int LoadGLTextures()           // Charge les images et les convertit en textures
{
    int Status=FALSE;          // Indicateur pour le bon déroulement
    AUX_RGBImageRec *TextureImage[1];         // Pour stocker notre texture
    memset(TextureImage,0,sizeof(void *)*1);  // On initialise le pointeur a NULL

Maintenant on charge l'image et on la convertit en texture. TextureImage[0] = LoadBMP("Data/Crate.bmp") va appeler notre fonction LoadBMP(). Le fichier nommé Crate.bmp dans le répertoire Data va être chargé. Si tout va bien, l'image va être stockée dans TextureImage[0], et la variable Status sera positionnée à TRUE, et on commencera à construire notre texture.

Image non disponible
L'image Crate.bmp
Lancement de la fonction précédente
Sélectionnez
// Charge l'image, verifie qu'il n'y a pas d'erreur,
 // si l'image n'est pas trouvée, on quitte
 if (TextureImage[0]=LoadBMP("Data/Crate.bmp"))
 {
  Status=TRUE;    // On positionne la variable Status à TRUE

Maintenant que nous avons chargé le contenu de l'image dans TextureImage[0], nous allons utiliser ces données pour construire nos 3 textures. La ligne suivante indique à OpenGL que nous voulons créer trois textures, qui seront stockées respectivement dans texture[0], texture[1] et texture[2].

Création des trois textures
Sélectionnez
      glGenTextures(3, &texture[0]);  // Creation des trois textures.

Dans le tutoriel six, nous avons utilisé des textures filtrées linéairement. Cela requiert une grosse puissance de calcul, mais le résultat est vraiment joli. Le premier type de texture que l'on va créer dans ce tutoriel va utiliser GL_NEAREST. Typiquement ce type de texture ne dispose d'aucun filtre. Il ne consomme qu'un tout petit peu de ressource, et ne rend pas réellement bien. Si vous avez déjà joué à un jeu où les textures sont plutôt pixellisées, c'est probablement parce qu'il utilise ce type de texture. Le seul bénéfice à utiliser ce type de texture dans un projet réside dans le fait qu'il fonctionnera relativement bien sur des petites configurations.

Vous noterez que nous utilisons GL_NEAREST pour le MIN et le MAG. Vous pouvez additionner GL_NEAREST avec GL_LINEAR, et la texture sera un peu mieux, mais nous sommes intéressés par la vitesse d'exécution, donc nous allons utiliser une basse qualité pour les deux. Le MIN_FILTER est le filtre utilisé quand une image est dessinée d'une façon réduite par rapport à sa taille d'origine. Le MAG_FILTER est le filtre utilisé quand une image est dessinée d'une façon agrandie par rapport à sa taille d'origine.

Création de filtre de texture de proximité
Sélectionnez
      // Creation des filtres de textures de proximite
      glBindTexture(GL_TEXTURE_2D, texture[0]);
      glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); // ( Nouveau )
      glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); // ( Nouveau )
      glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0,
         GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);

La texture suivante que nous allons construire est du même type que la texture que nous avons utilisée dans le tutoriel six. Filtrée linéairement. La seule chose qui change, est que nous la stockons dans texture[1] au lieu de texture[0] puisque c'est devenue notre seconde texture. Si nous la mettions dans texture[0] comme avant, cela écraserait la texture GL_NEAREST (la première texture).

Création de la texture filtrée linéairement
Sélectionnez
      // Creer la texture filtree lineairement
      glBindTexture(GL_TEXTURE_2D, texture[1]);
      glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
      glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
      glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0,
         GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);

Maintenant nous allons voir une nouvelle manière de faire une texture. Le Mipmapping ! Vous avez dû remarquer que quand vous affichez une image très petite sur l'écran, beaucoup de détails disparaissent. Les modèles qui sont utilisés pour afficher des belles images commencent à montrer des faiblesses. Quand vous indiquez à OpenGL de construire une texture mipmappée, OpenGL essaye de construire des textures de différentes tailles de hautes qualités. Quand vous dessinez une texture mipmappée à l'écran, OpenGL va sélectionner la meilleure texture dans celles qu'il a construite (texture avec le plus de détails) et la dessiner à l'écran, au lieu de redimensionner l'image originale (ce qui entraînerait la perte de détails).

J'ai dit dans la leçon six qu'il y avait un moyen de contourner la limite des 64, 128, 256, etc que OpenGL met aux hauteur et largeur des textures. gluBuild2DMipmaps est ce moyen. D'après ce que j'ai compris, vous pouvez utiliser les images que vous voulez (n'importe quelle hauteur ou largeur) quand vous construisez des textures mipmappées. OpenGL va automatiquement les redimensionner avec une hauteur et largeur correctes.

Comme c'est la texture numéro trois, nous allons la stocker dans texture[2]. Donc maintenant nous avons texture[0] qui n'a aucun filtrage, texture[1] qui utilise le filtrage linéaire, et texture[2] qui utilise les textures mipmappées. Nous avons donc fini de construire les textures de ce tutoriel.

Création de la texture mipmappée
Sélectionnez
      // Création de la texture mipmappée
      glBindTexture(GL_TEXTURE_2D, texture[2]);
      glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

      // Nouveau : Utiliser GL_LINEAR_MIPMAP_NEAREST
      glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);

La ligne suivante permet de construire la texture mipmappée. Nous créons ainsi une texture 2D qui utilise trois couleurs (rouge, verte, bleue). TextureImage[0]->sizeX est la largeur de l'image, TextureImage[0]->sizeY est la hauteur de l'image, GL_RGB signifie que nous utilisons les couleurs rouge, vert et bleu dans cet ordre. GL_UNSIGNED_BYTE signifie que les données qui composent la texture sont à base d'octets, et TextureImage[0]->data pointe sur les données de l'image que nous avons construite à partir de la texture.

Création de la texture mipmappée
Sélectionnez
      gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY,
         GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data); // ( Nouveau )
   }

Maintenant nous allons libérer toute la mémoire que nous avons pu utiliser pour stocker l'image. Nous vérifions d'abord qu'il y a des données correspondant à l'image stockées dans TextureImage[0]. Si tel est le cas, nous vérifions qu'il y a des données stockées. S'il y en a, nous les effaçons. Ensuite nous libérons la structure décrivant l'image pour être sûr que toute la mémoire est bien libérée.

Libération de l'image
Sélectionnez
   if (TextureImage[0])                // Est-ce que la texture existe ?
   {
      if (TextureImage[0]->data)       // Si le contenu de l'image est en memoire
      {
         free(TextureImage[0]->data);  // On libere la memoire du contenu de l'image
      }
      free(TextureImage[0]);           // On libere la structure decrivant l'image
   }

Enfin, on retourne l'état. Si tout c'est bien passé, la variable Status contiendra TRUE. Si quelque chose s'est mal déroulé Status contiendra FALSE.

Retourne le status
Sélectionnez
   return Status;  // Retourne la variable Status
}

II-D. La fonction InitGL▲

Maintenant nous allons charger les textures, et initialiser les options d'OpenGL. La première ligne de InitGL charge les textures en utilisant le code précédent. Après que les textures aient été créées, nous allons activer le mapping pour les textures 2D par l'intermédiaire de l'appel à glEnable(GL_TEXTURE2D). Les ombres seront des ombres douces, et la couleur d'arrière plan est fixée à noire, nous allons activer le test de profondeur et la mise en place de la perspective.

Chargement des textures et initialisation d'OpenGL
Sélectionnez
int InitGL(GLvoid)          // Tous les reglages pour OpenGL sont ici
{
   if (!LoadGLTextures())   // On appelle la fonction pour charger les textures
   {
      return FALSE;         // Si une texture s'est mal chargee, on retourne FALSE
   }
   glEnable(GL_TEXTURE_2D);               // On active le texture mapping
   glShadeModel(GL_SMOOTH);               // On active les ombres douces
   glClearColor(0.0f, 0.0f, 0.0f, 0.5f);  // Fond noir
   glClearDepth(1.0f);                    // Initialisation du tampon de profondeur
   glEnable(GL_DEPTH_TEST);               // On active le test de profondeur
   glDepthFunc(GL_LEQUAL);                // Le type de test de profondeur a effectuer
   glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);  // Le calcul des perspectives jolies

Maintenant nous allons mettre en place l'éclairage. La ligne suivante va fixer la quantité de lumière ambiante que light1 va fournir. Au début de ce tutoriel nous avons stocké la quantité de lumière ambiante dans LightAmbient. Les valeurs que nous avons mises dans le tableau vont être utilisées (une intensité moyenne d'éclairage ambiant).

Eclairage ambiant
Sélectionnez
   glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);  // Installe l'eclairage ambiant

Ensuite on fixe la quantité de lumière diffuse émise par la lumière numéro un. Nous avons stocké cette quantité de lumière diffuse dans LightDiffuse. Les valeurs que nous avons stockées dans ce tableau vont être utilisées ici (lumière blanche à pleine intensité).

Eclairage diffus
Sélectionnez
   glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);  // Installe l'eclairage diffus

Maintenant nous allons positionner la lumière. Nous avons stocké la position dans LightPosition. Les valeurs que nous avons mises dans ce tableau vont être utilisées ici (Au centre juste devant la face avant, 0.0f en x, 0.0f en y et 2 unités vers la caméra (en direction de l'extérieur de l'écran) sur le plan des Z.

Positionne la lumière
Sélectionnez
   glLightfv(GL_LIGHT1, GL_POSITION,LightPosition);  // Position de la lumiere

Enfin, on active la lumière numéro un. Nous n'avons pas encore activé GL_LIGHTING, donc vous ne verrez pas encore de lumière dans votre scène. La lumière est installée, positionnée et activée, mais tant que GL_LIGHTING n'est pas activé, l'éclairage ne fonctionnera pas.

Activation de GL_LIGHT1
Sélectionnez
   glEnable(GL_LIGHT1);  // Active la lumiere  numero un
   return TRUE;          // L'initialisation s'est bien passee
}

II-E. La fonction DrawGLScene▲

Dans la partie de code suivante, nous allons dessiner la texture mappée sur le cube. Je vais commenter un peu les lignes seulement parce qu'elles sont nouvelles. Si vous n'êtes pas sûr de ce que font les lignes non commentées, reportez vous au tutoriel six.

Debut de la fonction DrawGLScene
Sélectionnez
int DrawGLScene(GLvoid)  // C'est ici que tous les dessins sont faits
{
   // Efface l'ecran et le tampon de profondeur
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   glLoadIdentity();  // Reinitialise la vue

Les trois lignes suivantes permettent de positionner et tourner le cube avec la texture mappée. glTranslatef(0.0f,0.0f,z) bouge le cube de la valeur de z sur l'axe des Z ( celui qui vient de loin et s'approche de la caméra). glRotatef(xrot, 1.0f, 0.0f, 0.0f) utilise la variable xrot pour tourner le cube autour de l'axe des X. glRotatef(yrot, 0.0f, 1.0f, 0.0f) utilise la variable yrot pour tourner le cube autour l'axe des Y.

Bouge et tourne le cube
Sélectionnez
   // Translation vers l'interieur ou l'exterieur de l'ecran de z unites
   glTranslatef(0.0f,0.0f,z);
   glRotatef(xrot,1.0f,0.0f,0.0f);  // Tourne autour l'axe des X de xrot
   glRotatef(yrot,0.0f,1.0f,0.0f);  // Tourne autour l'axe des Y de yrot

Les lignes suivantes sont similaires à celles utilisées dans le tutoriel six, mais au lieu d'utiliser texture[0], nous allons utiliser texture[filter]. Chaque fois que la touche 'F' sera pressée, la valeur de filter sera incrémentée. Si cette valeur est plus grande que deux, alors la variable filter sera remise à zéro. Quand le programme va s'exécuter, la variable filter sera initialisée à zéro. Cela revient au même que glBindTexture(GL_TEXTURE_2D, texture[0]). Si on appuis sur 'F' une fois, la variable filter sera égale à un, ce qui est la même chose que de mettre glBindTexture(GL_TEXTURE_2D, texture[1]). En utilisant la variable filter, nous pouvons sélectionner chacune des trois textures que nous avons fabriquées.

Selectionne la texture à appliquer
Sélectionnez
   // Selectionne la texture correspondante a filter
   glBindTexture(GL_TEXTURE_2D, texture[filter]);
   glBegin(GL_QUADS);  // Commence a dessiner les quadrilateres

L'utilisation de glNormal3f est nouvelle dans mes tutoriels. Une normale est une ligne qui ressort d'un polygone en son milieu avec un angle de 90 degrés. Quand vous utilisez des lumières, vous devez spécifier les normales. Les normales indiquent à OpenGL dans quelle direction le polygone fait face...dans quel sens est le haut. Si vous ne spécifiez pas de normales, plein de bizarreries peuvent se produire. Les faces qui ne devaient pas être éclairées vont l'être, le mauvais coté d'un polygone va s'éclairer, etc. La normale doit pointer en dehors du polygone.

Quand vous regardez la face de devant, vous remarquez que la normale est positive sur l'axe des z. Ceci signifie qu'elle pointe vers l'observateur. Exactement la direction que nous voulions. Sur la face du fond, la normale pointe vers le fond de la scène, dans l'écran. Ici encore, c'est ce que nous voulions. Si le cube tourne de 180 degrés sur l'axe des x ou des y, la face de devant va être celle du fond, et le fond devenir la face de devant. Ainsi, n'importe quelle face qui sera devant l'observateur, sa normale pointera également vers lui. Comme la lumière est derrière l'observateur, chaque fois que la normale pointe vers l'observateur, elle va également pointer vers la lumière. Quand cela se produit, la face s'éclaire. Plus une normale pointe vers une lumière, plus le polygone correspondant sera éclairé. Si vous bougez à l'intérieur du cube, vous remarquez que c'est sombre. Toutes les normales pointent vers l'extérieur, donc il n'y a aucune lumière dans la boîte, exactement comme nous le voulions.

Dessin du cube
Sélectionnez
      // Face de devant
      glNormal3f( 0.0f, 0.0f, 1.0f);   // La normale pointe vers l'observateur
      glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);   // Point 1 (Devant)
      glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);   // Point 2 (Devant)
      glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);    // Point 3 (Devant)
      glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);    // Point 4 (Devant)

      // Face de derrière
      glNormal3f( 0.0f, 0.0f,-1.0f);  // La normale pointe vers le fond de l'écran
      glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);  // Point 1 (Fond)
      glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);   // Point 2 (Fond)
      glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);   // Point 3 (Fond)
      glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);  // Point 4 (Fond)

      // Face du haut
      glNormal3f( 0.0f, 1.0f, 0.0f);  // La normale pointe vers le haut
      glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);   // Point 1 (Haut)
      glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f);    // Point 2 (Haut)
      glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f);    // Point 3 (Haut)
      glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);   // Point 4 (Haut)

       // Face d'en bas
      glNormal3f( 0.0f,-1.0f, 0.0f);  // La normale pointe vers le bas
      glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);  // Point 1 (Bas)
      glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);  // Point 2 (Bas)
      glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);   // Point 3 (Bas)
      glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);   // Point 4 (Bas)

       // Face de droite
      glNormal3f( 1.0f, 0.0f, 0.0f);  // La normale pointe vers la droite
      glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);  // Point 1 (Droite)
      glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);   // Point 2 (Droite)
      glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);    // Point 3 (Droite)
      glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);   // Point 4 (Droite)

       // Face de gauche
      glNormal3f(-1.0f, 0.0f, 0.0f);  // La normale pointe vers la gauche
      glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);  // Point 1 (Gauche)
      glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);   // Point 2 (Gauche)
      glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);    // Point 3 (Gauche)
      glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);   // Point 4 (Gauche)
   glEnd();  // Fin du dessin du cube

Les deux lignes suivantes incrémentent xrot et yrot de la valeur contenue dans xspeed et yspeed. Si la valeur dans xspeed ou yspeed est grande, xrot et yrot vont augmenter très rapidement. Plus xrot (ou yrot) augmentera rapidement, plus le cube tournera vite sur cet axe.

Modifie la prochaine rotation
Sélectionnez
   xrot+=xspeed;  // Ajoute xspeed à xrot
   yrot+=yspeed;  // Ajoute yspeed à yrot
   return TRUE;   // On retourne TRUE
}

II-F. La fonction WinMain▲

Maintenant on arrive à la fonction WinMain(). Nous allons rajouter du code pour activer ou non l'éclairage, tourner le cube, changer le filtre, et bouger le cube d'avant en arrière de l'écran. Juste en bas de WinMain(), vous devez trouver l'instruction SwapBuffers(hDC). Juste après cette ligne, on rajoute le code suivant.

Ce code vérifie si la touche 'L' a été pressée sur le clavier. La première ligne vérifie que 'L' a été pressée. Si 'L' a été pressée, mais que lp n'est pas FALSE (ce qui signifie que 'L' est déjà pressée ou qu'elle est maintenue) rien ne va se passer.

Gestion du clavier
Sélectionnez
  SwapBuffers(hDC);      // Echange les buffers (Double Buffering)
  if (keys['L'] && !lp)  // 'L' est pressee et c'est la premiere fois qu'on vient ici ?
     {

Si lp est FALSE, cela signifie que 'L' n'a pas encore été pressée, ou qu'elle a été relâchée, lp sera égale à TRUE. Cela force l'utilisateur à relâcher la touche 'L' pour pouvoir ré-éxécuter cette portion de code. Si on ne vérifiait pas que la touche vient d'être pressée, l'éclairage s'activerait et se désactiverait sans arrêt, car le programme penserait vous êtes en train d'appuyer sur la touche 'L' à chaque fois qu'il arrive à ce bout de code.

Une fois que lp est mise à TRUE, le programme considère que 'L' est appuyée, et donc on active ou non l'éclairage. La variable light peut avoir uniquement les valeurs TRUE ou FALSE. Donc si on écrit light = !light, cela signifie que light sera égale à NON light. Ce qui donne en français, si light est égale à TRUE, la nouvelle valeur de light sera égale à NON TRUE (donc à FALSE), et si light est égale à FALSE, la nouvelle valeur de light sera égale à NON FALSE (donc à TRUE). Donc si light était égale à TRUE, light sera égale à FALSE, et si elle était égale à FALSE, elle devient TRUE.

Changement du mode d'éclairage
Sélectionnez
   lp=TRUE;       // lp se positionne à TRUE
   light=!light;  // On met light à TRUE ou FALSE

Maintenant on va vérifier l'état de light. La première ligne traduit en français signifie : Si light est égale à FALSE. Si on fait de même, les lignes suivantes donnent : Si light égale à FALSE, on désactive l'éclairage. La commande 'else' est traduit en : Si light n'est pas égale à FALSE. Donc si light n'est pas égale à FALSE (donc light est égale à TRUE), alors on active l'éclairage.

Activation ou non de la lumière
Sélectionnez
      if (!light)                 // Si Light est egale a FALSE
        {
         glDisable(GL_LIGHTING);  // On desactive l'eclairage
        }
      else                        // Sinon
        {
         glEnable(GL_LIGHTING);   // On active l'eclairage
        }
    }

Les lignes suivantes vérifient si la touche 'L' a été relâchée. Si c'est le cas, la variable lp prend la valeur FALSE, qui signifie que 'L' n'est plus pressée. Si on ne vérifie pas si la touche est relâchée, on ne pourra pas réactiver la lumière, car le programme pensera que la touche 'L' est toujours pressée, et donc il nous laissera pas désactiver la lumière.

'L' est relachée
Sélectionnez
    if (!keys['L'])  // Est-ce que 'L' est relachee?
     {
      lp=FALSE;      // Si oui, lp sera egale a FALSE
     }

Maintenant nous allons faire quelque chose de similaire avec la touche 'F'. Si la touche est en train d'être pressée, et qu'elle n'est pas restée appuyée ou qu'elle n'a jamais été appuyée avant, cela va affecter à fp la valeur TRUE. Ce qui signifie que la touche est actuellement pressée. Ensuite cela va incrémenter la variable filter. Si filter contient une valeur supérieure à 2 (ce qui correspondrait à texture[3], elle n'existe pas), on réinitialise la variable en la remettant à zéro.

Changement de la texture
Sélectionnez
     if (keys['F'] && !fp)  // Est-ce que 'F' est pressee?
      {
      fp=TRUE;              // On affecte TRUE à fp
      filter+=1;            // On incremente la variable fp de un
      if (filter>2)         // Est-ce que la valeur de filter est plus grande que 2 ?
         {
          filter=0;         // Si oui, on affecte 0 a filter
         }
      }
     if (!keys['F'])        // Est-ce que 'F' a ete relachee ?
      {
       fp=FALSE;            // Si oui, on affecte FALSE a fp
      }

Les quatre lignes suivantes vérifient si la touche 'Page Up' est pressée. Si c'est le cas, on décrémente la variable z. Si la valeur de z diminue, le cube va bouger vers l'intérieur car on utilise glTranslatef(0.0f, 0.0f, z) dans notre fonction DrawGLScene.

'Page Up' éloigne le cube
Sélectionnez
   if (keys[VK_PRIOR])  // Est-ce que 'Page Up' est pressee?
    {
     z-=0.02f;          // Si oui, on bouge vers l'interieur de l'ecran
    }

Ces quatre lignes vérifient si la touche 'Page Down' est pressée. Si c'est le cas, on incrémente la variable z ce qui fera bouger le cube vers l'observateur car la commande glTranslatef(0.0f, 0.0f, z) est utilisée dans la fonction DrawGLScene.

'Page Down' rapproche le cube
Sélectionnez
   if (keys[VK_NEXT])   // Est-ce que 'Page Down' est pressee ?
    {
     z+=0.02f;          // Si oui, on bouge vers l'observateur
    }

Maintenant nous allons vérifier l'état des touches directionnelles. En appuyant sur les touches 'gauche' ou 'droite', xspeed va augmenter ou diminuer. En appuyant sur 'haut' ou 'bas', yspeed va augmenter ou diminuer. Rappelez-vous qu'un peu plus haut dans ce tutoriel, je vous ai dit que si la valeur contenue dans xspeed ou yspeed était grande, le cube tournerait vite. Plus longtemps vous resterez appuyé sur une touche de direction, plus le cube tournera vite dans cette direction.

Accélération ou non de la vitesse de rotation
Sélectionnez
    if (keys[VK_UP])           // Est-ce que 'Fleche Haut' est pressee?
     {
     xspeed-=0.01f;            // Si oui, on decremente xspeed
     }
    if (keys[VK_DOWN])         // Est-ce que 'Fleche Bas' est pressee?
     {
     xspeed+=0.01f;            // Si oui, on incremente xspeed
     }
    if (keys[VK_RIGHT])        // Est-ce que 'Fleche Droite' est pressee?
     {
     yspeed+=0.01f;            // Si oui, on incremente yspeed
     }
    if (keys[VK_LEFT])         // Est-ce que 'Fleche Gauche' est pressee?
     {
     yspeed-=0.01f;            // Si oui, on decremente yspeed
     }

Comme dans les tutoriels précédents, assurez-vous que le titre de la fenêtre est correct.

Changement du titre
Sélectionnez
   if (keys[VK_F1])              // Est-ce que F1 est pressee?
      {
      keys[VK_F1]=FALSE;         // Si oui, on met FALSE pour cette touche
      KillGLWindow();            // On detruit notre fenetre OpenGL
      fullscreen=!fullscreen;    // On passe du mode plein ecran a fenetre (ou inversement)

      // Recreation de notre fenetre OpenGL
      if (!CreateGLWindow("Tutoriel de Nehe sur les Textures, les lumières et le clavier",
              640, 480, 16, fullscreen))
         {
         return 0;               // On quitte si la fenetre ne s'est pas creee
         }
      }
   }
   }
}

// Fermeture
KillGLWindow();                  // On detruit notre fenetre OpenGL
return (msg.wParam);             // On sort du programme
}

II-G. Conclusion▲

A la fin de ce tutoriel, vous devez être capable de créer et de manipuler des textures de hautes qualités, réalistes ou mappées sur des objets à base de cube. Vous devez avoir compris les avantages des trois filtres utilisés dans ce tutoriel. En appuyant sur une touche spécifique du clavier, vous devez être capable d'interagir avec des objets à l'écran, et finalement, vous devez maintenant savoir comment appliquer un simple éclairage sur votre scène pour la rendre un peu plus réelle.

Jeff Molofee ( NeHe )

III. Téléchargements

Compte tenu du nombre de versions de codes sources pour les tutoriels nehe, nous les laissons en anglais. En principe, si vous avez compris le code présenté dans ce tutoriel (et les tutoriels antérieurs), vous n'aurez pas de mal à le comprendre :

  • Borland C++ Builder 6 (Conversion par Christian Kindahl)
  • C# (Conversion par Brian Holley)
  • Code Warrior 5.3 (Conversion par Scott Lupton)
  • CygWin (Conversion par Stephan Ferraro)
  • D (Conversion par Familia Pineda Garcia)
  • Delphi (Conversion par Michal Tucek)
  • Dev C++ (Conversion par Dan)
  • Euphoria (Conversion par Evan Marshall)
  • Game GLUT (Conversion par Milikas Anastasios)
  • GLUT (Conversion par Andy Restad)
  • Irix (Conversion par Lakmal Gunasekara)
  • Java (Conversion par Jeff Kirby)
  • Jedi-SDL (Conversion par Dominique Louis)
  • JOGL (Conversion par Kevin J. Duling)
  • LCC Win32 (Conversion par Robert Wishlaw)
  • Linux (Conversion par Richard Campbell)
  • Linux GLX (Conversion par Mihael Vrbanec)
  • Linux SDL (Conversion par Ti Leggett)
  • LWJGL (Conversion par Mark Bernard)
  • Mac OS (Conversion par Anthony Parker)
  • Mac OS X/Cocoa (Conversion par Bryan Blackburn)
  • MASM (Conversion par Nico (Scalp))
  • VC++/OpenIL (Conversion par Denton Woods)
  • Pelles C (Conversion par Pelle Orinius)
  • Power Basic (Conversion par Angus Law)
  • Scheme (Conversion par Brendan Burns)
  • Solaris (Conversion par Lakmal Gunasekara)
  • Visual Basic (Conversion par Peter de Tagyos)
  • Visual Fortran (Conversion par Jean-Philippe Perois)
  • Visual Studio
  • Visual Studio NET (Conversion par Grant James)


La transparence

La transparence

  • I. Contributions
  • II. Tutoriel
    • II-A. Equation de mélange
    • II-B. Faire des mélanges de couleurs avec OpenGL
    • II-C. Canal alpha d'une texture
  • III. Téléchargements
  • IV. Remerciements
  • V. Liens

Un programmeur du site Hypercosm a demandé s'il pouvait écrire un tutoriel sur le blending (ou mélange de couleur). Cette leçon est donc un tutoriel de ce monsieur. Cette partie est en fait la continuité de la leçon sept. Le Blending (ou mélange) est un effet très pratique. L'auteur se prénomme Tom Stanis.

II. Tutoriel

La plupart des effets spéciaux en OpenGL utilisent une sorte de mélange de couleurs. Ce mélange est effectué en combinant la couleur d'un pixel donné avec ce qui se trouve déjà à l'écran. La façon dont les couleurs sont mélangées se base sur la valeur alpha des deux pixels, et/ou la fonction de mélange utilisée. L'alpha est généralement la quatrième et dernière composante d'une couleur. Dans le passé, vous utilisiez GL_RGB pour spécifier une couleur avec trois composantes. GL_RGBA peut être utilisé pour aussi spécifier le canal alpha. De plus, nous utiliserons glColor4f au lieu de glColor3f.

La plupart des personnes considèrent le canal Alpha comme étant le degré d'opacité d'un matériel. Une valeur de 0.0 veut dire que l'objet est entièrement transparent et 1.0 que l'objet est entièrement opaque.

II-A. Equation de mélange

Si vous êtes inconfortable avec les maths et voulez simplement voir comment utiliser la transparence, passez à la section suivante. Si vous voulez comprendre comment sont gérés les calculs de mélange de couleurs, cette section est pour vous.

Formule pour les couleurs
Sélectionnez
(Rs * Sr + Rd * Dr, Gs * Sg + Gd * Dg, Bs * Sb + Bd * Db, As * Sa + Ad * Da)

OpenGL va calculer le résultat du mélange de deux pixels en se basant sur l'équation précédente. Les variables s et d spécifient les pixels source et destination (donc Rs est la composante rouge du pixel source et Gd est la composante verte du pixel destination...). Les variables S et D représentent les facteurs de mélange. Ces valeurs indiquent comment vous voulez mélanger les deux pixels. Les valeurs les plus courantes pour S et D sont (As, As, As As) (donc la valeur Alpha du pixel source) pour S et (1 - As, 1 - As, 1 - As, 1 - As) (donc 1 moins la valeur Alpha du pixel source) pour D. Ceci va donner une équation de mélange qui ressemblera à :

Formule pour la transparence
Sélectionnez
(Rs * As + Rd * (1 - As), Gs * As + Gd * (1 - As), Bs * As + Bd * (1 - As), As * As + Ad * (1 - As))

Cette équation va permettre d'avoir un effet de transparence.

II-B. Faire des mélanges de couleurs avec OpenGL

On active le mélange comme tout le reste (avec la fonction glEnable). Ensuite, on définit l'équation, on désactive l'écriture dans le tampon de profondeur (puisque nous voulons que les objets qui sont derrière les formes transparentes soient encore dessinés). Ceci n'est pas vraiment la bonne façon de faire des mélanges de couleurs mais dans des projets simples, cela suffit. Rui Martins ajoute : Pour le faire correctement, il faut d'abord dessiner toute la scène et ensuite dessiner tous les polygones transparents (avec le canal alpha < 1.0). L'ordre du rendu doit se faire dans l'ordre inverse de la profondeur (donc le polygone le plus lointain doit être dessiné d'abord). Ceci est dû à la différence de résultats dépendant de l'ordre de rendu de deux polygones transparents (1 et 2). Par exemple, en supposant que 1 est plus proche de la caméra, pour être correct, il faudrait dessiner le polygone 2 et seulement ensuite le polygone 1. Si on regarde le rendu, comme dans la réalité, la lumière venant de derrière ces deux polygones (qui sont transparents) doit passer par 2 avant de passer par 1. Vous devez donc trier les polygones transparents par profondeur et les dessiner après tout le reste de la scène avec le test de profondeur mis en place. C'est une technique plus lourde, mais la meilleure si vous souhaitez un résultat correct.

On utilise le code du dernier tutoriel. Nous allons commencer par ajouter deux nouvelles variables au début de ce code. Je vais remettre toute cette partie du code pour être plus clair.

Inclusion, variables globales et déclaration de prototype
Sélectionnez
#include <windows.h>                 // Fichier d'en-tete pour Windows
#include <stdio.h>                   // Fichier d'en-tete pour l'entree/sortie standard
#include <gl\gl.h>                   // Fichier d'en-tete pour la bibliotheque OpenGL
#include <gl\glu.h>                  // Fichier d'en-tete pour la bibliotheque GLu
#include <gl\glaux.h>                // Fichier d'en-tete pour la bibliotheque GLaux

HDC       hDC=NULL;                  // Contexte de fenetre
HGLRC     hRC=NULL;                  // Contexte de rendu permanent
HWND      hWnd=NULL;                 // Contient un identifiant de fenêtre
HINSTANCE hInstance;                 // Contient une instance de l'application

bool    keys[256];                   // Tableau utilisé pour la gestion du clavier
bool    active=TRUE;                 // Window Active Flag Set To TRUE By Default
bool    fullscreen=TRUE;             // Variable pour le plein écran mis a TRUE par defaut
bool    light;                       // Lumiere ON/OFF
bool    blend;                       // Melange de couleurs (Nouveau)
bool    lp;                          // L appuye ?
bool    fp;                          // F appuye ?
bool    bp;                          // B appuye ? (Nouveau)
GLfloat xrot;                        // X Rotation
GLfloat yrot;                        // Y Rotation
GLfloat xspeed;                      // X Rotation Vitesse
GLfloat yspeed;                      // Y Rotation Vitesse
GLfloat z=-5.0f;                     // Profondeur dans l'ecran
GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f };    // Valeurs de la lumiere ambiente
GLfloat LightDiffuse[]=  { 1.0f, 1.0f, 1.0f, 1.0f };   // Valeurs de la lumiere diffuse
GLfloat LightPosition[]= { 0.0f, 0.0f, 2.0f, 1.0f };   // Position de la lumiere
GLuint  filter;                      // Utiliser quel filtre ?
GLuint  texture[3];                  // Stockage pour 3 textures
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);  // Declaration de WndProc

Allez dans la fonction LoadGLTextures. Trouvez la ligne où se trouve if (TextureImage[0]=LoadBMP("Data/Crate.bmp")). Utilisez la ligne ci-dessous à la place. Nous allons utiliser une texture représentant un vitrail pour ce tutoriel.

Chargement d'une image
Sélectionnez
if (TextureImage[0]=LoadBMP("Data/glass.bmp"))  // Charger la texture ( MODIFIE )

Ajoutez les deux lignes suivantes quelque part dans la fonction InitGL. La première ligne met en place une brillance de l'objet au maximum avec 50% d'alpha (opacité). Ceci veut dire que lorsque le mélange de couleur sera mis en place, l'objet sera à 50% transparent. La deuxième ligne met en place le type de mélange que nous allons utiliser.

Rui Martins ajoute : Une valeur de 0.0 veut dire que le matériel serait entièrement transparent. Une valeur de 1.0 veut dire qu'il serait entièrement opaque.

Mise en place du canal alpha et de la fonction de mélange
Sélectionnez
    // Brillance au maximum, 50% Alpha ( NOUVEAU )
    glColor4f(1.0f,1.0f,1.0f,0.5f);
    // Fonction de melange pour transparence dependant de la valeur source alpha(Nouveau)
    glBlendFunc(GL_SRC_ALPHA,GL_ONE);

Si vous regardez le code qui suit, il peut être retrouvé à la fin de la septième leçon.

Gestion de la fléche gauche
Sélectionnez
if (keys[VK_LEFT])   // Est-ce qu'on appuie sur la fleche gauche
 {
  yspeed-=0.01f;     // Si oui, décrémenter la vitesse Y
 }

En dessous de ce code, nous voulons ajouter les lignes suivantes. Les lignes à ajouter regardent si la touche 'B' a été appuyée. Si c'est le cas, le programme vérifie s'il est en train de mélanger les couleurs ou non. Si cette option est active, il la désactive, sinon il l'active.

Gestion de la touche 'B'
Sélectionnez
if (keys['B'] && !bp)     // Est-ce que B est appuyee et bp est a FALSE ?
 {
  bp=TRUE;                // Si oui, bp devient TRUE
  blend = !blend;         // Changer la valeur de blend (entre TRUE et FALSE)
  if(blend)               // Est-ce qu'on melange les couleurs ?
  {
   glEnable(GL_BLEND);    // Activation du melange des couleurs
   glDisable(GL_DEPTH_TEST); // Desactivation du test de profondeur
  }
  else                    // Sinon
  {
   glDisable(GL_BLEND);   // Desactivation du melange des couleurs
   glEnable(GL_DEPTH_TEST); // Activation du test de profondeur
  }
 }
 if (!keys['B'])          // Est-ce que B a ete appuye ?
 {
  bp=FALSE;               // bp devient faux si c'est le cas
 }

Mais comment spécifier la couleur que nous utilisons avec une texture ? C'est simple, en modulant la texture avec la couleur courante, chaque pixel de la texture sera multiplié par la couleur. Donc, si la couleur à utiliser est (0.5, 0.6, 0.4) nous allons mutliplié la couleur de chaque pixel de la texture par (0.5, 0.6, 0.4) (alpha est supposé à 1.0 si ce n'est pas spécifié).

C'est tout ! Le mélange de couleurs est assez simple à faire en OpenGL.

Remarque (13/11/99) :

J'ai (Nehe) modifié le code du mélange pour que le résultat ressemble plus au résultat attendu. En utilisant des valeurs Alpha pour la source et la destination, cela provoque des effets d'irréalisme. Les faces du cube qui se retrouvent derrière et sur les côtés sont plus sombres que ce qu'ells devraient être. Ce que je fais ici n'est probablement pas la meilleure solution mais elle fonctionne et le résultat semble correct lorsque la lumière est active. Merci à Tom pour le code initial, sa technique pour le mélange était correct mais les gens s'attendaient à quelque chose de plus attractif.

II-C. Canal alpha d'une texture

La valeur alpha utilisée pour la transparence peut être récupérée par un fichier image avec les couleurs. Pour le faire, il suffit de rajouter un canal alpha à l'image que vous voulez utiliser et mettre GL_RGBA pour le format à passer aux appels glTexImage2D.

Tom Stanis

Enfin, voici une petite image de ce que vous devez voir :

Image de l'application
Image de l'application

Jeff Molofee (NeHe)

III. Téléchargements

Compte tenu du nombre de versions de codes sources pour les tutoriels nehe, nous les laissons en anglais. En principe, si vous avez compris le code présenté dans ce tutoriel (et les tutoriels antérieurs), vous n'aurez pas de mal à le comprendre :

  • Borland C++ Builder 6 (Conversion par Christian Kindahl)
  • C# (Conversion par Brian Holley)
  • Code Warrior 5.3 (Conversion par Scott Lupton)
  • CygWin (Conversion par Stephan Ferraro)
  • D (Conversion par Familia Pineda Garcia)
  • Delphi (Conversion par Michal Tucek)
  • Dev C++ (Conversion par Dan)
  • Euphoria (Conversion par Evan Marshall)
  • Game GLUT (Conversion par Milikas Anastasios)
  • GLUT (Conversion par Andy Restad)
  • Irix (Conversion par Lakmal Gunasekara)
  • Java (Conversion par Jeff Kirby)
  • Jedi-SDL (Conversion par Dominique Louis)
  • JOGL (Conversion par Pepijn Van Eeckhoudt)
  • LCC Win32 (Conversion par Robert Wishlaw)
  • Linux (Conversion par Richard Campbell)
  • Linux GLX (Conversion par Mihael Vrbanec)
  • Linux SDL (Conversion par Ti Leggett)
  • LWJGL (Conversion par Mark Bernard)
  • Mac OS (Conversion par Anthony Parker)
  • Mac OS X/Cocoa (Conversion par Bryan Blackburn)
  • MASM (Conversion par Nico (Scalp))
  • VC++/OpenIL (Conversion par Denton Woods)
  • Pelles C (Conversion par Pelle Orinius)
  • Power Basic (Conversion par Angus Law)
  • Python (Conversion par Ryan Showalter)
  • Scheme (Conversion par Brendan Burns)
  • Solaris (Conversion par Lakmal Gunasekara)
  • Visual Basic (Conversion par Peter de Tagyos)
  • Visual Fortran (Conversion par Jean-Philippe Perois)
  • Visual Studio
  • Visual Studio NET (Conversion par Grant James)


Déplacer un bitmap en 3D

  • Déplacer un bitmap en 3D

    • I. Contributions
    • II. Tutoriel
      • II-A. Inclusions et déclarations
      • II-B. La fonction LoadBMP
      • II-C. La fonction LoadGLTextures
      • II-D. La fonction InitGL
      • II-E. La fonction DrawGLScene
      • II-F. La gestion clavier
      • II-G. Conclusion
    • III. Téléchargements
    • IV. Remerciements
    • V. Liens

    Ce tutoriel couvre quelques demandes que vous m'avez faites. Vous voulez savoir comment faire bouger les objets que vous avez créé dans votre univers 3D. Vous voulez savoir comment dessiner une image à l'écran, sans la partie noire de l'image qui cache ce qu'il y a derrière elle. Vous voulez des animations simples et utiliser plus la transparence. Ce tutoriel va vous expliquer tout ceci. Vous remarquerez qu'il n'y a pas de boîtes qui tournent. Les précédentes leçons introduisaient les bases d'OpenGL. Chaque tutoriel ajoutant de nouvelles notions par rapport au précédent. Ce tutoriel est un mélange de tout ce que vous avez appris jusqu'à présent, avec des informations complémentaires pour savoir comment faire bouger vos objets en 3D. Ce tutoriel est un tout petit peu plus compliqué, donc assurez-vous de bien avoir compris les leçons précédentes avant d'attaquer ce tutoriel.

    II. Tutoriel

    Bienvenue dans le tutoriel 9. A partir de maintenant vous devez être en mesure de mieux comprendre OpenGL. Vous avez appris tout ce qu'il faut pour créer et initialiser une fenêtre OpenGL, appliquer une texture sur un objet qui tourne en utilisant des effets d'éclairages et de transparences. Ceci sera le premier tutoriel du niveau supérieur. Vous allez apprendre : à bouger une image dans un univers 3D, enlever les pixels noirs autour de l'image (en utilisant le mélange), ajouter des couleurs à une texture noire et blanche, et finalement vous apprendrez comment créer des couleurs sympathiques et des animations en utilisant plusieurs textures colorées ensembles.

    II-A. Inclusions et déclarations

    Nous allons modifier le code de la première leçon dans ce tutoriel. Nous allons commencer par ajouter quelques nouvelles variables au début du programme. Je vais réécrire la section entière ainsi cela sera plus facile de voir où il y a eu des changements.

    En-têtes et déclarations
    Sélectionnez
    #include    <windows.h>                       // En-tête pour Windows
#include    <stdio.h>                         // En-tête pour les entrees/sorties standards (NOUVEAU)
#include    <gl\gl.h>                         // En-tête pour la bibliotheque OpenGL32
#include    <gl\glu.h>                        // En-tête pour la bibliotheque GLu32
#include    <gl\glaux.h>                      // En-tête pour la bibliotheque GLaux

HDC         hDC=NULL;                         // Contexte prive du systeme GDI
HGLRC       hRC=NULL;                         // Contexte de rendu permanent
HWND        hWnd=NULL;                        // Contient notre descripteur de fenêtre
HINSTANCE   hInstance;                        // Sauve l'instance de l'application
bool        keys[256];                        // Tableau utilise pour les routines du clavier
bool        active=TRUE;                      // Drapeau d'activation de la fenetre
bool        fullscreen=TRUE;                  // Drapeau pour le mode plein écran (Vrai par defaut)

    Les lignes suivantes sont nouvelles. twinkle et tp sont deux variables booléennes, ce qui signifie qu'elles peuvent soient contenir TRUE ou FALSE. twinkle permet de mémoriser si l'effet de scintillement doit être actif ou non. tp est utilisé pour savoir si la touche 'T' est dans un état pressé ou relâché (touche T pressée : tp = TRUE ; touche T relâchée : tp = FALSE).

    Drapeaux d'états
    Sélectionnez
    BOOL    twinkle;                         // Etoiles brillantes ?
BOOL    tp;                              // Touche 'T' appuyee ?

    num va permettre de mémoriser combien d'étoiles seront dessinées sur l'écran. Cette valeur sera stockée dans une constante. Ce qui signifie qu'elle ne pourra pas être changée dans le code. Nous la mettons dans une constante, car vous ne pouvez pas redéfinir la taille d'un tableau. Donc, si nous avons déclaré un tableau avec seulement 50 étoiles, et que nous décidons d'augmenter num à 51 dans le code, le tableau ne peut pas s'agrandir à 51 étoiles, et donc une erreur apparaîtra. Vous pouvez changer cette valeur pour celle que vous voulez uniquement dans la ligne suivante. N'essayez pas de changer la valeur de num plus loin dans le code, car vous risqueriez d'obtenir une erreur.

    Constante représentant le nombre d'étoiles à utiliser
    Sélectionnez
    const    num=50;                             // Nombre d'étoiles à dessiner

    Maintenant nous allons créer une structure. Le mot structure est quelque peu intimidant, mais en vérité il n'y a rien de bien compliqué. Une structure est un groupe de données simples (variables, etc) qui, une fois regroupées, permet de définir un ensemble plus complexe. Nous savons que nous devons garder une trace de nos étoiles. Vous allez voir que la 7ème ligne ci-dessous est stars. Nous savons que chaque étoile aura trois valeurs pour définir la couleur, et ces valeurs seront des entiers. La 3ème ligne (int r, g, b) définit trois entiers. Un pour le rouge (r), un pour le vert (g), et un pour le bleu (b). Nous savons que les étoiles seront à différentes distances du centre de l'écran, et qu'elles pourront être placées sur l'un des 360 angles différents à partir du centre. Si vous regardez à la 4ème ligne ci-dessous, vous remarquerez une valeur réelle nommée dist. Elle permettra de mémoriser la distance entre l'étoile et le centre de l'écran. La 5ème ligne crée un réel nommé angle. Cela permet de stocker les angles des étoiles.

    Ainsi, maintenant nous avons ce groupement de données qui permet de décrire la couleur, la distance, et l'angle d'une étoile sur l'écran. Par contre nous devons mémoriser les informations de plus d'une étoile. Au lieu de créer 50 valeurs pour le rouge, 50 pour le vert, 50 pour le bleu, 50 pour la distance, et 50 pour l'angle, nous allons juste créer un tableau nommé star. A chaque case dans le tableau star, nous y stockerons une occurrence de la structure stars. Nous créons ce tableau à la 8ème ligne ci-dessous. Voici le détail de la 8ème ligne : stars star[num]. Le type du tableau est stars. stars est une structure. Ainsi le tableau contiendra toutes les informations des structures. Le nom du tableau est star. Le nombre de cases du tableau est [num]. Hors comme num = 50, nous avons maintenant un tableau nommé star. Notre tableau stocke les éléments de la structure stars. C'est beaucoup plus facile ainsi de mémoriser les informations sur les étoiles qu'avec des variables séparées. Ce qui aurait été une chose stupide à faire, et cela ne nous aurait pas permis d'ajouter ou retirer facilement des étoiles en modifiant la valeur constante de num.

    Tableau de structures définissant les étoiles
    Sélectionnez
    typedef struct                      // On cree une structure pour definir une étoile
{
    int r, g, b;                    // Le couleur de l'etoile
    GLfloat dist;                   // La distance de l'etoile par rapport au centre
    GLfloat angle;                  // L'angle courant de l'étoile
}stars;                             // Le nom de la structure est stars

stars star[num];                    // Cree un tableau 'star' de 'num' case contenant des structures 'stars'

    Ensuite, nous allons déclarer des variables pour mémoriser à quelle distance sont les étoiles de l'observateur (zoom), et quel est l'angle à partir duquel on voit les étoiles (tilt). Nous créons également une variable nommée spin qui permettra de faire tourner le scintillement de l'étoile sur l'axe des z, comme si elles tournaient sur elle-même.

    loop est une variable qu'on utilisera dans le programme pour dessiner les 50 étoiles, et texture[1] permettra de stocker une texture en noir et blanc que nous aurons chargé. Si vous voulez plus de textures, vous devez modifier la valeur de un à autant de texture que vous souhaitez utiliser.

    Déclaration des variables globales
    Sélectionnez
    GLfloat   zoom=-15.0f;                                    // Distance entre l'observateur et les etoiles
GLfloat   tilt=90.0f;                                     // Inclinaison de l'observateur
GLfloat   spin;                                           // Rotation du scintillement des etoiles
GLuint    loop;                                           // Variable globales pour le parcours des etoiles
GLuint    texture[1];                                     // Tableau pour stocker une texture
LRESULT   CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);   // Declaration de WndProc

    Après le code ci-dessus, nous allons maintenant ajouter la fonction permettant de charger notre texture. Je n'ai pas besoin d'expliquer en détail le code. C'est le même code que nous avons utilisé dans les leçons 6, 7 et 8. L'image que nous allons charger ce coup-ci se nomme star.bmp. Nous allons générer uniquement une texture par l'intermédiaire de glGenTextures(1, &texture[0]). La texture utilisera le filtrage linéaire.

    II-B. La fonction LoadBMP

    Fonction pour charger l'image
    Sélectionnez
    AUX_RGBImageRec *LoadBMP(char *Filename)    // Permet de charger une fichier image Bitmap
{
    FILE *File=NULL;                        // Descripteur de fichier
    if (!Filename)                          // On s'assure qu'un nom de fichier a ete passe en parametre
    {
        return NULL;                        // Sinon on retourne NULL
    }
    File=fopen(Filename,"r");               // On verifie que le fichier existe
    if (File)                               // Est-ce qu'il existe ?
    {
        fclose(File);                       // On ferme le descripteur
        return auxDIBImageLoad(Filename);   // Charge l'image, et retourne un pointeur
    }
    return NULL;                            // Si le fichier n'existe pas, on retourne NULL
}

    II-C. La fonction LoadGLTextures

    C'est la portion de code qui permet de charger l'image (en appelant le code précédent) et qui la convertit en une texture. Status est utilisée pour indiquer si la texture a bien été chargée et créée.

    Charge une image, et la convertie en texture
    Sélectionnez
    int LoadGLTextures()                                 // Charge une image et la convertie en texture
{
    int Status=FALSE;                                // Indique le bon deroulement de l'operation
    AUX_RGBImageRec *TextureImage[1];                // Cree un emplacement memoire pour la texture
    memset(TextureImage,0,sizeof(void *)*1);         // Reinitialise le pointeur à NULL
    // Charge l'image, verifie les erreurs, on quitte si l'image n'est pas trouvee
    if (TextureImage[0]=LoadBMP("Data/Star.bmp"))
    {
        Status=TRUE;                                 // Initialise Status à TRUE
        glGenTextures(1, &texture[0]);               // Genere une texture
        // Cree une texture filtree lineairement
        glBindTexture(GL_TEXTURE_2D, texture[0]);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
        glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX,
            TextureImage[0]->sizeY,0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
    }
    if (TextureImage[0])                            // Si la texture existe
    {
        if (TextureImage[0]->data)                  // Si l'image de la texture existe
        {
            free(TextureImage[0]->data);            // On libere la memoire de l'image de la texture
        }
        free(TextureImage[0]);                      // On libere la structure de la texture
    }
    return Status;                                  // On retourne le statut
}

    II-D. La fonction InitGL

    Maintenant nous allons initialiser OpenGL pour qu'il fasse ce qu'on veut. Nous n'utiliserons pas de test de profondeur dans ce projet, donc assurez vous, si vous utilisez le code de la leçon un, d'enlever glDepthFunc(GL_LEQUAL); et glEnable(GL_DEPTH_TEST); sinon vous n'obtiendrez pas de résultats concluant. Nous allons utiliser le plaquage de texture dans ce code, donc assurez vous de ne pas laisser de lignes de code présentes dans la leçon 1.Vous remarquerez également que l'on active le plaquage de texture, ainsi que le mélange.

    Initialisation d'OpenGL
    Sélectionnez
    int InitGL(GLvoid)                                   // Tous les reglages d'OpenGL se font ici
{
    if (!LoadGLTextures())                           // On appelles notre fonction pour charger les textures
    {
        return FALSE;                                // Si les textures ne se sont pas chargees, on retourne FALSE
    }
    glEnable(GL_TEXTURE_2D);                         // Activation de l'application de textures
    glShadeModel(GL_SMOOTH);                         // Active les ombres douces
    glClearColor(0.0f, 0.0f, 0.0f, 0.5f);            // Fond d'ecran noir
    glClearDepth(1.0f);                              // Configuration du tampon de profondeur
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);     // Pour avoir des jolis calculs de perspective
    glBlendFunc(GL_SRC_ALPHA,GL_ONE);                // Positionne la fonction de melange sur la transparence
    glEnable(GL_BLEND);                              // Activation du melange

    Le code suivant est nouveau. Il permet d'initialiser l'angle, la distance et la couleur de chaque étoile au démarrage. Remarquez comment il est facile de changer les informations dans une structure. La boucle parcourt les 50 étoiles. Pour changer l'angle de star[1], vous avez juste à écrire star[1].angle = {un nombre}. C'est simple !

    Début de la boucle sur les étoiles
    Sélectionnez
        for (loop=0; loop<num; loop++)     // Cree une boucle sur les etoiles
    {
        star[loop].angle=0.0f;         // Initialise l'angle des etoiles à zero

    Je calcule la distance en prenant l'index de l'étoile courante (donc la valeur de loop) et en le divisant par le maximum d'étoiles présentes. Ensuite, je le multiplie le résultat par 5.0f. Pour résumer, cela permet de bouger chaque étoile un petit peu plus loin que la précédente. Quand loop sera égale à 50 (la dernière étoile), loop divisé par num sera égale à 1.0f. La raison pour laquelle je multiplie par 5.0f est parce que 1.0f * 5.0f est égale à 5.0f. 5.0f est l'extrémité de l'écran. Je n'ai pas envie que des étoiles soient en dehors de l'écran, donc 5.0f est parfait. Si vous fixez le zoom plus loin dans l'écran, vous feriez mieux d'utiliser une valeur plus grande que 5.0f, mais vos étoiles seront un peu plus petite (à cause de la perspective).

    Vous remarquerez que les couleurs de chacune des étoiles est attribuées suivant des valeurs aléatoires comprises entre 0 et 255. Vous devez être surpris que l'on puisse utiliser des valeurs aussi grandes, alors que habituellement elles sont comprises entre 0.0f et 1.0f. Quand nous fixons la couleur, nous utilisons glColor4ub au lieu de glColor4f. ub signifie Unsigned Byte. Un octet peut contenir n'importe quelle valeur comprise entre 0 et 255. Dans ce programme, c'est plus facile d'utiliser des octets que des nombres réels pour obtenir des valeurs aléatoires.

    Initialisation de la position et de la couleur
    Sélectionnez
            star[loop].dist=(float(loop)/num)*5.0f;          // Calcul la distance depuis le centre
        star[loop].r=rand()%256;                         // Donne a star[loop] une valeur aleatoire pour le rouge
        star[loop].g=rand()%256;                         // Donne a star[loop] une valeur aleatoire pour le vert
        star[loop].b=rand()%256;                         // Donne a star[loop] une valeur aleatoire pour le bleu
    }
    return TRUE;                                         // L'initialisation s'est correctement deroulee
}

    II-E. La fonction DrawGLScene

    Le code pour redimensionner la fenêtre est le même, donc nous allons passer directement à la partie où on dessine. Si vous utilisez le code de la leçon une, supprimer la fonction DrawGLScene, et copiez juste ce qu'il y a en dessous. Il y a uniquement deux lignes de code dans la leçon un de toute façon, donc vous n'avez pas beaucoup de choses à supprimer.

    Fonction de dessin
    Sélectionnez
    int DrawGLScene(GLvoid)                                    // C'est ici que l'on fera tous les dessins
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    // Efface l'écran et le tampon de profondeur
    glBindTexture(GL_TEXTURE_2D, texture[0]);              // Selectionne notre texture
    for (loop=0; loop<num; loop++)                         // Boucle sur nos etoiles
    {
        glLoadIdentity();                                  // Reinitialise la vue avant de dessiner chacune des etoiles
        glTranslatef(0.0f,0.0f,zoom);                      // Zoom dans l'écran (En utilisant la valeur de 'zoom')
        glRotatef(tilt,1.0f,0.0f,0.0f);                    // Tourne la vue (En utilisant la valeur de 'tilt')

    Maintenant nous allons bouger les étoiles. Les étoiles partent du milieu de l'écran. La première chose à faire est de tourner la scène sur l'axe des x. Si on tourne de 90 degrés, l'axe des x n'ira plus de gauche à droite, mais d'avant vers l'arrière de l'écran. Voici un exemple pour clarifier un peu le problème. Imaginez que vous êtes au milieu d'une pièce. Maintenant imaginez qu'il y a écrit -x sur le mur à gauche de vous, -z sur le mur devant vous, +x sur le mur à droite de vous, +z sur le mur derrière vous. Si la pièce tourne de 90 degrés vers la droite, mais que vous ne bougez pas, sur le mur en face de vous il n'y aura plus écrit -z mais -x. Tous les murs auront bougés. -z sera à droite, +z sera à gauche, -x sera devant et +x derrière vous. Vous comprenez ? En applicant une rotation sur votre scène, vous changez les directions des plans x et z.

    La seconde ligne de code permet de bouger vers d'une valeur positive sur le plan des x. Normalement une valeur positive sur x devrait nous faire bouger vers la partie droite de l'écran (où est +x habituellement), mais comme nous avons fait une rotation sur le plan des y, le '+x' peut se trouver n'importe où. Si nous appliquons une rotation de 180 degrés, il sera sur le coté gauche de l'écran au lieu de la partie droite. Donc lorsque vous allez bouger d'une valeur positive sur le plan des x, vous pouvez donc bouger vers la gauche, la droite, l'avant ou l'arrière.

    Rotation et déplacement
    Sélectionnez
        glRotatef(star[loop].angle,0.0f,1.0f,0.0f);         // Tourne l'étoile selon son angle
    glTranslatef(star[loop].dist,0.0f,0.0f);            // Bouge d'une valeur positive sur le plan des X

    Maintenant nous allons passer à une partie un peu plus délicate. L'étoile est en fait une texture plate. Maintenant si vous dessinez un rectangle plat au centre de l'écran et que vous le texturez, il sera joli. Il vous fera face. Mais si vous appliquez une rotation sur l'axe des y de 90 degrés, la texture sera face aux côtés droits et gauches de l'écran. La seule chose que vous verrez sera une ligne fine. Nous ne voulons pas que cela arrive. Nous voulons que les étoiles soient face à l'écran en permanence, peu importe si cela nous oblige à faire plus de rotation ou d'inclinaison de l'écran.

    On fait ceci en annulant la rotation que nous avons fait précédemment, juste avant de dessiner l'étoile. Vous annulez la rotation dans le sens inverse. Dans la partie précédente, nous avons incliné l'écran, et ensuite nous avons appliqué une rotation aux étoiles en fonction d'un angle. Dans l'ordre inverse, nous devons faire une rotation inverse aux étoiles de l'angle adéquat. Pour ce faire, nous allons utiliser la valeur négative de l'angle, et appliquer la rotation avec ce nouvel angle. Ainsi, si nous avions tourné l'étoile de 10 degrés, en appliquant une rotation de -10 degrés, l'étoile sera de nouveau en face de l'écran. Donc la première ligne ci-dessous annule la rotation sur l'axe des y. Ensuite nous avons besoin d'annuler l'inclinaison de l'écran sur l'axe des x. Pour faire ceci, nous inclinons juste l'écran d'une valeur de -tilt. Après avoir annuler les rotations en x et en y, l'étoile fera face de nouveau complètement à l'écran.

    Annulation des rotations précédentes
    Sélectionnez
        glRotatef(-star[loop].angle,0.0f,1.0f,0.0f);     // Annule la rotation de l'angle
    glRotatef(-tilt,1.0f,0.0f,0.0f);                 // Annule l'inclinaison de l'ecran

    Si twinkle est égale à TRUE, nous dessinerons une étoile qui ne tourne pas à l'écran. Pour obtenir une couleur différente, nous prenons le nombre maximum d'étoiles (num) et le soustrayons à l'index courant pour les étoiles (loop), ensuite on soustrait 1 car notre boucle (loop) ne va que entre 0 et num-1. Si le résultat est égal à 10, nous utiliserons la couleur de l'étoile numéro 10. De cette manière, les couleurs de deux étoiles seront différentes. Ce n'est pas la meilleure manière de faire, mais elle est efficace. La dernière valeur est la valeur alpha. Plus la valeur sera petite, plus l'étoile sera sombre.

    Si twinkle est égale à TRUE, chaque étoile sera dessinée en double. Cela ralentira le programme légèrement, selon l'ordinateur que vous avez. Si twinkle est activé, les couleurs des deux étoiles se mélangeront en créant des couleurs vraiment sympathiques. De plus comme cette étoile ne tournera pas, elle apparaîtra comme si elle était animée lorsque twinkle sera activé (regardez par vous-même si vous ne comprenez pas ce que je dis).

    Remarquez également comment il est facile d'ajouter des couleurs à la texture. Même si la texture est blanche et noire, elle prendra sa véritable apparence en fonction de la couleur que l'on sélectionne avant de dessiner la texture. De plus, prenez note que nous utilisons des octets pour les valeurs de la couleur plutôt que des nombres réels. Même la valeur de la composante alpha est un octet.

    Dessin du scintillement
    Sélectionnez
            if (twinkle)                           // Si le scintillement est active
        {
            // Applique une couleur en utilisant des octets
            glColor4ub(star[(num-loop)-1].r,star[(num-loop)-1].g,star[(num-loop)-1].b,255);
            glBegin(GL_QUADS);                 // Commence a dessiner notre carre texture
                glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,-1.0f, 0.0f);
                glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,-1.0f, 0.0f);
                glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 0.0f);
                glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 0.0f);
            glEnd();                           // Fin du dessin de notre carre texture
        }

    Maintenant nous allons dessiner l'étoile principale. La seule différence avec le code précédent est que l'étoile est toujours dessinée et cette étoile tourne autour de l'axe des z.

    Dessin de l'étoile principale
    Sélectionnez
            glRotatef(spin,0.0f,0.0f,1.0f);          // Applique une rotation a l'etoile sur l'axe des Z
                                                 // Applique une couleur en utilisant des octets
        glColor4ub(star[loop].r,star[loop].g,star[loop].b,255);
        glBegin(GL_QUADS);                       // Commence a dessiner notre carre texture
            glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,-1.0f, 0.0f);
            glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,-1.0f, 0.0f);
            glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 0.0f);
            glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 0.0f);
        glEnd();                                // Fin du dessin de notre carre texture

    Ici est la partie où nous effectuons tous les mouvements. Nous tournons l'étoile normale en incrémentant la valeur de spin. Ensuite on change l'angle de chacun des angles. L'angle de chaque étoile est incrémenté de loop/num. Ce qui fait que les étoiles tournent plus vite si elles sont éloignées du centre. Les étoiles les plus proches du centre tourneront moins vite. Et enfin, on décrémente la distance entre chaque étoile et le centre de l'écran. Nous aurons l'impression que les étoiles se font aspirer vers le centre de l'écran.

    Mouvement des étoiles
    Sélectionnez
            spin+=0.01f;                                // Change la valeur d'inclinaison des etoiles
        star[loop].angle+=float(loop)/num;          // Change l'angle de l'etoile
        star[loop].dist-=0.01f;                     // Change la distance entre l'etoile et le centre

    Les lignes suivantes vérifient si les étoiles sont arrivées au centre de l'écran ou non. Quand une étoile touche le centre de l'écran, on lui attribue une nouvelle couleur, et on la bouge de 5 unités du centre, ainsi elle peut reprendre son parcours vers le centre comme si c'était une nouvelle étoile.

    Traitement des étoiles arrivées au centre de l'écran
    Sélectionnez
            if (star[loop].dist<0.0f)            // Est-ce que l'etoile est arrivee au centre ?
        {
            star[loop].dist+=5.0f;           // Bouge l'etoile de 5 unités loin du centre
            star[loop].r=rand()%256;         // Attribution d'une nouvelle composante rouge
            star[loop].g=rand()%256;         // Attribution d'une nouvelle composante verte
            star[loop].b=rand()%256;         // Attribution d'une nouvelle composante bleue
        }
    }
    return TRUE;                             // Tout s'est bien deroule
}

    Maintenant nous allons ajouter le code qui vient vérifier si une touche a été pressée. Allez plus bas au niveau de la fonction WinMain(). Regardez la ligne SwapBuffers(hDC). Nous allons ajouter le code qui vérifier les touches juste en dessous de cette ligne.

    II-F. La gestion clavier

    Les lignes suivantes vérifient si la touche 'T' a été pressée. Si elle a été appuyée et qu'elle n'est pas maintenue pressée, ce qui suit sera exécuté. Si twinkle est égale à FALSE, on lui attribuera TRUE, sinon on lui attribuera FALSE. Lorsque 'T' sera pressée, tp prendra la valeur TRUE. Cela permet d'éviter que le code soit exécuté encore et encore durant la période où la touche 'T' est pressée.

    Activation du scintillement
    Sélectionnez
            SwapBuffers(hDC);               // Echange les tampons (Double Buffering)
        if (keys['T'] && !tp)           // Si 'T' est pressee et tp est egal a FALSE
        {
            tp=TRUE;                    // Si oui, on affecte TRUE à tp
            twinkle=!twinkle;           // On affecte a twinkle l'oppose de ce qu'il est
        }

    Le code qui suit vérifie si vous avez relâché la touche 'T'. Si c'est le cas, tp prend la valeur FALSE. En appuyant sur la touche 'T' cela ne fera rien tant que tp n'est pas égale à FALSE. Donc cette portion de code est très importante.

    Relâchement de la touche 'T'
    Sélectionnez
            if (!keys['T'])                   // Est-ce que la touche 'T' a ete relachee ?
        {
            tp=FALSE;                     // Si oui, tp devient egal a FALSE
        }

    Le reste du code vérifie si les touches flèche haut, flèche bas, page haut ou page bas sont pressées.

    Vérification de l'état des touches
    Sélectionnez

        if (keys[VK_UP])                // Est-ce que la touche 'fleche haut' est pressee ?
        {
            tilt-=0.5f;                 // Incline l'ecran vers le haut
        }
        if (keys[VK_DOWN])              // Est-ce que la touche 'fleche bas' est pressee ?
        {
            tilt+=0.5f;                 // Incline l'ecran vers le bas
        }
        if (keys[VK_PRIOR])             // Est-ce que la touche 'page haut' est pressee ?
        {
            zoom-=0.2f;                 // Zoom vers l'interieur
        }
        if (keys[VK_NEXT])              // Est-ce que la touche 'page bas' est pressee ?
        {
            zoom+=0.2f;                 // Zoom vers l'exterieur
        }

    Comme pour tous les précédents tutoriels, assurez vous que le titre de la fenêtre est correct.

    Modification du titre de la fenêtre
    Sélectionnez
            if (keys[VK_F1])                    // Est-ce que la touche 'F1' est pressee ?
        {
            keys[VK_F1]=FALSE;              // Si oui, on la remets a FALSE
            KillGLWindow();                 // On detruit notre fenetre courante
            fullscreen=!fullscreen;         // On inverse le mode Plein ecran / Mode fenetre
                                            // On Recree notre fenetre OpenGL
            if (!CreateGLWindow("Tutoriel de NeHe sur les textures, l'eclairage et le clavier",640,480,16,fullscreen))
            {
                return 0;                   // On quitte si la fenetre ne s'est pas creee
            }
        }
    }
}

    II-G. Conclusion

    Dans ce tutoriel, j'ai essayé de vous expliquer avec le plus de détails possibles comment charger une image en noir et blanc, supprimer les espaces noirs autour de l'images (en utilisant la transparence), ajouter des couleurs à l'image, et bouger l'image dans la scène en 3D. Je vous ai également montré comment créer de jolies couleurs et des animations en se faisant chevaucher une seconde copie de l'image au-dessus de l'image originale. Une fois que vous avez bien compris tous ce que j'ai expliqué jusqu'ici, vous ne devriez pas avoir de problème pour réaliser vos propres demos 3D. Toutes les bases d'OpenGL ont été parcourues !

    Enfin, voici une petite image de ce que vous devez voir :

    Image de l'application
    Image de l'application

    Jeff Molofee ( NeHe )

    III. Téléchargements

    Compte tenu du nombre de versions de codes sources pour les tutoriels nehe, nous les laissons en anglais. En principe, si vous avez compris le code présenté dans ce tutoriel (et les tutoriels antérieurs), vous n'aurez pas de mal à le comprendre :

    • Borland C++ Builder 6 (Conversion par Christian Kindahl)
    • C# (Conversion par Brian Holley)
    • Code Warrior 5.3 (Conversion par Scott Lupton)
    • CygWin (Conversion par Stephan Ferraro)
    • D (Conversion par Familia Pineda Garcia)
    • Delphi (Conversion par Michal Tucek)
    • Dev C++ (Conversion par Dan)
    • Euphoria (Conversion par Evan Marshall)
    • Game GLUT (Conversion par Milikas Anastasios)
    • Irix (Conversion par Lakmal Gunasekara)
    • Java (Conversion par Jeff Kirby)
    • Jedi-SDL (Conversion par Dominique Louis)
    • JOGL (Conversion par Abdul Bezrati)
    • LCC Win32 (Conversion par Robert Wishlaw)
    • Linux (Conversion par Richard Campbell)
    • Linux GLX (Conversion par Mihael Vrbanec)
    • Linux SDL (Conversion par Ti Leggett)
    • LWJGL (Conversion par Mark Bernard)
    • Mac OS (Conversion par Anthony Parker)
    • Mac OS X/Cocoa (Conversion par Bryan Blackburn)
    • MASM (Conversion par Nico (Scalp))
    • VC++/OpenIL (Conversion par Denton Woods)
    • Pelles C (Conversion par Pelle Orinius)
    • Power Basic (Conversion par Angus Law)
    • Python (Conversion par Ryan Showalter)
    • Solaris (Conversion par Lakmal Gunasekara)
    • Visual Basic (Conversion par Peter de Tagyos)
    • Visual Fortran (Conversion par Jean-Philippe Perois)
    • Visual Studio
    • Visual Studio NET (Conversion par Grant James)


Charger et se déplacer dans un univers 3D

  • Charger et se déplacer dans un univers 3D

    • I. Contributions
    • II. Tutoriel
      • II-A. Structures de données
      • II-B. Chargement de fichier
      • II-C. Affichage de notre univers 3D
    • III. Téléchargements
    • IV. Remerciements
    • V. Liens

    Voici le tutoriel que vous attendez tous ! Ce tutoriel a été conçu par le programmeur nommé Lionel Brits. Dans cette leçon vous allez apprendre comment charger un monde 3D à partir d'un fichier, et comment se déplacer à travers ce monde 3D. Le code est basé sur celui de la leçon 1, toutefois, le tutoriel explique seulement les nouvelles parties de code, c'est à dire le passage pour charger la scène 3D, et celui pour se déplacer dans cet univers 3D. Téléchargez le code ci-dessous, et parcourez-le en parallèle de votre lecture de ce tutoriel. Les touches à essayer dans le programme sont les suivantes : B (Mélange), F (Filtre), L (Eclairage) [Les lumières ne bougeront pas avec la scène] ainsi que les touches Page Haut et Page Bas. J'espère que cette contribution de Lionel vous fera plaisir.

    II. Tutoriel

    Ce tutoriel a été créé par Lionel Brits (ßetelgeuse). Cette leçon explique seulement les parties de code qui ont été rajoutées. En ajoutant seulement les lignes suivantes, le programme ne fonctionnera pas. Si vous êtes intéressé de savoir où est le bon emplacement des lignes de code suivantes, téléchargez le code source fourni, et parcourez le en même temps que vous lisez ce tutoriel.

    Bienvenue dans cet infâme tutoriel 10. Dès à présent vous savez coder un cube qui tourne, ou des couples d'étoiles, et vous avez les bases pour la programmation 3D. Mais attendez ! Ne partez pas et ne commencez pas à coder Quake IV tout de suite. Les simples cubes qui vrillent ne feront pas de jolis adversaires pour les duels :-) De nos jours vous devez avoir un large, compliqué et dynamique univers 3D avec 6 angles de liberté et des effets sophistiqués comme les miroirs, les portails, les distorsions et bien sûr tout ceci avec un haut taux de rafraîchissement de l'image. Ce tutoriel explique la structure basique d'un monde 3D, et aussi comment bouger dedans.

    II-A. Structures de données

    Il est parfaitement compréhensible de vouloir coder un environnement 3D en se basant sur de longues suites de nombres, mais cela devient vite compliqué lorsque la complexité de l'environnement grandie. Pour cette raison, nous devons grouper nos données dans un modèle plus malléable. Au sommet de notre liste il y a les secteurs. Chaque monde 3D peut se percevoir comme une collection de secteurs. Un secteur peut être une pièce, un cube, ou n'importe quel volume fermé.

    Définition du type SECTOR
    Sélectionnez
    typedef struct tagSECTOR // Construction de notre structure de donnees Sector
{
  int numtriangles;      // Nombre de triangles dans le secteur
  TRIANGLE* triangle;    // Pointeur sur le tableau de triangles
}SECTOR;                 // Nom de la structure : SECTOR

    Un secteur contient une série de polygones, dans la prochaine catégorie ce sera le triangle. (Nous allons utiliser les triangles à partir de maintenant, ils faciliteront la mise en place du code.)

    Définition du type TRIANGLE
    Sélectionnez
    typedef struct tagTRIANGLE  // Construction de notre structure de donnees Triangle
{
   VERTEX vertex[3];        // Tableau de trois sommets
}TRIANGLE;                  // Nom de la structure : TRIANGLE

    Un triangle est simplement un polygone fait de trois sommets, ce qui nous conduit à notre dernière catégorie. Les sommets contiennent les vraies données utiles à OpenGL. Nous définissons chaque sommet des triangles par leurs positions dans l'espace 3D (x, y, z) ainsi que par leurs coordonnées de texture (u, v).

    Définition du type VERTEX
    Sélectionnez
    typedef struct tagVERTEX  // Construction de notre structure de donnees Vertex
{
  float x, y, z;          // Coordonnees 3D
  float u, v;             // Coordonnees de texture
}VERTEX;                  // Nom de la structure : VERTEX

    II-B. Chargement de fichier

    Stocker les données de notre univers 3D dans notre programme le rend statique et vite ennuyeux. Charger le monde depuis un fichier rajoute une certaine flexibilité. En effet vous pouvez ainsi tester différents univers sans recompiler votre programme. Un autre avantage réside dans le fait que les utilisateurs peuvent échanger et modifier les univers sans connaître le fonctionnement interne de votre programme. Nous allons utiliser le format texte pour décrire les données de notre univers. Cela permettra de l'éditer facilement, et de produire moins de code. Nous nous occuperons du format binaire une autre fois.

    Le problème est de savoir comment nous allons récupérer les données depuis notre fichier. Premièrement, nous créons une nouvelle fonction nommée SetupWorld(). Nous manipulerons notre fichier à travers la variable filein. Le fichier est ouvert en mode lecture seule. Nous devons également fermer le fichier lorsque nous avons fini. Voici ce que cela donne :

    Fonction de lecture du fichier
    Sélectionnez
    //Precedente declaration : char* worldfile = "data\\world.txt";
void SetupWorld()                     // Creation de notre univers
{
   FILE *filein;                     // Descripteur de notre fichier contenant l'univers
   filein = fopen(worldfile, "rt");  // Ouverture du fichier
   ...
   (Lecture des données)
   ...
   fclose(filein);                   // Fermeture du fichier
   return;                           // Fin de la fonction
}

    Le prochain problème que nous allons traiter est de pouvoir lire chaque ligne de texte dans une variable. Ceci peut être fait de plusieurs manières. Le problème réside dans le fait que toutes les lignes ne contiennent pas forcément des informations utiles. Les lignes blanches et les lignes de commentaires ne doivent pas être lues. Nous allons créer une fonction nommée readstr(). Cette fonction va lire une ligne utile de texte et la stocker une chaîne de caractères initialisée. Voici le code :

    Fonction pour lire une ligne
    Sélectionnez
    void readstr(FILE *f,char *string)             // Lit une ligne et la stocke dans une chaine de caracteres
{
    do                                         // Debut de la boucle
    {
        fgets(string, 255, f);                 // Lit une ligne
    }while ((string[0] == '/') || (string[0] == '\n'));        // Si c'est un commentaire ou une ligne vide, on reboucle
    return;                                    // On quitte la fonction
}

    Ensuite, nous devons lire les données concernant les secteurs. Cette leçon va se baser avec un seul secteur, mais il est facile de développer la solution fonctionnant pour plusieurs secteurs. Retournons à la fonction SetupWorld(). Notre programme doit savoir combien de triangles sont présents dans le secteur. Dans le fichier de données, nous allons stocker le nombre de triangles comme suit :

    Contenu du fichier pour nombre de polygones
    Sélectionnez
    NUMPOLLIES n

    Voici le code pour lire le nombre de triangles :

    Lecture du nombre de triangle
    Sélectionnez
    int numtriangles;                                    // Nombre de triangles dans le secteur
char oneline[255];                                   // Chaîne de caracteres pour stocker les données
...
readstr(filein,oneline);                             // On lit la premiere ligne utile du fichier
sscanf(oneline, "NUMPOLLIES %d\n", &numtriangles);   // On recupere le nombre de triangles

    Le reste de l'algorithme pour charger notre univers 3D se base sur le même procédé. Ensuite, nous initialisons notre secteur et nous stockons les données lues dedans :

    Lecture d'un secteur
    Sélectionnez
    // Precedente declaration : SECTOR sector1;
char oneline[255];        // Chaine de caracteres contenant les informations lues
int numtriangles;         // Nombre de triangles du secteur
float x, y, z, u, v;      // Coordonnees 3D et de texture
...
sector1.triangle = new TRIANGLE[numtriangles];             // Allocation mémoire de numtriangles emplacements
sector1.numtriangles = numtriangles;                       // Definis le nombre de triangle de sector1
                                                           // On examine chacun des triangles du secteur
for (int triloop = 0; triloop < numtriangles; triloop++)   // On boucle sur tous les triangles
{
                                                           // On examine chacun des sommets du triangle
    for (int vertloop = 0; vertloop < 3; vertloop++)       // On boucle sur tous les sommets
    {
        readstr(filein,oneline);                           // On lit une chaine de caractere utile
                                                           // Convertit les donnees en coordonnees 3D et de texture
        sscanf(oneline, "%f %f %f %f %f", &x, &y, &z, &u, &v);
                                                           // Stocke les donnees dans le bon sommet
        sector1.triangle[triloop].vertex[vertloop].x = x;  // sector1, triangle n°triloop, sommet n°vertloop, valeur de x=x
        sector1.triangle[triloop].vertex[vertloop].y = y;  // sector1, triangle n°triloop, sommet n°vertloop, valeur de y=y
        sector1.triangle[triloop].vertex[vertloop].z = z;  // sector1, triangle n°triloop, sommet n°vertloop, valeur de z=z
        sector1.triangle[triloop].vertex[vertloop].u = u;  // sector1, triangle n°triloop, sommet n°vertloop, valeur de u=u
        sector1.triangle[triloop].vertex[vertloop].v = v;  // sector1, triangle n°triloop, sommet n°vertloop, valeur de v=v
    }
}

    Chaque triangle dans notre fichier de données est déclaré comme ci-dessous :

    Déclaration d'un triangle dans le fichier de données
    Sélectionnez
    X1 Y1 Z1 U1 V1
X2 Y2 Z2 U2 V2
X3 Y3 Z3 U3 V3

    II-C. Affichage de notre univers 3D

    Maintenant que nous avons chargé notre secteur en mémoire, nous devons l'afficher sur l'écran. Plus loin nous ferons quelques rotations et translations, mais notre caméra sera toujours centrée au point d'origine (0, 0, 0). Tous les bons moteurs 3D permettent à l'utilisateur de naviguer à travers leurs univers 3D, donc le notre permettra également de le faire. Une des façons de le faire est de bouger la caméra et de dessiner l'environnement 3D en fonction de la position de la caméra. Ceci est lent et dur à coder. Voici ce que nous allons faire : Tourner et translater la position de la caméra en fonction des touches pressées par l'utilisateur Tourner l'univers autour de l'origine dans le sens opposé de la rotation de la caméra (ce qui donne l'illusion que la caméra à tourné) Déplacer le monde dans le sens opposé que la caméra (ce qui provoque aussi l'illusion que la caméra à bougé) Ceci est relativement simple à implémenter. Commençons tout de suite avec la première partie (rotation et translation de la caméra).

    Gestion de la caméra
    Sélectionnez
    if (keys[VK_RIGHT])                        // Est-ce que la touche Fleche Droite a ete pressee ?
{
    yrot -= 1.5f;                          // Tourne la scene vers la gauche
}
if (keys[VK_LEFT])                         // Est-ce que la touche Fleche Gauche a ete pressee ?
{
    yrot += 1.5f;                          // Tourne la scene vers la droite
}
if (keys[VK_UP])                           // Est-ce que la touche Fleche Haut a ete pressee ?
{
    xpos -= (float)sin(heading*piover180) * 0.05f;            // Bouge sur le plan des X dans la direction du joueur
    zpos -= (float)cos(heading*piover180) * 0.05f;            // Bouge sur le plan des Z dans la direction du joueur
    if (walkbiasangle >= 359.0f)             // Si walkbiasangle >= 359 ?
    {
        walkbiasangle = 0.0f;                // On met walkbiasangle a 0
    }
    else                                     // Autrement
    {
        walkbiasangle+= 10;                  // Si walkbiasangle < 359 on l'incremente de 10
    }
    walkbias = (float)sin(walkbiasangle * piover180)/20.0f;    // Le joueur "rebondit"
}
if (keys[VK_DOWN])                           // Est-ce que la touche Fleche Bas a ete pressee ?
{
    xpos += (float)sin(heading*piover180) * 0.05f;            // Bouge sur le plan des X dans la direction du joueur
    zpos += (float)cos(heading*piover180) * 0.05f;            // Bouge sur le plan des Z dans la direction du joueur
    if (walkbiasangle <= 1.0f)               // Si walkbiasangle <= 1 ?
    {
        walkbiasangle = 359.0f;              // On met walkbiasangle a 359
    }
    else                                     // Autrement
    {
        walkbiasangle-= 10;                  // Si walkbiasangle > 1 on le decremente de 10
    }
    walkbias = (float)sin(walkbiasangle * piover180)/20.0f;  // Le joueur "rebondit"
}

    C'était assez simple. Quand l'une ou l'autre des touches gauche ou droite sont pressées, la variable de rotation yrot est incrémentée ou décrémentée respectivement. Lorsque les touches haut ou bas sont pressées, une nouvelle destination pour la caméra est calculée en utilisant les fonctions sinus et cosinus (quelques notions de trigonométrie sont requises). Piover180 est simplement une fonction de conversion pour passer des degrés aux radians.

    Ensuite vous allez me demander : Qu'est-ce que walkbias ? C'est en quelque sorte pour compenser l'effet qui apparaît lorsqu'une personne se promène (avec un déplacement vertical de la tête dû au mouvement du corps ; en effet, une personne qui marche n'arrive généralement pas à garder sa tête immobile). Cela permet d'ajuster la position en Y de la caméra grâce à la fonction sinus. J'ai du rajouter ceci dans le code, sinon bouger en avant ou en arrière ne rendait pas terrible.

    Maintenant que nous avons ces variables, nous pouvons passer aux étapes deux et trois. Cela va se faire en mettant en place une boucle d'affichage des secteurs, en effet le programme n'est pas suffisamment compliqué pour mettre l'affichage d'un secteur dans une fonction séparée.

    Fonction d'affichage
    Sélectionnez
    int DrawGLScene(GLvoid)                                    // Dessin de la scene OpenGL
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    // Efface l'ecran et le tampon de profondeur
    glLoadIdentity();                                      // Reinitialise la matrice d'affichage courante
    GLfloat x_m, y_m, z_m, u_m, v_m;                       // Declaration des coordonnees 3D et de textures
    GLfloat xtrans = -xpos;                                // Utilise pour la translation du joueur sur l'axe des X
    GLfloat ztrans = -zpos;                                // Utilise pour la translation du joueur sur l'axe des Z
    GLfloat ytrans = -walkbias-0.25f;                      // Utilise pour les mouvements vers le haut et vers le bas
    GLfloat sceneroty = 360.0f - yrot;                     // Un angle de 360 degres pour la direction du joueur
    int numtriangles;                                      // Entier pour stocker le nombre de triangles
    glRotatef(lookupdown,1.0f,0,0);                        // Tourne vers le haut et le bas comme le regard
    glRotatef(sceneroty,0,1.0f,0);                         // La rotation dépend vers ou le joueur regarde
    glTranslatef(xtrans, ytrans, ztrans);                  // Deplace la scene en fonction de la position du joueur
    glBindTexture(GL_TEXTURE_2D, texture[filter]);         // Applique une texture en fonction de "filter"
    numtriangles = sector1.numtriangles;                   // Recupere le nombre de triangles du secteur 1
                                                           // On dessine chaque triangle
    for (int loop_m = 0; loop_m < numtriangles; loop_m++)  // Boucle autour de tous les triangles
    {
        glBegin(GL_TRIANGLES);                             // Commence a dessiner les triangles
            glNormal3f( 0.0f, 0.0f, 1.0f);                   // La normale pointe vers devant
            x_m = sector1.triangle[loop_m].vertex[0].x;       // Coordonnee X du 1er point
            y_m = sector1.triangle[loop_m].vertex[0].y;       // Coordonnee Y du 1er point
            z_m = sector1.triangle[loop_m].vertex[0].z;       // Coordonnee Z du 1er point
            u_m = sector1.triangle[loop_m].vertex[0].u;       // Coordonnee de texture U du 1er point
            v_m = sector1.triangle[loop_m].vertex[0].v;       // Coordonnee de texture V du 1er point
            glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);// Place les coordonnees de texture et le sommet
            x_m = sector1.triangle[loop_m].vertex[1].x;       // Coordonnee X du 2eme point
            y_m = sector1.triangle[loop_m].vertex[1].y;       // Coordonnee Y du 2eme point
            z_m = sector1.triangle[loop_m].vertex[1].z;       // Coordonnee Z du 2eme point
            u_m = sector1.triangle[loop_m].vertex[1].u;       // Coordonnee de texture U du 2eme point
            v_m = sector1.triangle[loop_m].vertex[1].v;       // Coordonnee de texture V du 2eme point
            glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);// Place les coordonnees de texture et le sommet
            x_m = sector1.triangle[loop_m].vertex[2].x;       // Coordonnee X du 3eme point
            y_m = sector1.triangle[loop_m].vertex[2].y;       // Coordonnee Y du 3eme point
            z_m = sector1.triangle[loop_m].vertex[2].z;       // Coordonnee Z du 3eme point
            u_m = sector1.triangle[loop_m].vertex[2].u;       // Coordonnee de texture U du 3eme point
            v_m = sector1.triangle[loop_m].vertex[2].v;       // Coordonnee de texture V du 3eme point
            glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);// Place les coordonnees de texture et le sommet
        glEnd();                                           // Fin du dessin du triangle
    }
    return TRUE;                                           // Fin de la fonction
}

    Et voila ! Nous avons fini de dessiner notre première image. Ce n'est pas exactement Quake mais nous ne sommes pas encore comme Carmack ou Abrash. Pendant que le programme fonctionne, vous pouvez appuyer sur F, B, PageHaut et PageBas pour voir ce que cela fait. Page Haut/Page Bas incline simplement la caméra vers le haut ou le bas (la même chose que si l'on faisait un panoramique coté par coté). La texture utilisée est juste texture de boue avec le logo de mon école appliquée en bumpmap. Enfin si NeHe décide de les garder :-).

    Donc maintenant vous pensez probablement à ce que vous allez faire ensuite. Ne pensez pas qu'à partir de ce code vous pourrez faire un moteur 3D ultra complet, en effet il n'a pas été prévue pour. Vous voulez sans doute plus qu'un unique secteur dans votre jeu, surtout si vous allez implémenter un système de portail. Vous allez sans doute vouloir avoir des polygones de plus de trois sommets, ce qui est essentiel pour les portails. Je continue de développer le code, maintenant on peut avoir plusieurs secteurs, et il gère les faces cachées (ne pas dessiner les polygones qui ne font pas face à la caméra). Je réécrirais un tutoriel prochainement, mais comme il y aura besoin de notions mathématiques plus complexes, j'écrirais un tutoriel sur les matrices avant.

    NeHe (05/01/00):

    J'ai entièrement commenté chaque ligne de code listée dans ce tutoriel. On comprend mieux ainsi. Quelques lignes seulement étaient commentées avant, maintenant elles le sont toutes.

    Lionel Brits ( ßetelgeuse )

    Enfin, voici une petite image de ce que vous devez voir :

    Image de l'application
    Image de l'application

    Jeff Molofee ( NeHe )

    III. Téléchargements▲

    Compte tenu du nombre de versions de codes sources pour les tutoriels nehe, nous les laissons en anglais. En principe, si vous avez compris le code présenté dans ce tutoriel (et les tutoriels antérieurs), vous n'aurez pas de mal à le comprendre :

    • Borland C++ Builder 6 (Conversion par Christian Kindahl)
    • C# (Conversion par Brian Holley)
    • Code Warrior 5.3 (Conversion par Scott Lupton)
    • CygWin (Conversion par Stephan Ferraro)
    • D (Conversion par Familia Pineda Garcia)
    • Delphi (Conversion par Michal Tucek)
    • Dev C++ (Conversion par Dan)
    • Euphoria (Conversion par Evan Marshall)
    • Game GLUT (Conversion par Milikas Anastasios)
    • Irix (Conversion par Rob Fletcher)
    • Java (Conversion par Jeff Kirby)
    • Jedi-SDL (Conversion par Dominique Louis)
    • JOGL (Conversion par Nicholas Campbell)
    • LCC Win32 (Conversion par Robert Wishlaw)
    • Linux (Conversion par Richard Campbell)
    • Linux GLX (Conversion par Mihael Vrbanec)
    • Linux SDL (Conversion par Ti Leggett)
    • LWJGL (Conversion par Mark Bernard)
    • Mac OS (Conversion par Anthony Parker)
    • Mac OS X/Cocoa (Conversion par Bryan Blackburn)
    • MASM (Conversion par Nico (Scalp))
    • VC++/OpenIL (Conversion par Denton Woods)
    • Pelles C (Conversion par Pelle Orinius)
    • Power Basic (Conversion par Angus Law)
    • Python (Conversion par Ryan Showalter)
    • Visual Basic (Conversion par Jarred Capellman)
    • Visual Basic (Conversion par Ross Dawson)
    • Visual Fortran (Conversion par Jean-Philippe Perois)
    • Visual Studio
    • Visual Studio NET (Conversion par Grant James)


ASM Mode Reel

Table des matières Plier Déplier

  • II. Les tutoriels
    • II-1. Effet de drapeau
    • II-2. Liste d'affichage
    • II-3. Les polices Bitmap
    • II-4. Les polices vectorielles
    • II-5. Application de texture sur les polices vectorielles
    • II-6. Effet de brouillard réaliste
    • II-7. Texture 2D de police
    • II-8. Quadriques
    • II-9. Moteur de particules utilisant les chaînes de triangles
    • II-10. Masquage

II. Les tutoriels

II-1. Effet de drapeau

Effet de drapeau

Effet de drapeau

Ce tutoriel vous est offert par Bosco. Le même gars qui a créé la superbe mini démo appelée 'worthless'. Il a aimé la réaction des personnes par rapport à sa démo, et a décidé d'aller plus loin en expliquant comment il a fait son effet à la fin de sa démo. Ce tutoriel se base sur le code de la leçon 6. À la fin du tutoriel, vous devriez être capable de manipuler les textures par vous-même. C'est vraiment un effet sympa et tellement mieux que les textures plates et immobiles. Si vous aimez le tutoriel, s'il vous plaît envoyez un courriel à Bosco pour le lui dire.

Voici le lien vers ce tutoriel : Effet de drapeau.

II-2. Liste d'affichage

Liste d'affichage

Vous voulez savoir comment accélérer vos programmes OpenGL ? Vous êtes fatigué d'écrire plein de code à chaque fois que vous voulez afficher un objet à l'écran ? Si c'est le cas, ce tutoriel est fait pour vous. Apprenez à utiliser les listes d'affichage d'OpenGL. Préconstruisez les objets et affichez-les à l'écran avec juste une seule ligne de code. Accélérez vos programmes en utilisant des objets précompilés dans vos programmes. Arrêtez d'écrire toujours le même code encore et encore. Laissez les listes d'affichage faire tout le travail pour vous ! Dans ce tutoriel nous allons construire une pyramide dans le style de Q-Bert en utilisant juste quelques lignes de code grâce aux listes d'affichage.

Voici le lien vers ce tutoriel : Liste d'affichage.

II-3. Les polices Bitmap

Les polices Bitmap

Les polices Bitmap

Je pense que la question que l'on m'a le plus souvent posée dans les courriels est : « Comment puis-je afficher du texte à l'écran en utilisant OpenGL ? ». Vous pouvez toujours appliquer une texture avec du texte à l'écran. Bien sûr vous avez peu de contrôle sur le texte et, sauf si vous êtes bon avec le fondu, le texte va souvent se fondre avec les images sur l'écran. Si vous voulez une méthode simple pour écrire du texte n'importe où à l'écran avec n'importe quelle couleur, en utilisant n'importe laquelle des polices sur votre ordinateur, alors ce tutoriel est pour vous. Les polices Bitmap sont des polices 2D redimensionnables qui ne peuvent pas être tournées. Elles sont toujours vues de face.

Voici le lien vers ce tutoriel : Les polices Bitmap.

II-4. Les polices vectorielles

Les polices vectorielles

Les polices vectorielles

Les polices Bitmap ne sont pas assez bonnes ? Vous avez besoin de plus de contrôle sur l'emplacement de vos polices sur l'axe des Z ? Vous avez besoin de polices 2D (polices avec une profondeur) ? Vous avez besoin d'une police en fil de fer ? Si tel est le cas, les polices vectorielles sont la solution parfaite. Vous pouvez les déplacer sur l'axe des Z et les redimensionner. Vous pouvez les tourner autour d'un axe (chose que vous ne pouviez pas faire avec les polices Bitmap) et, parce que les normales sont générées pour chaque caractère, la police peut être éclairée proprement. Vous pouvez construire les polices vectorielles en utilisant n'importe laquelle des polices installées sur votre ordinateur. C'est certainement une bonne police pour les jeux et les démos.

Voici le lien vers ce tutoriel : Les polices vectorielles.

II-5. Application de texture sur les polices vectorielles

Application de texture sur les polices vectorielles

Application de texture sur les polices vectorielles

Heureusement c'est mon dernier tutoriel sur les polices {sourire}. Cette fois nous allons apprendre une façon rapide et assez jolie pour appliquer une texture sur les polices, ou n'importe quel autre objet 3D. En jouant avec le code, vous pouvez créer quelques effets spéciaux cool en partant des objets utilisant l'application classique des textures à l'application de texture sur des objets sphériques. Au cas où vous ne le saviez pas... l'application de texture sur une sphère donne un effet métallique qui reflète n'importe quoi allant d'un motif à une image.

Voici le lien vers ce tutoriel : Application de texture sur les polices vectorielles.

II-6. Effet de brouillard réaliste

Effet de brouillard réaliste

Effet de brouillard réaliste

Le code de ce tutoriel a été généreusement offert par Chris Aliotta. Il est basé sur le code de la leçon 7, c'est pourquoi vous voyez la fameuse caisse encore une fois. :) C'est un tutoriel assez court visant à vous apprendre l'art du brouillard. Vous allez apprendre comment utiliser trois différents filtres de brouillard, comment changer sa couleur et comment définir où le brouillard va démarrer et où il va finir. C'est certainement un joli effet qu'il faut connaitre !

Voici le lien vers ce tutoriel : Effet de brouillard réaliste.

II-7. Texture 2D de police

Texture 2D de police

Texture 2D de police

La version originale de ce tutoriel a été écrite par Giuseppe D'Agata. Dans ce tutoriel vous allez apprendre à écrire à l'écran n'importe quel caractère ou phrase que vous voulez en utilisant des quadrilatères texturés. Vous allez apprendre comment lire un des 256 caractères d'une texture de taille 256x256, et finalement je vais vous montrer comment placer, à l'écran, chaque caractère en utilisant les pixels à la place des unités. Même si vous n'êtes pas intéressé par l'affichage de caractère se basant sur une texture 2D, il y a beaucoup à apprendre de ce tutoriel. Cela vaut définitivement le coup de le lire !

Voici le lien vers ce tutoriel : Texture 2D de police.

II-8. Quadriques

Quadriques

Le code de ce tutoriel a été écrit par GB Schmick le merveilleux site opérateur de TipTup. Il va vous emmener dans le merveilleux monde des quadriques. Avec les quadriques vous pouvez facilement créer des objets complexes tels que les sphères, disques, cylindres et cônes. Ces objets peuvent être créés avec une seule ligne de code. En ajoutant un peu de mathématiques et de méthode, il devrait être possible de transformer un objet en un autre. S'il vous plaît, dites à GB Schmisk ce que vous pensez du tutoriel, c'est toujours bon lorsque les visiteurs contribuent à ce site, cela profite à tous. Tous ceux qui ont contribué à un tutoriel ou à un projet méritent des remerciements, donc s'il vous plaît dites-leur que leur travail est apprécié !

Voici le lien vers ce tutoriel : Quadriques.

II-9. Moteur de particules utilisant les chaînes de triangles

Moteur de particules utilisant les chaînes de triangles

Moteur de particules utilisant les chaînes de triangles

Avez-vous déjà voulu faire une explosion, une fontaine à eau, une étoile flamboyante ou n'importe quel autre effet sympathique, dans votre programme OpenGL, mais que l'écriture d'un moteur de particules était trop difficile, ou simplement trop complexe ? Si oui, ce tutoriel est pour vous. Vous allez apprendre à programmer un simple mais joli moteur de particules. J'ai ajouté des bonus comme un mode arc-en-ciel et plein d'interactions avec le clavier. Vous allez aussi apprendre comment créer des chaines de triangles dans OpenGL. J'espère que vous trouverez le code à la fois utile et amusant.

Voici le lien vers ce tutoriel : Moteur de particules utilisant les chaînes de triangles.

II-10. Masquage

Masquage

Jusqu'à maintenant, nous avons fondu nos images sur l'écran. Bien que ce soit efficace, et que cela ajoute notre image à la scène, un objet transparent n'est pas toujours joli. Disons que vous voulez faire un jeu et que vous voulez créer un texte solide, ou une console avec une forme irrégulière. Avec le fondu que nous avons utilisé jusqu'à présent, la scène va apparaître à travers nos objets. En assemblant le fondu et une image masque, votre texte peut être solide. Vous pouvez aussi placer sur l'écran des images solides ayant une forme irrégulière. Un arbre avec des branches solides et des feuilles non transparentes ou une fenêtre avec un verre transparent et un contour solide. Plein de possibilités !

Voici le lien vers ce tutoriel : Masquage.

Copyright © 2000-2016 - www.developpez.com


Effet de drapeau

  • Effet de drapeau

    • I. Contributions
    • II. Tutoriel
      • II-A. Introduction
      • II-B. Structure de données
      • II-C. La fonction LoadGLTextures
      • II-D. La fonction InitGL
      • II-E. La boucle
      • II-F. DrawGLScene
      • II-G. Rendu
      • II-H. Conclusion
    • III. Téléchargements
    • IV. Remerciements
    • V. Liens

    Bien le bonjour à tous. Pour ceux qui souhaitent voir ce que nous allons faire ici, vous pouvez dorénavant télécharger ma démo à la fin de ce tutoriel ! Je suis 'Bosco' et je vais faire de mon mieux pour vous apprendre à créer une image animée d'un effet de vague. Ce tutoriel est basé sur le code et les connaissances requises du tutoriel n° 6 de NeHe. Vous pouvez télécharger les sources du tutoriel et placer le fichier bitmap que j'ai inclus dans le dossier 'data' avec le code source. Sinon vous pouvez utiliser votre propre texture si elle est appropriée à l'usage des textures avec OpenGL.

    II. Tutoriel

    Image du tutoriel

    II-A. Introduction

    Commençons par le début. Chargez le projet du tutoriel n° 6 dans Visual C++ ou un autre éditeur C++ et ajoutez la ligne suivante, juste après les autres lignes '#include'. Ce '#include' ci-dessous nous permet de travailler avec les fonctions mathématiques comme le sinus et le cosinus.

    Inclusion
    Sélectionnez
    #include <math.h>    // Pour la fonction sin()

    II-B. Structure de données

    Nous allons utiliser un tableau pour stocker les coordonnées x, y et z de la grille. Cette grille est composée de 45 points sur 45, ce qui par conséquent constitue 44 carrés sur 44. L'entier 'wiggle_count' (comprendre compteur d'ondulation) va stocker le coefficient de rapidité des vagues de la texture. Pour qu'à chaque seconde l'image soit belle, la variable 'hold' va stocker un flottant pour lisser les ondulations du drapeau. Les lignes suivantes peuvent être ajoutées en haut du programme, quelque part en dessous de la dernière ligne '#include' mais avant la ligne 'GLuint texture[1]'.

    Variables globales
    Sélectionnez
    float points[45][45][3];        // Le tableau pour stocker les coordonnées de la "vague"
int wiggle_count = 0;            // Le compteur utilisé pour contrôler la vitesse des ondulations
GLfloat hold;                    // Le flottant pour lisser les ondulations

    II-C. La fonction LoadGLTextures

    Allez à la fonction 'LoadGLTextures()', plus bas dans le code. Nous voulons utiliser la texture appelée 'Tim.bmp'. Trouvez la ligne 'LoadBMP("Data/NeHe.bmp")' et remplacez-la par 'LoadBMP("Data/Tim.bmp")'.

    Appel à LoadBMP
    Sélectionnez
    if (TextureImage[0]=LoadBMP("Data/Tim.bmp"))    // Charge le bitmap

    II-D. La fonction InitGL

    Maintenant, ajoutez le code suivant à la fin de la fonction InitGL() avant le return TRUE.

    Mise en place de glPolygonMode
    Sélectionnez
    glPolygonMode( GL_BACK, GL_FILL );    // La face de derrière est dessinée pleine
glPolygonMode( GL_FRONT, GL_LINE );    // La face de devant n'est dessinée qu'en mode fil de fer

    Ces lignes indiquent que nous voulons que les faces arrière des polygones soient complètement remplies et que les faces avant soient juste dessinées avec des lignes. C'est principalement un point de vue personnel. Cela a un rapport avec l'orientation des polygones ou la direction des sommets. Allez voir le 'Red Book' pour plus de précisions là-dessus. Tant que j'y suis, laissez-moi vous dire que ce livre est un atout majeur qui m'a aidé à apprendre OpenGL, sans oublier le site de NeHe ! Merci NeHe. Achetez le 'Red Book' écrit par Addison-Wesley. C'est une ressource incontournable selon moi. OK, retour au tutoriel. Juste en dessous du code ci-dessus, et avant la ligne 'return TRUE', ajoutez les lignes suivantes.

    II-E. La boucle

    Mis en place des points
    Sélectionnez
    // Boucle sur le plan des X
for (int x = 0; x < 45; x++)
{
    // Boucle sur le plan des Y
    for (int y = 0; y < 45; y++)
    {
        // On applique la forme d'une vague à notre grille
        points[x][y][0] = float((x / 5.0f) - 4.5f);
        points[x][y][1] = float((y / 5.0f) - 4.5f);
        points[x][y][2] = float(sin((((x / 5.0f) * 40.0f) / 360.0f) * 3.141592654 * 2.0f));
    }
}

    Je remercie Graham Gibbons de m'avoir suggéré d'utiliser une boucle sur un entier afin d'éliminer les pointes sur l'ondulation. Les deux boucles ci-dessus initialisent les coordonnées de notre grille. J'initialise les compteurs dans les boucles elles-mêmes pour garder à l'esprit que ce sont de simples variables de boucle. Nous utilisons des boucles avec des entiers pour prévenir d'étranges bogues graphiques faisant apparaître des pointes lorsqu'on utilise des flottants. Nous divisons les variables x et y par 5 (exemple : 45 / 5 = 9) et enlevons 4.5 pour chacun d'entre eux pour centrer la "vague" (on passe de l'intervalle [0, 45) à [-4.5, 4.5)). Le même effet pourrait être réalisé avec une translation, mais je préfère cette méthode. La valeur finale 'points[x][y][2]' est notre valeur sinusoïdale. La fonction 'sin()' prend en entrée des radians. Nous prenons notre valeur en degré, qui est notre 'float_x' multiplié par '40.0f'. Une fois que nous avons fait ça, pour convertir les degrés en radians : on divise par 360.0f, on multiplie par PI, ou une approximation, et on multiplie par 2.0f.

    II-F. DrawGLScene

    Je vais réécrire la fonction 'DrawGLScene' depuis le début pour nettoyer un peu le code et le remplacer par le suivant.

    La fonction DrawGLScene
    Sélectionnez
    int DrawGLScene(GLvoid)                            // Dessine notre scène OpenGL
{
    int x, y;                                    // Variables de boucle
    float float_x, float_y, float_xb, float_yb;    // Utilisé pour casser le drapeau en petits carrés

    Différentes variables sont utilisées pour contrôler les boucles. Regardez le code ci-dessous, la plupart d'entre elles ne servent pas à autre chose qu'au contrôle des boucles et à la sauvegarde de valeurs temporaires.

    Initialisation OpenGL
    Sélectionnez
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    // Efface la scène et le tampon de profondeur
glLoadIdentity();                                    // Réinitialise la matrice courante

glTranslatef(0.0f,0.0f,-12.0f);                        // Déplace de 12 unités dans l'écran

glRotatef(xrot,1.0f,0.0f,0.0f);                        // Rotation sur l'axe des X
glRotatef(yrot,0.0f,1.0f,0.0f);                        // Rotation sur l'axe des Y
glRotatef(zrot,0.0f,0.0f,1.0f);                        // Rotation sur l'axe des Z

glBindTexture(GL_TEXTURE_2D, texture[0]);            // Sélection de notre texture

    Vous avez déjà vu ce code, bien entendu. C'est le même qu'au tutoriel n° 6 sauf que j'ai un petit peu éloigné la scène de la caméra.

    Boucle externe
    Sélectionnez
    glBegin(GL_QUADS);                // Commence à dessiner les carrés
for( x = 0; x < 44; x++ )        // Boucle sur le plan sur l'axe des X (44 Points)
{
    for( y = 0; y < 44; y++ )    // Boucle sur le plan de l'axe des Y (44 Points)
    {

    II-G. Rendu

    C'est simplement le lancement de la boucle qui dessine nos polygones. J'utilise des entiers ici, pour éviter d'utiliser la fonction 'int()' comme je l'avais fait avant pour récupérer la référence du tableau qui est retournée comme un entier.

    Valeurs flottantes pour le drapeau
    Sélectionnez
    float_x = float(x)/44.0f;        // Crée un point en virgule flottante pour la valeur X
float_y = float(y)/44.0f;        // Crée un point en virgule flottante pour la valeur Y
float_xb = float(x+1)/44.0f;    // Crée un point en virgule flottante pour la valeur X + 0.0227f
float_yb = float(y+1)/44.0f;    // Crée un point en virgule flottante pour la valeur Y + 0.0227f

    Nous utilisons quatre variables pour les coordonnées de texture, ci-dessus. Chaque polygone (un carré de notre grille) est rattaché à une section de 1/44 par 1/44 de la texture. La boucle va spécifier le sommet en bas à gauche en premier et ensuite nous ajoutons les trois autres sommets (exemple : x + 1 ou y + 1).

    Fonction GL
    Sélectionnez
            glTexCoord2f( float_x, float_y);    // Première coordonnée de texture (bas gauche)
        glVertex3f( points[x][y][0], points[x][y][1], points[x][y][2] );

        glTexCoord2f( float_x, float_yb );    // Seconde coordonnée de texture (haut gauche)
        glVertex3f( points[x][y+1][0], points[x][y+1][1], points[x][y+1][2] );

        glTexCoord2f( float_xb, float_yb );    // Troisième coordonnée de texture (haut droit)
        glVertex3f( points[x+1][y+1][0], points[x+1][y+1][1], points[x+1][y+1][2] );

        glTexCoord2f( float_xb, float_y );    // Quatrième coordonnée de texture (bas droit)
        glVertex3f( points[x+1][y][0], points[x+1][y][1], points[x+1][y][2] );
    }
}
glEnd();                                    // Fin de dessin des carrés

    Les lignes ci-dessus sont simplement les appels aux fonctions OpenGL pour traiter les données dont nous venons de parler. Quatre appels différents à 'glTexCoord2f()' et 'glVertex3f()'. Remarquez que les carrés sont dessinés dans le sens horaire. Cela signifie que la face que vous voyez en premier est la face arrière. Cette face est remplie. L'autre face est dessinée en fil de fer. Si vous dessinez dans le sens antihoraire, vous verrez en premier la face avant, cela signifie que vous verrez une texture en fil de fer à la place d'une texture pleine.

    Est-ce que nous voulons ralentir le drapeau ?
    Sélectionnez
    if( wiggle_count == 2 )    // Ralentit la vague (toutes les deux images seulement)
{

    Si nous avions dessiné deux scènes, alors nous aurions voulu boucler nos valeurs sinusoïdales pour donner un effet de "mouvement".

    Gérer la mise à jour
    Sélectionnez
        for( y = 0; y < 45; y++ )        // Boucle sur les axes des Y
    {
        hold=points[0][y][2];        // Sauvegarde la valeur courante de la partie gauche de la vague
        for( x = 0; x < 44; x++)    // Boucle sur les axes des X
        {
            // Valeurs de la vague équivalent à celle sur la droite
            points[x][y][2] = points[x+1][y][2];
        }
        points[44][y][2]=hold;        // Dernière valeur qui devient la valeur de l’extrémité gauche
    }
    wiggle_count = 0;                // Remise du compteur à zéro
}
wiggle_count++;                        // Incrémentation du zéro

    Ce que nous faisons ici, c'est sauvegarder la première valeur de chaque ligne et nous déplacer sur la gauche d'un cran, pour obtenir l'effet d'ondulation. La valeur que nous avons sauvegardée est alors ajoutée à la fin pour créer un effet sans fin à travers la texture. Finalement nous remettons à zéro le compteur 'wiggle_count' pour garder un effet correct. Le code ci-dessus a été modifié par NeHe (février 2000) pour enlever un défaut sur l'ondulation de la surface de la texture. Maintenant, l'ondulation est douce.

    Gère la rotation
    Sélectionnez
    xrot+=0.3f;        // Incrémentation de la variable de rotation de l'axe des X
yrot+=0.2f;        // Incrémentation de la variable de rotation de l'axe des Y
zrot+=0.4f;        // Incrémentation de la variable de rotation de l'axe des Z

return TRUE;    // Retour

    II-H. Conclusion

    Les valeurs de rotation standards de NeHe. :) Et c'est fini. Compilez et vous devriez avoir un joli effet de "vague" texturée. Je ne sais quoi dire de plus sauf, whaouh... c'était long ! Mais j'espère que vous pourrez comprendre et en retirer quelque chose. Si vous avez des questions, ou si vous avez des points que vous voulez que j'éclaircisse ou me dire à quel point je code divinement bien, ;) alors envoyez-moi un message. C'était compliqué mais vraiment prenant. Maintenant, j'apprécie beaucoup plus les gens comme Nehe, merci à tous.

    Bosco (bosco4@home.com) Jeff Molofee (NeHe)

    III. Téléchargements

    Compte tenu du nombre de versions de codes sources pour les tutoriels NeHe, nous les laissons en anglais. En principe, si vous avez compris le code présenté dans ce tutoriel (et les tutoriels antérieurs), vous n'aurez pas de mal à les comprendre :

    • Borland C++ Builder 6 (Conversion par Christian Kindahl) ;
    • C# (Conversion par Brian Holley) ;
    • Code Warrior 5.3 (Conversion par Scott Lupton) ;
    • CygWin (Conversion par Stephan Ferraro) ;
    • Delphi (Conversion par Michal Tucek) ;
    • Dev C++ (Conversion par Dan) ;
    • Euphoria (Conversion par Evan Marshall) ;
    • Game GLUT (Conversion par Milikas Anastasios) ;
    • Irix (Conversion par Rob Fletcher) ;
    • Java (Conversion par Jeff Kirby) ;
    • Jedi-SDL (Conversion par Dominique Louis) ;
    • JOGL (Conversion par Nicholas Campbell) ;
    • LCC Win32 (Conversion par Robert Wishlaw) ;
    • Linux (Conversion par Richard Campbell) ;
    • Linux GLX (Conversion par Mihael Vrbanec) ;
    • Linux SDL (Conversion par Ti Leggett) ;
    • LWJGL (Conversion par Mark Bernard) ;
    • Mac OS (Conversion par Anthony Parker) ;
    • Mac OS X/Cocoa (Conversion par Bryan Blackburn) ;
    • MASM (Conversion par Nico (Scalp)) ;
    • VC++/OpenIL (Conversion par Denton Woods) ;
    • Pelles C (Conversion par Pelle Orinius) ;
    • Power Basic (Conversion par Angus Law) ;
    • Visual Basic (Conversion par Ross Dawson) ;
    • Visual Studio ;
    • Visual Studio NET (Conversion par Grant James).


Liste d'affichage

Liste d'affichage

  • I. Contributions
  • II. Tutoriel
    • II-A. Introduction
    • II-B. Inclusion
    • II-C. Les variables
    • II-D. Deux tableaux
    • II-E. La fonction BuildLists
    • II-F. Le dessin du cube
    • II-G. La deuxième liste
    • II-H. Charger les textures
    • II-I. L'affichage
    • II-J. WinMain
  • III. Téléchargements
  • IV. Remerciements
  • V. Liens

Dans ce tutoriel, je présente l'utilisation des listes d'affichage. Les listes d'affichage n'améliorent pas uniquement la vitesse de rendu du programme, elles réduisent également le nombre de lignes nécessaires à la création de scènes GL.

I. Contributions

Nous recherchons toujours des traducteurs pour finir ce projet de traduction des articles de NeHe. Votre aide serait appréciée ! Vous êtes intéressé ? Alors contactez-nous à l'adresse suivante : nehe@redaction-developpez.com .

II. Tutoriel

II-A. Introduction

Soit l'exemple suivant : nous sommes en train de programmer un jeu qui utilise et manipule des astéroïdes. Chaque niveau du jeu commence avec deux astéroïdes minimum affichés à l'écran. Vous vous asseyez donc avec votre crayon et votre feuille, et vous cherchez comment faire un astéroïde en 3D. Une fois que vous avez entièrement fini, vous construisez votre astéroïde avec OpenGL en utilisant des polygones et des rectangles. Imaginons que cet astéroïde est octogonal (huit côtés). Normalement, vous devriez créer une boucle et dessiner l'astéroïde dans la boucle. Vous devriez y parvenir avec environ 18 (ou plus) lignes de code. Devoir créer l'astéroïde à chaque fois qu'il faut l'afficher à l'écran est lourd pour votre machine. Vous constaterez que c'est d'autant plus vrai que vous manipulez des objets complexes.

La solution se trouve être l'utilisation des listes d'affichage. Avec une liste d'affichage, l'objet est créé une seule et unique fois. Vous pouvez alors lui donner une texture, de la couleur, ce que vous voulez. Donnez un nom à votre liste d'affichage. Comme il s'agit d'un astéroïde, en conséquence nous appellerons cette liste d'affichage 'asteroid'. Ainsi chaque fois que je voudrai dessiner l'astéroïde coloré ou texturé à l'écran, la seule chose à faire sera d'appeler glCallList(asteroid). L'astéroïde précédemment créé apparaîtra directement à l'écran. Puisque l'astéroïde est déjà préexistant dans la liste d'affichage, il sera inutile de le construire dans le programme OpenGL. Il est déjà présent dans la mémoire. Cela permet d'économiser du temps de calcul et ainsi de gagner en rapidité d'exécution.

Alors êtes-vous prêt à apprendre ? :) Nous allons appeler ceci la démo "Liste d'affichage Q-Bert". Nous obtiendrons un écran de type Q-Bert représentant 15 cubes. Chaque cube est en réalité composé d'une boîte et d'un couvercle. Le haut sera placé dans une liste d'affichage à part, ainsi nous pourrons lui ajouter une texture différente (plus sombre). Le cube sera en fait une boîte sans couvercle et vide. (simulé par un couvercle sombre).

II-B. Inclusion

Ce code est basé sur la leçon 6. Une grande partie du code de la leçon 6 est réutilisée, ainsi il sera plus facile d'étudier les quelques modifications. Les lignes ci-dessous sont standards et utilisées dans à peu près toutes les leçons.

En-têtes et déclarations
Sélectionnez
#include    <windows.h>                     // En-tête pour Windows
#include    <stdio.h>                       // En-tête pour les entrées/sorties standards (NOUVEAU)
#include    <gl\gl.h>                       // En-tête pour la bibliothèque OpenGL32
#include    <gl\glu.h>                      // En-tête pour la bibliothèque GLu32
#include    <gl\glaux.h>                    // En-tête pour la bibliothèque GLaux
HDC         hDC=NULL;                       // Contexte privé du système GDI
HGLRC       hRC=NULL;                       // Contexte de rendu permanent
HWND        hWnd=NULL;                      // Contient notre descripteur de fenêtre
HINSTANCE   hInstance;                      // Sauve l'instance de l'application
bool        keys[256];                      // Tableau utilisé pour les routines du clavier
bool        active=TRUE;                    // Drapeau d'activation de la fenêtre
bool        fullscreen=TRUE;                // Drapeau pour le mode plein écran (Vrai par défaut)

II-C. Les variables

À présent nous initialisons nos variables. La première est une variable de stockage pour une texture. Ensuite viennent deux variables pour les deux listes d'affichage. Ces variables sont utilisées comme pointeurs sur la mémoire de stockage des listes d'affichage. Elles sont appelées box et top.

Deux variables, nommées xloop et yloop, sont utilisées pour le positionnement des cubes à l'écran et deux autres nommées xrot et yrot sont utilisées pour la rotation des cubes suivant les axes X et Y.

Déclaration des variables pour les cubes
Sélectionnez
GLuint texture[1];          // Pour stocker une texture
GLuint box;                 // Pour stocker une liste d'affichage
GLuint top;                 // Pour stocker une seconde liste d'affichage
GLuint xloop;               // Boucle sur l'axe des X
GLuint yloop;               // Boucle sur l'axe des Y
GLfloat xrot;               // Rotation des cubes sur l'axe des X
GLfloat yrot;               // Rotation des cubes sur l'axe des Y

II-D. Deux tableaux

Ensuite, ce sont deux tableaux qui sont initialisés, qui contiennent les codes pour les couleurs. Le premier, 'boxcol' stockera les valeurs du taux de brillance rouge, orange, jaune, vert et bleu. Chaque valeur incluse dans les accolades '{}' représente les valeurs des composantes rouge, vert et bleu. Chaque groupe d'accolades correspond à une couleur spécifique.

Le deuxième tableau de couleurs que nous allons créer est pour le rouge foncé, orange foncé, jaune foncé, vert foncé, et bleu foncé. Ces couleurs seront utilisées pour dessiner le haut des boîtes. Nous voulons que le couvercle soit plus sombre que le reste de la boîte.

Déclaration des couleurs des cubes
Sélectionnez
static GLfloat boxcol[5][3]= // Tableau pour la couleur des boîtes
{
    // Brillant : rouge, orange, jaune, vert, bleu
    {1.0f,0.0f,0.0f},
    {1.0f,0.5f,0.0f},
    {1.0f,1.0f,0.0f},
    {0.0f,1.0f,0.0f},
    {0.0f,1.0f,1.0f}
};

static GLfloat topcol[5][3]= // Tableau pour la couleur des faces supérieures
{
    // Sombre : rouge, orange, jaune, vert, bleu
    {.5f,0.0f,0.0f},
    {0.5f,0.25f,0.0f},
    {0.5f,0.5f,0.0f},
    {0.0f,0.5f,0.0f},
    {0.0f,0.5f,0.5f}
};

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Déclaration de WndProc

II-E. La fonction BuildLists

Maintenant nous allons construire la vraie liste d'affichage. Vous avez sans doute remarqué que tout le code pour construire la boîte est dans la première liste, et que tout le code pour construire le haut de la boîte est dans l'autre liste. Je vais essayer de détailler les explications pour cette partie.

Sélectionnez
GLvoid BuildLists() // Construction des listes d'affichage pour les boîtes
{

Nous commençons par dire à OpenGL que nous avons besoin de deux listes. glGenLists(2) va créer l'espace nécessaire pour ces deux listes et retourner un pointeur sur la première liste. 'box' va mémoriser l'endroit de cette première liste. Dès que nous appellerons box, la première liste d'affichage sera dessinée.

Création des listes
Sélectionnez
    box=glGenLists(2); // Construction des deux listes

Ici nous allons construire notre première liste. Nous avons déjà alloué la mémoire pour deux listes, et nous savons que box pointe sur la mémoire prévue pour la première liste. À présent, il faut simplement donner à OpenGL la destination et le type de liste voulu.

Pour cela, nous utilisons la procédure glNewList(). Remarquez que box est le premier paramètre de cette procédure. Ce paramètre indique à OpenGL l'emplacement mémoire pour le stockage de cette première liste. Le deuxième paramètre, GL_COMPILE, indique à OpenGL que nous voulons que la liste soit préexistante dans la mémoire à l'exécution du programme. Ainsi, OpenGL n'a pas besoin d'utiliser la procédure de fabrication de l'objet à chaque fois qu'on souhaite l'afficher.

L'utilisation de GL_COMPILE est similaire à ce qui se passe lorsque l'on utilise un compilateur pour faire un programme. Si vous n'utilisez pas GL_COMPILE, cela est équivalent à écrire un programme et à devoir le compiler avant chaque utilisation. Si le programme compilé est déjà préexistant dans un fichier .EXE, vous avez simplement à cliquer sur cet exécutable pour utiliser le programme. Pas de compilation nécessaire. Une fois qu'OpenGL a compilé les listes d'initialisation, elles sont prêtes à être utilisées, plus besoin de compilation. Ainsi, les listes d'affichage permettent de gagner en vitesse, en gardant en mémoire les objets du jeu sans devoir les recréer à chaque fois.

Création de la liste d'affichage box
Sélectionnez
    glNewList(box,GL_COMPILE);
    // Nouvelle liste d'affichage box

La partie suivante du programme va dessiner la boîte sans son couvercle. Elle n'apparaîtra pas à l'écran. Elle sera stockée dans une liste d'affichage.

Vous pouvez insérer n'importe quelle instruction entre glNewList() et glEndList(). Vous pouvez fixer la couleur, changer les textures, etc. La seule chose que vous ne pouvez pas faire, c'est écrire du code qui modifierait la liste d'affichage à la volée. Une fois que la liste d'affichage est construite, vous ne pouvez plus la modifier.

Vous pourriez penser par exemple que, en ajoutant la ligne glColor3ub(rand()%255,rand()%255,rand()%255) dans le code suivant, vous obtiendriez une nouvelle couleur à chaque lancement du programme. Mais, puisque la liste est créée une fois pour toutes, la couleur ne changera pas à chaque fois que vous le redessinerez à l'écran. La couleur aléatoire utilisée dans la première création de l'objet sera non-modifiable par la suite.

Si vous voulez changer la couleur de la liste d'affichage, vous devez le faire avant de dessiner votre liste d'affichage. J'en parlerai plus tard de manière approfondie.

II-F. Le dessin du cube

dessin du cube
Sélectionnez
    glBegin(GL_QUADS); // Début du dessin du cube

    // Face inférieure
    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);     // En haut à droite de la texture et du quadrilatère
    glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);     // En haut à gauche de la texture et du quadrilatère
    glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);     // En bas à gauche de la texture et du quadrilatère
    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);     // En bas à droite de la texture et du quadrilatère

    // Face avant
    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);     // En bas à gauche de la texture et du quadrilatère
    glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);     // En bas à droite de la texture et du quadrilatère
    glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);     // En haut à droite de la texture et du quadrilatère
    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);     // En haut à gauche de la texture et du quadrilatère

    // Face arrière
    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);     // En bas à droite de la texture et du quadrilatère
    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);     // En haut à droite de la texture et du quadrilatère
    glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);     // En haut à gauche de la texture et du quadrilatère
    glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);     // En bas à gauche de la texture et du quadrilatère

    // Face de droite
    glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);     // En bas à droite de la texture et du quadrilatère
    glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);     // En haut à droite de la texture et du quadrilatère
    glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);     // En haut à gauche de la texture et du quadrilatère
    glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);     // En bas à gauche de la texture et du quadrilatère

    // Face de gauche
    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);     // En bas à gauche de la texture et du quadrilatère
    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);     // En bas à droite de la texture et du quadrilatère
    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);     // En haut à droite de la texture et du quadrilatère
    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);     // En haut à gauche de la texture et du quadrilatère

    glEnd(); // Fin du dessin du cube

La commande glEndList() signifie pour OpenGL que la création de la liste d'affichage est terminée. Tout ce qui se trouve entre glNewList() et glEndList() sera contenu dans la liste d'affichage, tout ce qui se trouve avant glNewList() ou après glEndList() ne sera pas pris en compte dans la liste d'affichage courante.

Appel de glEndList
Sélectionnez
    glEndList(); // Fin de la construction de la liste pour le cube

II-G. La deuxième liste

À présent, nous allons créer notre deuxième liste d'affichage. L'emplacement de notre seconde liste est à l'adresse suivant celle pointée par box. On utilise la valeur de l'emplacement de la première liste d'affichage (box) et lui ajoutons une unité. Le code suivant permet d'utiliser la variable top comme conteneur pour la seconde liste d'affichage.

Mise à jour de top
Sélectionnez
    top=box+1; // La liste d'affichage de top est la liste d'affichage de cube + 1

Maintenant que nous avons un emplacement pour stocker notre seconde liste d'affichage, nous pouvons la construire. Nous faisons ceci de la même manière que précédemment, mais cette fois l'emplacement mémoire sera indiqué par la variable 'top' au lieu de la variable 'box'.

Nouvelle liste
Sélectionnez
    glNewList(top,GL_COMPILE); // Nouvelle liste d'affichage compilée top

La portion de code suivante dessine le couvercle de la boîte. C'est un carré contenu dans le plan des Z (plan xOy).

Dessin du haut du cube
Sélectionnez
    glBegin(GL_QUADS); // Début du dessin de rectangle

    // Face supérieure
    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);     // En haut à gauche de la texture et du quadrilatère
    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f);     // En bas à gauche de la texture et du quadrilatère
    glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f);     // En bas à droite de la texture et du quadrilatère
    glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);     // En haut à droite de la texture et du quadrilatère

    glEnd(); // Fin du dessin du rectangle

À nouveau, la fin de la construction pour cette liste d'affichage est faite avec l'instruction glEndList(). C'est aussi simple que ça. Nous avons réussi à créer deux listes d'affichage.

Fin de la liste
Sélectionnez
    glEndList(); // fin de la construction de la liste d'affichage top
}

II-H. Charger les textures

Pour charger et initialiser les textures, reprenons le code des tutoriels précédents. Nous voulons une texture couvrant les six faces de chaque cube. J'ai décidé d'utiliser le MIP mapping pour avoir un meilleur aspect lisse des textures. Je n'aime pas voir les pixels. La texture utilisée est 'cube.bmp', stockée dans 'data'. Reprenez la fonction LoadBMP et changez la ligne qui ouvre le fichier pour qu'elle devienne :

Chargement de la texture
Sélectionnez
if (TextureImage[0]=LoadBMP("Data/Cube.bmp")) // Charge la texture

Le code de redimensionnement est exactement le même que la leçon 6.

Le code d'initialisation a seulement quelques changements. J'ai ajouté la ligne BuildList(). Cela permet d'appeler la section de code qui construit la liste d'affichage. Remarquez que l'appel à BuildList() est placé après celui à LoadGLTextures(). Il est important de connaître l'ordre dans lequel ces opérations doivent être placées. Tout d'abord, nous construisons les textures, ce qui permet lors de la création des listes d'affichage de pouvoir les recouvrir des textures ainsi créées.

La fonction InitGL
Sélectionnez
int InitGL(GLvoid)              // Toute l'initialisation OpenGL est faite ici
{
    if (!LoadGLTextures())      // Appelle la procédure de chargement des textures.
    {
        return FALSE;           // Si les textures ne s'affichent pas, retourner la valeur FALSE.
    }
    BuildLists();               // Appelle la procédure de création de la liste d'affichage
    glEnable(GL_TEXTURE_2D);    // Active le 'mapping' de texture
    glShadeModel(GL_SMOOTH);    // Active le 'Smooth Shading'
    glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // Place un fond de scène noir
    glClearDepth(1.0f);         // Règle la profondeur des buffers
    glEnable(GL_DEPTH_TEST);    // Active le test de profondeur
    glDepthFunc(GL_LEQUAL);     // Sélectionne le type de test de profondeur

Les trois lignes de code suivantes activent un éclairage rapide et sale. Light0 est une valeur prédéfinie sur la plupart des cartes vidéo, ce qui nous évite la difficulté d'initialiser l'éclairage. Après avoir sélectionné light0, nous activons l'éclairage. Si light0 ne fonctionne pas sur votre carte vidéo (c'est-à-dire que vous voyez un écran noir), désactivez simplement l'éclairage.

La dernière ligne GL_COLOR_MATERIAL nous permet d'ajouter des couleurs pour les textures des objets. Si nous n'activons pas le coloriage matériel, les textures garderont toujours leurs couleurs d'origine. glColor3f(r,g,b) n'aura pas d'effet sur la coloration. C'est donc important de l'activer.

Les glEnable
Sélectionnez
    glEnable(GL_LIGHT0);            // Éclairage vite fait, mal fait (cela suppose que Light0 est actif)
    glEnable(GL_LIGHTING);          // Activation de l'affichage
    glEnable(GL_COLOR_MATERIAL);    // Activation du coloriage matériel

Finalement nous activons la correction de perspective pour avoir un rendu sympathique, et nous retournons la valeur TRUE pour faire savoir au programme que l'initialisation s'est bien passée.

La perspective
Sélectionnez

    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Activation de la correction de perspective
    return TRUE;    // L'initialisation a réussi
}

II-I. L'affichage

À présent, c'est le code de tracé qui est abordé. Comme d'habitude, je vais utiliser quelques mathématiques ardues. Pas de sinus et cosinus, mais cela reste un peu étrange. Nous débutons, comme d'habitude, en effaçant l'écran et en vidant le buffer de profondeur.

Ensuite, nous lions une texture au cube. Je pourrais ajouter cette ligne dans la création de la liste d'affichage, mais la laisser à l'extérieur me permet de la modifier à n'importe quel moment. Si j'avais ajouté la ligne glBindTexture(GL_TEXTURE_2D,texture[0]) à l'intérieur de la liste d'affichage, l'affichage aurait été non modifiable, et aurait utilisé la couleur que j'avais choisie lors de la création de l'objet.

Sélectionnez
int DrawGLScene(GLvoid) // C'est ici que tout le dessin est fait
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Efface l'écran et le buffer de profondeur
    glBindTexture(GL_TEXTURE_2D, texture[0]);           // Sélectionne la texture

Voici maintenant les choses intéressantes. Une boucle aura pour variable itérative yloop. Cette boucle est utilisée pour positionner les cubes sur l'axe des Y (haut et bas). Nous souhaitons avoir cinq lignes de cubes, de haut en bas, donc nous allons faire une boucle pour laquelle yloop va de un jusqu'à cinq.

Boucle externe
Sélectionnez
    for (yloop=1;yloop<6;yloop++) // Boucle au travers du plan y
    {

Nous avons une autre boucle appelée xloop. Elle est utilisée dans le positionnement des cubes sur l'axe des X (de gauche à droite). Le nombre de cubes dessinés de gauche à droite dépend de la ligne où nous nous trouvons. Si nous sommes en haut d'une ligne, xloop va aller seulement de 0 à 1 (pour dessiner un seul cube). La ligne d'après ira de 0 à 2 (pour dessiner deux cubes), etc.

Boucle interne
Sélectionnez
        for (xloop=0;xloop<yloop;xloop++) // Boucle de parcours dans le plan X
        {

Nous réinitialisons la vue avec glLoadIdentity().

Réinitialisation
Sélectionnez
            glLoadIdentity(); // Réinitialise la vue

La ligne suivante effectue une translation à un emplacement spécifique de l'écran. Cela peut sembler étrange, mais ça ne l'est pas vraiment. Sur l'axe des X, il se passe la chose suivante.

Nous nous déplaçons vers la droite de 1,4 unité, afin de placer la pyramide au centre de l'écran. Nous multiplions alors l'indice de boucle xloop par 2.8 et nous lui ajoutons 1.4. (Nous le multiplions par 2.8 afin que les cubes ne soient pas aux sommets les uns des autres. (2,8 est approximativement la largeur d'un cube lorsqu'il a subi une rotation de 45 degrés.) Finalement nous soustrayons yloop*1.4. Cela déplace les cubes à gauche, en fonction de la ligne que nous traitons. Si nous ne le bougions pas vers la gauche, la pyramide s'appuierait sur le côté gauche (ce ne serait pas vraiment la pyramide que nous souhaitons avoir).

Suivant l'axe des Y nous soustrayons yloop à 6.0f, autrement la pyramide serait construite à l'envers. Le résultat est ensuite multiplié par 2.4. Autrement les cubes se chevaucheraient les uns les autres sur l'axe des Y (2.4 est approximativement la hauteur de chaque cube). Puis nous soustrayons 7. Ainsi, la pyramide débute au pied de l'écran et monte au fur et à mesure.

Finalement, suivant l'axe Z, nous déplaçons la scène de vingt unités. De cette manière, la pyramide remplit exactement l'écran.

Translation
Sélectionnez
            // Positionnement des cubes à l'écran
            glTranslatef(1.4f+(float(xloop)*2.8f)-(float(yloop)*1.4f),((6.0f-float(yloop))*2.4f)-7.0f,-20.0f);

À présent nous réalisons la rotation suivant l'axe des X. Nous inclinons le cube vers le bas de 45 degrés moins 2*yloop. Le mode perspective incline le cube automatiquement, donc je fais une soustraction pour compenser l'inclinaison. Ce n'est pas le meilleur moyen, mais ça marche.

Finalement, nous ajoutons xrot. Cela nous donne un contrôle du clavier sur l'angle (c'est drôle de s'amuser avec ça).

Après avoir effectué la rotation suivant l'axe des X, nous effectuons une rotation de 45 degrés sur l'axe des Y, et nous ajoutons la valeur de yrot pour avoir un contrôle clavier sur l'axe des Y.

Rotations
Sélectionnez
            glRotatef(45.0f-(2.0f*yloop)+xrot,1.0f,0.0f,0.0f); // Incline le cube de haut en bas
            glRotatef(45.0f+yrot,0.0f,1.0f,0.0f);              // Tourne le cube de gauche à droite

Maintenant nous sélectionnons une couleur (brillante) avant de dessiner la partie 'boîte ' du cube. Remarquez que nous utilisons la procédure glColor3fv(). L'effet produit est le chargement de la couleur à partir des valeurs (rouge, vert et bleu) contenues dans les accolades. 3fv représente trois valeurs flottantes, v est un pointeur sur un tableau. Nous sélectionnons la couleur avec un indice de tableau qui vaut yloop-1, ce qui attribue une couleur différente à chaque ligne de cube. Si nous avions utilisé xloop-1 nous aurions une couleur différente pour chaque colonne.

Couleur
Sélectionnez
            glColor3fv(boxcol[yloop-1]); // Sélectionne la couleur d'une boîte

Maintenant que la couleur est initialisée, il nous reste à dessiner notre boîte. Au lieu d'écrire le code de dessin d'une boîte, nous appelons simplement notre liste d'affichage. Nous faisons cela avec la commande glCallList(box). box signifie que nous choisissons d'appeler la liste d'affichage box, qui est le cube sans son sommet.

La boîte sera dessinée en utilisant la couleur que nous avons sélectionnée avec glcolor3fv(), à la position à laquelle nous nous sommes déplacés.

Le rendu
Sélectionnez
            glCallList(box); // Dessine la boîte

Maintenant nous sélectionnons une couleur pour le sommet de la boîte (plus sombre) avant de le dessiner. Si vous vouliez réaliser Q-Bert (le jeu) il faudrait changer cette couleur après passage du personnage. (Le but du jeu est de passer sur toutes les boîtes).

La couleur
Sélectionnez

            glColor3fv(topcol[yloop-1]); // Sélectionne la couleur du sommet

Finalement, la seule chose qui nous reste à faire est le dessin de la liste d'affichage. Ceci va ajouter une couleur sombre. C'est très facile.

Le haut
Sélectionnez
            glCallList(top); // Dessine le haut
        }
    }
    return TRUE; // Retour
}

II-J. WinMain

Les modifications restantes ont toutes été effectuées dans WinMain(). Le code a été ajouté juste après la ligne SwapBuffer(hDC). Il vérifie si les touches gauche, droite, haut ou bas ont été pressées et fait tourner les cubes en conséquence.

Gestion du clavier
Sélectionnez
SwapBuffers(hDC); // Échange les tampons (Double Buffering)

if (keys[VK_LEFT]) // Est-ce que la touche 'flèche gauche' est pressée ?
{
    yrot-=0.2f; // Incline le cube vers la gauche
}
if (keys[VK_RIGHT]) // Est-ce que la touche 'flèche droite' est pressée ?
{
    yrot+=0.2f; // Incline le cube vers la droite
}
if (keys[VK_UP]) // Est-ce que la touche 'flèche haut' est pressée ?
{
    xrot-=0.2f; // Incline le cube vers le haut
}
if (keys[VK_DOWN]) // Est-ce que la touche 'flèche bas' est pressée ?
{
    xrot+=0.2f; // Incline le cube vers le bas
}

Comme pour tous les précédents tutoriels, on s'assure que le titre de la fenêtre est correct.

Gestion de F1
Sélectionnez
        if (keys[VK_F1])                    // Est-ce que la touche 'F1' est pressée ?
        {
            keys[VK_F1]=FALSE;                // Si oui, on remet à FALSE
            KillGLWindow();                    // On détruit notre fenêtre courante
            fullscreen=!fullscreen;            // On inverse le mode Plein écran/Mode fenêtre
                                                    // On recrée notre fenêtre OpenGL
            if (!CreateGLWindow("Tutoriel de NeHe sur les listes d'affichage",640,480,16,fullscreen))
            {
                return 0;                    // On quitte si la fenêtre ne s'est pas créée
            }
        }
    }
}
-->

À la fin de ce tutoriel, vous devriez avoir acquis le fonctionnement des listes d'affichage, comment les créer et les utiliser. Les listes d'affichage sont vraiment utiles. Pas seulement pour simplifier le codage d'objets complexes, elles vous permettent également de gagner en vitesse d'exécution nécessaire pour conserver un nombre de FPS élevé.

J'espère que vous avez apprécié ce tutoriel. Si vous avez des questions ou si quelque chose n'est pas clair, contactez-moi par mail.

Jeff Molofee ( NeHe )

III. Téléchargements

Compte tenu du nombre de versions de codes sources pour les tutoriels NeHe, nous les laissons en anglais. En principe, si vous avez compris le code présenté dans ce tutoriel (et les tutoriels antérieurs), vous n'aurez pas de mal à les comprendre :

  • Borland C++ Builder 6 (Conversion par Christian Kindahl) ;
  • C# (Conversion par Brian Holley) ;
  • Code Warrior 5.3 (Conversion par Scott Lupton) ;
  • CygWin (Conversion par Stephan Ferraro) ;
  • Delphi (Conversion par Michal Tucek) ;
  • Dev C++ (Conversion par Dan) ;
  • Euphoria (Conversion par Evan Marshall) ;
  • Game GLUT (Conversion par Milikas Anastasios) ;
  • Irix (Conversion par Rob Fletcher) ;
  • Java (Conversion par Jeff Kirby) ;
  • Jedi-SDL (Conversion par Dominique Louis)
  • JOGL (Conversion par Nicholas Campbell) ;
  • LCC Win32 (Conversion par Robert Wishlaw) ;
  • Linux (Conversion par Richard Campbell) ;
  • Linux GLX (Conversion par Mihael Vrbanec) ;
  • Linux SDL (Conversion par Ti Leggett) ;
  • LWJGL (Conversion par Mark Bernard) ;
  • Mac OS (Conversion par Anthony Parker) ;
  • Mac OS X/Cocoa (Conversion par Bryan Blackburn) ;
  • MASM (Conversion par Nico (Scalp)) ;
  • VC++/OpenIL (Conversion par Denton Woods) ;
  • Pelles C (Conversion par Pelle Orinius) ;
  • Visual Basic (Conversion par Ross Dawson) ;
  • Visual Studio ;
  • Visual Studio NET (Conversion par Grant James).
<_img src="./_img/lightbox-btn-close.png">


Les polices Bitmap

  • Les polices Bitmap

    • I. Contributions
    • II. Tutoriel
    • III. Téléchargements
    • IV. Remerciements
    • V. Liens

    Je pense que la question que l'on m'a le plus souvent posée dans les courriels est : « Comment puis-je afficher du texte à l'écran en utilisant OpenGL ? ». Vous pouvez toujours appliquer une texture avec du texte à l'écran. Bien sûr vous avez peu de contrôle sur le texte et, sauf si vous êtes bon avec le fondu, le texte va souvent se fondre avec les images sur l'écran. Si vous voulez une méthode simple pour écrire du texte n'importe où à l'écran avec n'importe quelle couleur, en utilisant n'importe laquelle des polices sur votre ordinateur, alors ce tutoriel est pour vous. Les polices Bitmap sont des polices 2D redimensionnables qui ne peuvent pas être tournées. Elles sont toujours vues de face.

    II. Tutoriel

    Bienvenue dans un nouveau tutoriel. Cette fois nous allons vous apprendre à utiliser les polices Bitmap.
    Vous devez vous dire à vous-même « En quoi c'est si dur de mettre du texte à l'écran ? ». Si vous n'avez jamais essayé, ce n'est pas si facile !

    Bien sûr, vous pouvez lancer un programme de dessin, écrire du texte dans une image, charger l'image dans votre programme OpenGL, activer le fondu et appliquer le texte à l'écran. Mais ceci est une perte de temps, le résultat final ressort souvent flou ou pixelisé dépendant du type de filtrage que vous utilisez, et bien que votre image ait une composante alpha, votre texte va finir transparent (fondu avec les objets à l'écran) une fois appliqué à l'écran.

    Si vous avez déjà utilisé le bloc-notes, Microsoft Word ou d'autres logiciels de traitement de texte, vous devez avoir remarqué les différents types de police disponibles. Ce tutoriel va vous apprendre à utiliser exactement la même police dans vos programmes OpenGL. Bien sûr… toute police installée sur votre ordinateur peut être utilisée dans vos démos.

    Les polices Bitmap sont cent fois mieux que les polices graphiques (textures). Vous pouvez changer le texte à la volée. Pas besoin de faire des textures pour chaque mot ou lettre que vous voulez écrire à l'écran. Vous avez juste à positionner le texte et utiliser ma commande OpenGL pour afficher le texte à l'écran.

    J'ai essayé de faire la fonction aussi simple que possible. Tout ce que vous avez à faire est écrire glPrint("Hello"). C'est aussi simple que ça. Peu importe. Vous pouvez dire en voyant cette introduction que je suis extrêmement content de ce tutoriel. Cela m'a pris environ une heure et demie pour créer le programme. Pourquoi autant de temps ? Parce qu'il n'y a aucune information disponible sur l'utilisation des polices Bitmap, sauf si, bien sûr, vous aimez le code MFC. Dans l'esprit de garder le code simple j'ai décidé qu'il serait sympa si je l'écrivais simplement pour comprendre le code C :)

    Une petite chose, ce code est spécifique à Windows. Il utilise des fonctions wgl de Windows pour construire la police. Apparemment Apple a un support agl qui devrait faire la même chose et X a glx. Malheureusement, je ne peux garantir ce code portable. Si quelqu'un a du code indépendant de la plateforme pour dessiner des polices à l'écran, envoyez-le et j'écrirai un autre tutoriel sur les polices.

    Nous allons commencer avec le code typique de la leçon 1. Nous allons ajouter le fichier d'entête stdio.h pour les opérations d'entrée/sortie standard. Le fichier d'entête stdarg.h pour analyser le texte et convertir les variables en texte, et finalement le fichier d'entête math.h pour déplacer le texte sur l'écran en utilisant SIN et COS.

    Sélectionnez
    #include <windows.h>                                            // Fichier d'entête pour Windows
#include <math.h>                                               // Fichier d'entête pour la bibliothèque mathématique           (AJOUT)
#include <stdio.h>                                              // Fichier d'entête pour les entrées/sorties    (AJOUT)
#include <stdarg.h>                                             // Fichier d'entête pour les fonctions à arguments variables    (AJOUT)
#include <gl\gl.h>                                              // Fichier d'entête pour la bibliothèque OpenGL32
#include <gl\glu.h>                                             // Fichier d'entête pour la bibliothèque GLu32
#include <gl\glaux.h>                                           // Fichier d'entête pour la bibliothèque GLaux

HDC             hDC=NULL;                                       // Contexte de périphérique privé GDI
HGLRC           hRC=NULL;                                       // Contexte de rendu permanent
HWND            hWnd=NULL;                                      // Contient notre ID de la fenêtre
HINSTANCE       hInstance;                                      // Contient l'instance de l'application

    Nous allons aussi ajouter trois nouvelles variables. base va contenir le numéro de la première liste d'affichage que nous allons créer. Chaque caractère requiert sa propre liste d'affichage. Le caractère 'A' est 65 dans la liste d'affichage, 'B' est 66, 'C' est 67, etc. Donc 'A' va être enregistré dans la liste d'affichage base+65.

    Ensuite nous créons deux compteurs (cnt1 et cnt2). Ces compteurs vont compter à différentes fréquences et sont utilisés pour déplacer le texte sur l'écran en utilisant SIN et COS. Cela crée un mouvement semi-aléatoire sur l'écran. Nous allons aussi contrôler la couleur des lettres avec les compteurs.

    Sélectionnez
    GLuint  base;                                                   // Base des listes d'affichage pour le jeu de polices
GLfloat cnt1;                                                   // 1er compteur utilisé pour déplacer le texte et pour la coloration
GLfloat cnt2;                                                   // 2nd compteur utilisé pour déplacer le texte et pour la coloration

bool    keys[256];                                              // Tableau utilisé pour la fonction du clavier
bool    active=TRUE;                                            // Drapeau de fenêtre active, défini à TRUE par défaut
bool    fullscreen=TRUE;                                        // Drapeau de plein écran, défini à TRUE par défaut

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);           // Prototype de WndProc

    La partie suivante construit la police actuelle. Ceci était la partie la plus difficile à écrire. 'HFONT font' indique à Windows que nous allons manipuler une police Windows. oldfont est utilisé pour des questions de propreté.

    Ensuite nous définissons base. Nous faisons ceci en créant un groupe de 96 listes d'affichage avec glGenLists(96). Suite à la création des listes d'affichage, la variable base contiendra le numéro de la première liste.

    Sélectionnez
    GLvoid BuildFont(GLvoid)                                        // Construit notre police Bitmap
{
        HFONT   font;                                           // ID de la police windows
        HFONT   oldfont;                                        // Utilisé pour des questions de propreté

        base = glGenLists(96);                                  // Espace de stockage pour 96 caractères (AJOUT)

    Maintenant la partie amusante. Nous allons créer notre police. Nous commençons par spécifier la taille de la police. Vous pouvez remarquer que c'est un nombre négatif. En ajoutant un moins, nous disons à Windows de nous trouver une police basée sur la taille des caractères. Si nous utilisions un nombre positif nous aurions une police basée sur la cellule.

    Sélectionnez
            font = CreateFont(      -24,                            // Taille de la police (AJOUT)

    Ensuite nous spécifions la largeur de la cellule. Vous pouvez remarquer que nous l'avons définie à 0. En mettant 0, Windows va utiliser la valeur par défaut. Vous pouvez jouer avec cette valeur si vous voulez, faire la police large, etc.

    Sélectionnez
                                    0,                              // Largeur de la police

    L'angle d'échappement va tourner la police. Malheureusement ceci n'est pas une fonctionnalité très utile. Bien que vous puissiez tourner de 0, 90, 180 et de 270 degrés, la police va être rognée pour rentrer dans les bordures d'un rectangle invisible. L'angle d'orientation, d'après l'aide de MSDN spécifie l'angle, en dixièmes de degré, entre chaque ligne de base du caractère et l'axe des X du périphérique. Malheureusement, je n'ai aucune idée de ce que cela signifie :(

    Sélectionnez
                                    0,                              // Angle d'échappement
                                0,                              // Angle d'orientation

    Le poids de la police est un super paramètre. Vous pouvez entrer un nombre entre 0 et 1000 ou vous pouvez utiliser une des valeurs prédéfinies. FW_DONTCARE est 0, FW_NORMAL est 400, FW_BOLD est 700 et FW_BLACK est 800. Il y a beaucoup plus de valeurs prédéfinies, mais ces quatre-là sont un bon exemple. Plus la valeur est élevée, plus la police est épaisse (plus gras).

    Sélectionnez
                                    FW_BOLD,                        // Poids de la police

    Italique, souligné et barré peuvent être soit TRUE soit FALSE. Simplement, si souligné est TRUE, la police va être soulignée. Si c'est FALSE, elle ne le sera pas. :)

    Sélectionnez
                                    FALSE,                          // Italique
                                FALSE,                          // Souligné
                                FALSE,                          // Barré

    L'identifieur du jeu de caractères décrit le type du jeu de caractères que vous souhaitez utiliser. Il y en a de trop pour tous les expliquer. CHINESEBIG5_CHARSET, GREEK_CHARSET, RUSSIAN_CHARSET, DEFAULT_CHARSET, etc. ANSI est celui que j'utilise, bien que le DEFAULT marcherait sans doute tout aussi bien.

    Si vous êtes intéressé dans l'utilisation des polices comme Webdings ou Wingdings, vous devez utiliser SYMBOL_CHARSET à la place de AINSI_CHARSET.

    Sélectionnez
                                    ANSI_CHARSET,                   // Identifiant du jeu de caractères

    La précision du rendu est très importante. Cela indique à Windows le type du jeu de caractères à utiliser s'il y en a plus d'un de disponible. OUT_TT_PRECIS indique à Windows que s'il y a plus d'un type de police, de choisir celui avec le même nom, en sélectionnant la version TRUETYPE de la police. Les polices Truetype rendent toujours mieux, notamment lorsque vous les grossissez. Vous pouvez toujours utiliser OUT_TT_ONLY_PRECIS, qui tente TOUJOURS d'utiliser une police TRUETYPE.

    Sélectionnez
                                    OUT_TT_PRECIS,                  // Précision du rendu

    La précision de la coupure est le type de coupure à faire sur la police si celle-ci va en dehors de la région de coupure. Pas grand-chose à dire dessus, laissez-le juste à la valeur par défaut.

    Sélectionnez
                                    CLIP_DEFAULT_PRECIS,            // Précision du découpage

    La qualité de rendu est très importante. Vous pouvez avoir PROOF, DRAFT, NONANTIALIASED, DEFAULT ou ANTIALIASED. Nous savons tous que les polices ANTIALIASED rendent bien :) L'anticrénelage d'une police a le même effet que vous avez lorsque vous activez la douceur des polices dans Windows. Cela rend tout moins déchiqueté.

    Sélectionnez
                                    ANTIALIASED_QUALITY,            // Qualité de rendu

    Ensuite nous avons les options de famille et de pitch. Pour le pitch vous pouvez avoir DEFAULT_PITCH, FIXED_PITCH et VARIABLE_PITCH, et pour la famille vous pouvez avoir FF_DECORATIVE, FF_MODERN, FF_ROMAN, FF_SCRIPT, FF_SWISS, FF_DONTCARE. Jouez avec pour trouver ce que vous voulez faire. J'ai juste mis les deux à défaut.

    Sélectionnez
                                    FF_DONTCARE|DEFAULT_PITCH,      // Famille et pitch

    Finalement… Nous avons le nom de la police. Lancez Microsoft Word ou autre éditeur de texte. Cliquez sur le menu ascenseur pour la police et trouvez une police qui vous plaît. Pour utiliser la police, remplacez 'Courier New' par le nom de la police que vous préférez utiliser.

    Sélectionnez
                                    "Courier New");                 // Nom de la police

    oldFont garde la police utilisée avant. SelectObject va retourner la police (ou pinceau, ou remplisseur, ou n'importe quel autre objet GDI) qui était défini avant de changer pour la nouvelle police. La façon dont GDI travaille est telle que l'utilisation de la valeur de retour de SelectObject n'est pas très évidente. Au premier regard, on pourrait croire que le code sélectionne la nouvelle police et retourne un pointeur qui va être contenu dans oldFont.

    Sélectionnez
            oldfont = (HFONT)SelectObject(hDC, font);               // Sélectionne la police que nous voulons
        wglUseFontBitmaps(hDC, 32, 96, base);                   // Construit 96 caractères en commençant par le caractère 32
        SelectObject(hDC, oldfont);                             // Sélectionne la police que nous voulons
        DeleteObject(font);                                     // Supprime la police
}

    Le code qui suit est assez simple. Il supprime les 96 listes d'affichage de la mémoire en partant de la première spécifiée par base. Je ne suis pas sûr que Windows l'aurait fait pour vous, mais il est mieux d'être sûr que d'être désolé. :)

    Sélectionnez
    GLvoid KillFont(GLvoid)                                         // Détruit la liste des polices
{
        glDeleteLists(base, 96);                                // Détruit les 96 listes de caractères (AJOUT)
}

    Maintenant la fonction utile d'affichage de texte OpenGL. Vous pouvez appeler cette partie du code avec la commande glPrint("Le message va ici"). Le texte est sauvegardé dans la chaine de caractères *fmt.

    Sélectionnez
    GLvoid glPrint(const char *fmt, ...)                            // Personnalisation de la fonction "Print" avec OpenGL
{

    La première ligne ci-dessous crée un espace de stockage pour une chaine de caractères de 256 éléments. text est la chaine que nous allons afficher à l'écran. La seconde ligne en dessous, crée un pointeur qui pointe sur une liste d'arguments que nous passons avec la chaine de caractères. Si nous envoyons des variables avec le texte, le pointeur pointera sur celles-ci.

    Sélectionnez
            char            text[256];                              // Garde notre chaine de caractères
        va_list         ap;                                     // Pointe sur la liste d'arguments

    Les deux prochaines lignes de code vérifient qu'il n'y a rien à afficher. S'il n'y a pas de texte, fmt va être équivalent à rien (NULL), et rien ne sera dessiné à l'écran.

    Sélectionnez
            if (fmt == NULL)                                        // S'il n'y a pas de texte
                return;                                         // Ne fait rien

    Les prochaines lignes de code convertissent les symboles dans le texte en nombres qu'ils représentent. Le texte final et tous les symboles convertis sont stockés dans la chaine de caractères text. Je vais expliquer les symboles en détail un peu plus bas.

    Sélectionnez
            va_start(ap, fmt);                                      // Analyse la chaine pour les variables
            vsprintf(text, fmt, ap);                            // Puis convertit les symboles en nombres
        va_end(ap);                                             // Les résultats sont dans text

    Nous pouvons pousser la GL_LIST_BOT, ce qui évite glListBase d'être affecté par les autres listes d'affichage qui peuvent être utilisées dans notre programme.

    La commande glListBase(base-32) est un peu dure à expliquer. Disons que nous voulons dessiner la lettre 'A', qui est représentée par le nombre 65. Sans glListBase(base-32) OpenGL ne saurait pas où trouver la lettre. Il irait regarder à la liste d'affichage 65, mais si base était égale à 1000, 'A' serait actuellement sauvegardé à la liste d'affichage 1065. Donc par la définition d'un point de départ, OpenGL va savoir où trouver la bonne liste d'affichage. La raison de soustraire 32 est que nous n'avons jamais fait les 32 premières listes d'affichage. Nous les avons passées. Donc nous devons le dire à OpenGL en soustrayant 32 à la valeur de base. J'espère que c'est clair.

    Sélectionnez
            glPushAttrib(GL_LIST_BIT);                              // Met dans la pile le bit des listes d'affiches (AJOUT)
        glListBase(base - 32);                                  // Définit la base au caractère 32       (AJOUT)

    Maintenant qu'OpenGL sait où sont stockées les lettres, nous pouvons lui dire d'écrire le texte à l'écran. glCallLists est une commande très intéressante. Elle est capable de mettre plus d'une liste d'affichage à la fois à l'écran.

    La ligne ci-dessous fait la chose suivante : premièrement elle dit à OpenGL que nous allons afficher les listes à l'écran. strlen(text) trouve le nombre de lettres que nous avons à envoyer à l'écran. Ensuite la fonction nécessite de connaitre le plus grand numéro de liste qui va être utilisé. Nous n'envoyons pas plus de 255 caractères. Le paramètre des listes est traité comme un tableau d'octets non signés, chacun dans l'ensemble 0 à 255. Finalement, nous disons ce qu'il faut afficher en passant text (pointeur sur une chaine de caractères).

    Au cas où vous seriez en train de vous demander pourquoi les lettres ne tombent pas côte à côte. Chaque liste d'affichage pour chaque caractère connait où le côté droit de la lettre est. Après que chaque lettre soit dessinée, OpenGL se déplace sur la droite de la lettre dessinée. La prochaine lettre ou objet va être dessiné en commençant par le dernier emplacement où OpenGL s'est déplacé, ce qui correspond à la droite de la dernière lettre.

    Finalement, nous remettons en place le paramètre GL_LIST_BIT à la valeur qu'il avait avant que l'on utilise notre base avec glListBase(base-32).

    Sélectionnez
            glCallLists(strlen(text), GL_UNSIGNED_BYTE, text);      // Dessine la liste d'affichage text (AJOUT)
        glPopAttrib();                                          // Remet le bit de la liste d'affichage (AJOUT)
}

    La seule chose de différente dans le code de InitGL() est la ligne BuildFont(). Elle appelle le code ci-dessus qui construit notre police donc OpenGL pourra l'utiliser plus tard.

    Sélectionnez
    int InitGL(GLvoid)                                              // Toute initialisation pour OpenGL va ici
{
        glShadeModel(GL_SMOOTH);                                // Activation des ombres douces
        glClearColor(0.0f, 0.0f, 0.0f, 0.5f);                   // Fond noir
        glClearDepth(1.0f);                                     // Paramétrage du tampon de profondeur
        glEnable(GL_DEPTH_TEST);                                // Activation du test de profondeur
        glDepthFunc(GL_LEQUAL);                                 // Le type de test de profondeur que l'on veut
        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);      // Calcul de belle perspective

        BuildFont();                                            // Construction de la police

        return TRUE;                                            // L'initialisation s'est bien passée
}

    Maintenant, passons au code pour l'affichage. Nous commençons par effacer l'écran et le tampon de profondeur. Nous appelons glLoadIdentity() pour tout réinitialiser. Ensuite nous nous déplaçons d'une unité dans l'écran. Si nous ne le faisions pas, le texte ne serait pas visible. Les polices Bitmap sont mieux quand vous utilisez une projection orthogonale plutôt qu'une perspective, mais la projection orthogonale rend mal, donc pour que cela marche, nous nous déplaçons.

    Vous remarquerez que même si nous nous déplaçons plus dans l'écran, la taille de la police ne va pas rétrécir comme vous l'auriez espéré. Ce qui se passe réellement c'est que lorsque vous vous déplacez dans l'écran, vous allez avoir plus de contrôle sur l'emplacement du texte. Si vous vous déplacez d'une unité dans l'écran, vous pouvez placer le texte entre -0.5 et 0.5 sur l'axe des X. Si vous vous déplacez de 10 unités dans l'écran, vous pouvez placer le texte entre -5 et 5. Cela vous donne juste plus de contrôle au lieu d'utiliser des nombres décimaux pour la position du texte. Rien ne va changer dans la taille du texte. Même pas avec glScalef(x,y,z). Si vous voulez une police plus grande ou plus petite, faites-la plus grande ou plus petite lors de la création !

    Sélectionnez
    int DrawGLScene(GLvoid)                                         // Ici le code de dessin de la scène
{
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);     // Efface l'écran et le tampon de profondeur
        glLoadIdentity();                                       // Réinitialise la vue
        glTranslatef(0.0f,0.0f,-1.0f);                          // Déplace d'une unité dans l'écran

    Maintenant nous allons utiliser des mathématiques fantaisistes pour avoir les couleurs. Ne vous inquiétez pas si vous ne comprenez pas ce que je fais. J'aime bien jouer avec plein de variables et de combines pour arriver à mes résultats.

    Dans ce cas, j'utilise les deux compteurs pour déplacer le texte sur l'écran et pour changer les couleurs rouge, vert et bleu. Le rouge ira de -1.0 à 1.0 en utilisant COS et le compteur 1. Le vert ira aussi de -1.0 à 1.0 en utilisant SIN et le compteur 2. Le bleu ira de 0.5 à 1.5 en utilisant COS et les compteurs 1 et 2. De cette façon bleu ne sera jamais 0, et le texte ne disparaîtra jamais complètement. Idiot, mais cela marche. :)

    Sélectionnez
            // Changement de la couleur selon la position
        glColor3f(1.0f*float(cos(cnt1)),1.0f*float(sin(cnt2)),1.0f-0.5f*float(cos(cnt1+cnt2)));

    Maintenant une nouvelle commande. glRasterPos2f(x,y) va positionner notre police Bitmap sur l'écran. Le centre de l'écran est 0,0. Remarquez qu'il n'y a pas d'axe Z. La police Bitmap utilise seulement l'axe des X (gauche/droite) et l'axe des Y (haut/bas). Parce que nous nous déplaçons d'une unité dans l'écran, le bord gauche est à -0.5 et le bord droit à 0.5. Vous pouvez remarquer que je me déplace de 0.45 pixel sur la gauche sur l'axe des X. Cela déplace le texte au centre de l'écran. Sinon, il aurait été plus sur la droite de l'écran car il aurait été dessiné du centre vers la droite.

    Les mathématiques fantaisistes font la même chose que pour le choix de la couleur. Cela déplace le texte sur l'axe des X de -0.50 à -0.40 (rappel : nous enlevons 0.45 de la droite dès le départ). Cela garde tout le temps le texte sur l'écran. Cela oscille de gauche à droite en utilisant COS et le compteur 1. Cela se déplace de -0.35 à 0.35 sur l'axe des Y en utilisant SIN et le compteur 2.

    Sélectionnez
            // Position du texte sur l'écran
        glRasterPos2f(-0.45f+0.05f*float(cos(cnt1)), 0.35f*float(sin(cnt2)));

    Maintenant, ma partie préférée… Écrire le texte sur l'écran. J'ai essayé de faire super facile, et très convivial. Vous pouvez remarquer que cela ressemble beaucoup à une commande OpenGL, mixée avec la bonne vieille fonction d'affichage :) Tout ce qu'il y a à faire pour écrire du texte à l'écran est glPrint("{Tout ce que vous voulez}"). C'est facile. Le texte va être dessiné à l'écran à la position exacte que vous avez déterminée.

    Shawn T. m'a envoyé un nouveau code qui permet à glPrint de passer des variables à l'écran. Cela signifie que vous pouvez incrémenter un compteur et l'afficher à l'écran ! Cela marche comme ça… Ci-dessous vous voyez le texte normal. Alors il y a un espace, un tiret, un espace et alors un "symbole" (%7.2f). Maintenant vous devez regarder %7.2f et vous dire « Bon sang, qu'est-ce que cela signifie ». C'est très simple. % est comme un marqueur qui dit de ne pas afficher 7.2f à l'écran, car cela représente une variable. Le 7 signifie d'afficher un maximum de sept chiffres pour la partie entière. Puis le symbole décimal, et juste après la virgule, un 2. Ce 2 signifie que vous n'allez afficher que deux chiffres à droite du symbole décimal. Finalement, le f. Le f signifie que le nombre que vous voulez afficher est un nombre à virgule flottante. Nous voulons afficher la valeur de cnt1 à l'écran. Comme exemple, si cnt1 était égal à 300.12345f le nombre finalement affiché serait 300.12. Le 3, 4 et 5 après le point seraient amputés car nous ne voulons que deux chiffres dans la partie décimale.

    Je sais si que vous êtes un bon programmeur de C, ceci est absolument basique, mais il peut y avoir des personnes qui n'ont jamais utilisé printf. Si vous êtes intéressés et voulez en apprendre plus sur les symboles, achetez un livre, ou lisez la MSDN.

    Sélectionnez
            glPrint("Active OpenGL Text With NeHe - %7.2f", cnt1);  // Afficher le texte à l'écran

    Il reste àincrémenter les compteurs selon différentes valeurs pour que les couleurs et la position du texte changent.

    Sélectionnez
            cnt1+=0.051f;                                           // Incrémente le premier compteur
        cnt2+=0.005f;                                           // Incrémente le second compteur
        return TRUE;                                            // Tout est OK
}

    La dernière chose à faire est d'ajouter KillFont() à la fin de KillGLWindow() comme je le montre ci-dessous. C'est important d'ajouter cette ligne. Cela nettoie les choses avant de quitter le programme.

    Sélectionnez
            if (!UnregisterClass("OpenGL",hInstance))               // Peut-on désenregistrer la classe
        {
                MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
                hInstance=NULL;                                 // Met hInstance à NULL
        }

        KillFont();                                             // Détruit la police
}

    C'est tout… Tout ce que vous devez savoir pour utiliser les polices Bitmap dans vos propres applications OpenGL. J'ai recherché sur le net un tutoriel comme celui-ci, et je n'ai rien trouvé. Peut-être que mon site est le premier à couvrir ce sujet avec un code C facile à comprendre ? Peu importe. Profitez du tutoriel et codez bien !

    Jeff Molofee (NeHe)

    III. Téléchargements

    Compte tenu du nombre de versions de codes sources pour les tutoriels NeHe, nous les laissons en anglais. En principe, si vous avez compris le code présenté dans ce tutoriel (et les tutoriels antérieurs), vous n'aurez pas de mal à les comprendre :

    • Borland C++ Builder 6 (Conversion by Christian Kindahl) ;
    • Code Warrior 5.3 (Conversion by Scott Lupton) ;
    • Cygwin (Conversion by John Jolly) ;
    • Delphi (Conversion by Michal Tucek) ;
    • Dev C++ (Conversion by Dan) ;
    • Euphoria (Conversion by Evan Marshall) ;
    • Game GLUT (Conversion by Milikas Anastasios) ;
    • Irix/GLUT (Conversion by Rob Fletcher) ;
    • Java (Conversion by Jeff Kirby) ;
    • JoGL (Conversion by Nicholas Campbell) ;
    • LCC Win32 (Conversion by Robert Wishlaw) ;
    • Linux (Conversion by Richard Campbell) ;
    • Linux/GLX (Conversion by Mihael Vrbanec) ;
    • Linux/SDL (Conversion by Ti Leggett) ;
    • LWJGL (Conversion by Mark Bernard) ;
    • Mac OS (Conversion by Anthony Parker) ;
    • Mac OS X/Cocoa (Conversion by Bryan Blackburn) ;
    • MASM (Conversion by Andras Kobor) ;
    • Pelles C (Conversion by Pelle Orinius) ;
    • Python (Conversion by Brian Leair) ;
    • Visual Basic (Conversion by Ross Dawson) ;
    • Visual Basic (Conversion by Edo) ;
    • Visual C++ ;
    • Visual Studio .NET (Conversion by Grant James).


Les polices vectorielles

Les polices vectorielles

  • I. Contributions
  • II. Tutoriel
  • III. Téléchargements
  • IV. Remerciements
  • V. Liens

Les polices Bitmap ne sont pas assez bonnes ? Vous avez besoin de plus de contrôle sur l'emplacement de vos polices sur l'axe des Z ? Vous avez besoin de polices 2D (polices avec une profondeur) ? Vous avez besoin d'une police en fil de fer ? Si tel est le cas, les polices vectorielles sont la solution parfaite. Vous pouvez les déplacer sur l'axe des Z et les redimensionner. Vous pouvez les tourner autour d'un axe (chose que vous ne pouviez pas faire avec les polices Bitmap), et, parce que les normales sont générées pour chaque caractère, la police peut être éclairée proprement. Vous pouvez construire les polices vectorielles en utilisant n'importe laquelle des polices installées sur votre ordinateur. C'est certainement une bonne police pour les jeux et les démos.

I. Contributions

Nous recherchons toujours des traducteurs pour finir ce projet de traduction des articles de NeHe. Votre aide serait appréciée ! Vous êtes intéressé ? Alors contactez-nous à l'adresse suivante : nehe@redaction-developpez.com .

II. Tutoriel

Ce tutoriel est une suite au tutoriel précédent. Dans le tutoriel 13, je vous ai appris comment utiliser les polices Bitmap. Dans ce tutoriel, je vais vous apprendre à utiliser les polices vectorielles.

La façon dont nous créons les polices vectorielles est assez proche de ce que nous avons fait pour les polices Bitmap dans la leçon 13. Toutefois… les polices vectorielles sont 100 fois plus cool ! Vous pouvez dimensionner les polices vectorielles. Les polices vectorielles peuvent être déplacées dans un monde 3D, et les polices vectorielles peuvent avoir une épaisseur ! Plus de caractères 2D plats. Avec les polices vectorielles, vous pouvez transformer n'importe quelle police installée sur votre ordinateur en une police 3D pour OpenGL, y ajouter les normales afin que les caractères s'illuminent d'une belle façon lorsque la lumière brille sur eux.

Une petite note, ce code est spécifique à Windows. Il utilise les fonctions wgl de Windows pour construire la police. Apparemment Apple a un support de agl qui pourrait faire la même chose, et X a glx. Malheureusement, je ne peux garantir que ce code soit portable. Si quelqu'un a un code plateforme indépendant pour dessiner les polices à l'écran, envoyez-le-moi et j'écrirai un autre tutoriel.

Nous commençons par le code typique de la première leçon. Nous allons ajouter l'entête stdio.h pour les opérations d'entrée/sortie standard ; le fichier d'entête stdarg.h pour l'analyse du texte et la conversion de variables à texte et finalement le fichier d'entête math.h qui nous permettra de déplacer le texte avec SIN et COS.

Sélectionnez
#include <windows.h>                        // Fichier d'entête pour Windows
#include <math.h>                        // Fichier d'entête pour la bibliothèque mathématique        (AJOUT)
#include <stdio.h>                        // Fichier d'entête pour pour les entrées/sorties    (AJOUT)
#include <stdarg.h>                        // Fichier d'entête pour pour les fonctions à arguments variables    (AJOUT)
#include <gl\gl.h>                        // Fichier d'entête pour la bibliothèque OpenGL32
#include <gl\glu.h>                        // Fichier d'entête pour la bibliothèque GLu32
#include <gl\glaux.h>                        // Fichier d'entête pour la bibliothèque GLaux

HDC        hDC=NULL;                    // Contexte de périphérique privé GDI
HGLRC        hRC=NULL;                    // Contexte de rendu permanent
HWND        hWnd=NULL;                    // Contient notre ID de la fenêtre
HINSTANCE    hInstance;                    // Contient l'instance de l'application

Nous ajoutons deux nouvelles variables. base va contenir le numéro de la première liste d'affichage que nous allons créer. Chaque caractère requiert sa propre liste d'affichage. Le caractère 'A' est 65 dans la liste d'affichage, 'B' est 66, 'C' est 67, etc. Donc 'A' va être dans la liste d'affichage base+65.

Ensuite nous ajoutons une variable rot. rot va être utilisée pour tourner le texte sur l'écran en utilisant SIN et COS. Cela va aussi servir pour les couleurs.

Sélectionnez
GLuint    base;                            // Base de la liste d'affichage pour la police de caractères (AJOUT)
GLfloat    rot;                            // Utilisé pour tourner le texte (AJOUT)

bool    keys[256];                        // Tableau utilisé pour la fonction du clavier
bool    active=TRUE;                        // Drapeau de fenêtre active défini à TRUE par défaut
bool    fullscreen=TRUE;                    // Drapeau de plein écran défini en plein écran par défaut

GLYPHMETRICSFLOAT gmf[256] va contenir les informations à propos de l'emplacement et de l'orientation de chacune de nos 256 listes d'affichage de la police vectorielle. Nous sélectionnons une lettre en utilisant gmf[num]. num est le numéro de la liste d'affichage que nous voulons connaître. Plus tard dans le code, je vais vous montrer comment trouver la largeur de chaque caractère pour pouvoir automatiquement centrer le texte sur l'écran. Gardez à l'esprit que chaque caractère peut avoir une largeur différente. glyphmetrics va rendre notre vie plus simple.

Sélectionnez
GLYPHMETRICSFLOAT gmf[256];                    // Emplacement pour les informations sur notre police

LRESULT    CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);        // Déclaration de WndProc

La prochaine section de code construit notre police de la même façon que pour les polices Bitmap. Tout comme dans la leçon 13, cette section de code était la partie la plus difficile à faire.

'HFONT font' va contenir l'ID de la police pour Windows.

Après nous définissons base. Nous faisons ceci par la création d'un groupe de 256 listes d'affichage en utilisant glGenLists(256). Suite à la création des listes d'affichage, la variable base contiendra le numéro de la première liste.

Sélectionnez
GLvoid BuildFont(GLvoid)                    // Construit notre police
{
    HFONT    font;                        // ID de police pour Windows

    base = glGenLists(256);                    // Stockage pour 256 caractères

Partie plus amusante. Nous allons créer la police vectorielle. Nous commençons par spécifier la taille de la police. Vous pouvez remarquer que le nombre est négatif. En mettant un moins, nous disons à Windows de nous trouver la police basée sur la hauteur des caractères. Si nous utilisions un nombre positif, nous comparerions la police en se basant sur la hauteur de la cellule.

Sélectionnez
    font = CreateFont(    -12,                // Hauteur de la police

Ensuite nous spécifions la largeur de la cellule. Vous pouvez remarquer que j'ai mis zéro. En mettant cette valeur à zéro, Windows va utiliser la valeur par défaut. Vous pouvez jouer avec cette valeur si vous voulez, faire la police large, etc.

Sélectionnez
                0,                // Largeur de la police

L'angle d'échappement va tourner la police. L'angle d'orientation, d'après l'aide MSDN, spécifie l'angle en dixièmes de degré, entre chaque base des caractères et l'axe des X du périphérique. Malheureusement, je n'ai aucune idée de ce que cela signifie. :(

Sélectionnez
                0,                // Angle d'échappement
                0,                // Angle d'orientation

Le poids de la police est un super paramètre. Vous pouvez entrer un nombre entre 0 et 1000 ou vous pouvez utiliser une des valeurs prédéfinies. FW_DONTCARE est 0, FW_NORMAL est 400, FW_BOLD est 700 et FW_BLACK est 800. Il y a beaucoup plus de valeurs prédéfinies, mais ces quatre-là sont un bon exemple. Plus la valeur est élevée, plus la police est épaisse (plus gras).

Sélectionnez
                FW_BOLD,            // Poids de la police

Italique, souligné et barré peuvent être soit TRUE soit FALSE. Basiquement, si souligné est TRUE, la police va être soulignée. Si c'est FALSE, elle ne va pas l'être. Simple :)

Sélectionnez
                FALSE,                // Italique
                FALSE,                // Souligné
                FALSE,                // Barré

L'identifieur du jeu de caractères décrit le type du jeu de caractères que vous souhaitez utiliser. Il y en a de trop pour tous les expliquer. CHINESEBIG5_CHARSET, GREEK_CHARSET, RUSSIAN_CHARSET, DEFAULT_CHARSET, etc. ANSI est celui que j'utilise, bien que le DEFAULT marcherait sans doute tout aussi bien.

Si vous êtes intéressés dans l'utilisation des polices comme Webdings ou Wingdings, vous devez utiliser SYMBOL_CHARSET à la place de AINSI_CHARSET.

Sélectionnez
                ANSI_CHARSET,            // Identifiant du jeu de caractères

La précision du rendu est très importante. Cela indique à Windows, le type de jeu de caractères à utiliser s'il y en a plus d'un de disponible. OUT_TT_PRECIS indique à Windows que s'il y a plus d'un type de police, de choisir celui avec le même nom, en sélectionnant la version TRUETYPE de la police. Les polices Truetype rendent toujours mieux, précisément lorsque vous les grossissez. Vous pouvez toujours utiliser OUT_TT_ONLY_PRECIS, qui tente TOUJOURS d'utiliser une police TRUETYPE.

Sélectionnez
                OUT_TT_PRECIS,            // Précision du rendu

La précision de la coupure est le type de coupure à faire sur la police si celle-ci va en dehors de la région de coupure. Pas grand-chose à dire dessus, laissez-le juste à la valeur par défaut.

Sélectionnez
                CLIP_DEFAULT_PRECIS,        // Précision du découpage

La qualité de rendu est très importante. Vous pouvez avoir PROOF, DRAFT, NONANTIALIASED, DEFAULT or ANTIALIASED. Nous savons tous que les polices ANTIALIASED rendent bien. :) L'anticrénelage d'une police a le même effet que vous avez lorsque vous activez la douceur des polices dans Windows. Cela rend tout moins déchiqueté.

Sélectionnez
                ANTIALIASED_QUALITY,        // Qualité de rendu

Ensuite nous avons les options de famille et de pitch. Pour le pitch vous pouvez avoir DEFAULT_PITCH, FIXED_PITCH et VARIABLE_PITCH, et pour la famille vous pouvez avoir FF_DECORATIVE, FF_MODERN, FF_ROMAN, FF_SCRIPT, FF_SWISS, FF_DONTCARE. Jouez avec pour trouver ce que vous voulez faire. J'ai juste défini les deux à défaut.

Sélectionnez
                FF_DONTCARE|DEFAULT_PITCH,    // Famille et pitch

Finalement… Nous avons le nom de la police. Lancez Microsoft Word ou un autre éditeur de texte. Cliquez sur le menu ascenseur pour la police et trouvez une police qui vous plaît. Pour utiliser la police, remplacez 'Courier New' avec le nom de la police que vous préférez utiliser.

Sélectionnez
                "Courier New");            // Nom de la police

Maintenant, nous sélectionnons la police en la liant à notre DC.

Sélectionnez
    SelectObject(hDC, font);                // Sélectionne la police que nous avons créée

Maintenant du code nouveau. Nous construisons la police en utilisant la nouvelle commande wgUseFontOutlines. Nous sélectionnons notre DC, le caractère de début, le nombre de caractères à créer et la 'base' de la liste d'affichage. C'est très similaire à la façon de construire une police Bitmap.

Sélectionnez
    wglUseFontOutlines(    hDC,                // Sélectionne le DC courant
                0,                // Caractère de début
                255,                // Nombre de listes d'affichage à construire
                base,                // Début des listes d'affichage

Toutefois, ce n'est pas tout. Nous devons ensuite définir le niveau de divergence. Plus il sera proche de 0.0f, plus la police sera belle. Après avoir défini le niveau de divergence, nous allons définir l'épaisseur de la police. Cela décrit comment la police est sur l'axe des Z. 0.0f va produire une police plate et 1.0f va produire une police avec de l'épaisseur.

Le paramètre WGL_FONT_POLYGONS indique à OpenGL de créer une police solide en utilisant des polygones. Si nous utilisons WGL_FONT_LINES à la place, la police sera en fil de fer (constituée de lignes). Il est important de remarquer que si vous utilisez GL_FONT_LINES, les normales ne seront pas générées donc l'éclairage ne marchera pas correctement.

Le dernier paramètre gmf pointe sur l'adresse du tampon pour les données des listes d'affichage.

Sélectionnez
                0.0f,                // Divergence en partant des vrais contours
                0.2f,                // Épaisseur de la police dans l'axe des Z
                WGL_FONT_POLYGONS,        // Utilisation de polygones, et non de lignes
                gmf);                // Adresse du tampon où l'on doit recevoir les données
}

Le code qui suit est très simple. Il supprime les 256 listes d'affichage de la mémoire en commençant par la première liste spécifiée par base. Je ne suis pas sûr que Windows l'aurait fait pour vous, mais c'est mieux d'être prudent que désolé :)

Sélectionnez
GLvoid KillFont(GLvoid)                        // Supprime la police
{
     glDeleteLists(base, 256);                // Supprime les 256 listes d'affichage
}

Maintenant ma pratique fonction de texte pour OpenGL. Vous pouvez appeler cette section de code avec la commande glPrint("le message va ici"). Exactement de la même façon que pour les polices Bitmap de la leçon 13. Le texte va être sauvegardé dans la chaine de caractères fmt.

Sélectionnez
GLvoid glPrint(const char *fmt, ...)                // Fonction personnalisée OpenGL "Print"
{

La première ligne ci-dessous définit la variable appelée length. Nous allons utiliser cette variable pour trouver la longueur de notre texte. La seconde ligne crée un espace de stockage pour une chaine de caractères de 256 éléments. text est la chaine que nous allons afficher à l'écran. La troisième ligne crée un pointeur pointant sur la liste d'arguments que nous passons avec la chaine de caractères. Si nous envoyons des variables avec le texte, le pointeur va pointer sur celles-ci.

Sélectionnez
    float        length=0;                // Utilisé pour trouver la longueur du texte
    char        text[256];                // Contient notre chaine de caractères
    va_list        ap;                    // Pointeur sur la liste d'arguments

Les deux prochaines lignes de code vérifient qu'il n'y a rien à afficher. S'il n'y a pas de texte, fmt va être égale à rien (NULL), et rien ne sera affiché sur l'écran.

Sélectionnez
    if (fmt == NULL)                    // S'il n'y a pas de texte
        return;                        // Ne rien faire

Les prochaines lignes de code convertissent les symboles dans le texte en nombres qu'ils représentent. Le texte final et tous les symboles convertis sont stockés dans la chaine de caractères text. Je vais expliquer les symboles en détail un peu plus bas.

Sélectionnez
    va_start(ap, fmt);                    // Analyse la chaine de caractères pour les variables
        vsprintf(text, fmt, ap);                // Et convertit les symboles en nombres
    va_end(ap);                        // Le résultat est mis dans text

Merci à Jim Williams pour la suggestion du code ci-dessous. Je centrais le texte manuellement. Sa méthode marche beaucoup mieux :)

Nous commençons par faire une boucle qui va parcourir le texte caractère par caractère. strlen(text) nous donne la taille de notre texte. Après avoir mis en place la boucle, nous allons augmenter la valeur de la longueur par la largeur de chaque caractère. Quand nous avons fini, la valeur gardée dans length va être la largeur de notre chaine de caractères. Donc si nous affichions "hello" et que par chance tous les caractères mesuraient 10 unités de largeur, nous augmenterions la valeur de length par la largeur de la première lettre, 10. Nous vérifierions ensuite la largeur de la seconde lettre. La largeur serait aussi de 10, donc la largeur deviendrait 10+10 (20). Une fois la largeur des cinq lettres vérifiée, elle sera égale à 50 (5*10).

Le code qui nous donne la largeur de chaque caractère est gmf[text[loop]].gmfCellIncX. Rappelez-vous que ce gmf garde les informations de chaque liste d'affichage. Si loop est égal à 0, text[loop] va correspondre au premier caractère de notre chaine. Si loop est égal à 1, text[loop] va correspondre au deuxième caractère de la chaine. gmfCellIncX nous indique la largeur du caractère. gmfCellIncX est en fait la distance du déplacement sur la droite de notre écran après chaque caractère qui a été dessiné pour que les caractères ne soient pas les uns sur les autres. Donc cette distance est notre largeur. :) Vous pouvez aussi trouver la hauteur des caractères avec gmfCellInvY. Ceci peut être pratique si vous dessinez du texte verticalement au lieu d'horizontalement.

NOTE : Curt Werline ajoute : quand vous calculez la dimension de votre chaine de caractères, vous utilisez gmfCellIncX pour la largeur et vous pensez utiliser gmfCellIncY pour la hauteur. Ces valeurs sont les décalages et non les vraies dimensions. Pour avoir les vraies dimensions vous devez utiliser gmfBlackBoxX et gmfBlackBoxY.

Sélectionnez
    for (unsigned int loop=0;loop<(strlen(text));loop++)    // Boucle pour trouver la longueur du texte
    {
        length+=gmf[text[loop]].gmfCellIncX;        // Ajoute à la longueur la largeur de chaque caractère
    }

Finalement, nous prenons la longueur que nous avons calculée et nous en faisons un nombre négatif (parce que nous devons nous déplacer sur la gauche du centre de notre texte pour le centrer). Alors nous divisons la longueur par deux. Nous ne voulons pas que tout le texte se déplace sur la gauche du centre, juste la moitié !

Sélectionnez
    glTranslatef(-length/2,0.0f,0.0f);            // Centre le texte sur l'écran

Puis nous sauvegardons le GL_LIST_BIT, qui empêche glListBase de modifier les autres listes d'affichage que nous pourrions utiliser dans notre programme.

La commande glListBase(base) indique à OpenGL où trouver la bonne liste d'affichage pour chaque caractère.

Sélectionnez
    glPushAttrib(GL_LIST_BIT);                // Sauvegarde les bits des listes d'affichage
    glListBase(base);                    // Définit le caractère de base à 0

Maintenant qu'OpenGL sait où les caractères sont, nous pouvons lui dire d'écrire le texte à l'écran. glCallLists écrit la totalité de la chaine de caractères à l'écran, d'un coup, en faisant plusieurs listes d'affichage pour vous.

La ligne ci-dessous fait ce qui suit. Premièrement, cela indique à OpenGL que nous allons afficher des listes d'affichage à l'écran. strlen(text) trouve le nombre de lettres que nous avons à envoyer à l'écran. Ensuite la fonction nécessite de connaitre le plus grand numéro de liste qui va être utilisé. Nous n'envoyons jamais plus de 255 caractères. Donc nous pouvons utiliser un UNSIGNED_BYTE. (Un byte (octet) représente un nombre entre 0 et 255 ce qui correspond exactement à ce dont nous avons besoin). Finalement, nous indiquons ce qu'il faut afficher en donnant la chaine de caractères text.

Dans ce cas vous vous demandez pourquoi les lettres ne tombent pas pile les unes sur les autres. Chaque liste d'affichage connait pour chaque caractère le côté droit de celui-ci. Après que chaque lettre soit dessinée à l'écran, OpenGL se déplace sur le côté droit de la lettre dessinée. La prochaine lettre ou prochain objet va être dessiné en commençant par le dernier emplacement qu'occupe OpenGL, ce qui signifie à droite de la dernière lettre.

Finalement, nous replaçons le GL_LIST_BIT comme avant la mise en place de notre base avec glListBase(base).

Sélectionnez
    glCallLists(strlen(text), GL_UNSIGNED_BYTE, text);    // Dessine le texte des listes d'affichage
    glPopAttrib();                        // Replace le bit des listes d'affichage
}

Le code de redimensionnement est le même que dans la leçon 1 donc nous passons directement à la suite.

Il y a peu de nouvelles lignes à la fin de notre code InitGL. La ligne BuildFont() implémentée dans la leçon 13 est toujours ici, avec un nouveau code qui permet un éclairage rapide et sale. Light0 est prédéfini sur la plupart des cartes et va illuminer la scène d'une belle façon, sans effort de ma part. :)

J'ai aussi ajouté la commande glEnable(GL_Color_Material), car les caractères sont des objets 3D donc vous devez activer la coloration des matériaux, sinon, changer la couleur avec glColor3f(r,g,b) ne va pas changer la couleur du texte. Si vous dessinez sur l'écran vos formes pendant que vous écrivez du texte, activez la coloration des matériaux avant d'écrire le texte, et désactivez-la après avoir écrit le texte, sinon, les objets sur l'écran vont être colorés.

Sélectionnez
int InitGL(GLvoid)                        // L'initialisation de OpenGL va ici
{
    glShadeModel(GL_SMOOTH);                // Activation de l'ombre douce
    glClearColor(0.0f, 0.0f, 0.0f, 0.5f);            // Fond noir
    glClearDepth(1.0f);                    // Mise en place du tampon de profondeur
    glEnable(GL_DEPTH_TEST);                // Activation du test de profondeur
    glDepthFunc(GL_LEQUAL);                    // Le type de test de profondeur à faire
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);    // Calcul de jolies perspectives
    glEnable(GL_LIGHT0);                    // Activation de la lampe 0 (rapide et sale)    (AJOUT)
    glEnable(GL_LIGHTING);                    // Activation de l'éclairage            (AJOUT)
    glEnable(GL_COLOR_MATERIAL);                // Activation de la coloration des matériaux    (AJOUT)

    BuildFont();                        // Construction de la police            (AJOUT)

    return TRUE;                        // L'initialisation s'est bien déroulée

}

Maintenant le code de l'affichage. Nous commençons par effacer l'écran et le tampon de profondeur. Nous appelons glLoadIdentity() pour tout réinitialiser. Puis nous nous déplaçons de dix unités dans l'écran. Les polices vectorielles rendent bien en perspective. Plus vous vous déplacez dans l'écran, plus la police devient petite. Plus vous le rapprochez vers vous, plus la police devient grande.

Les polices vectorielles peuvent aussi être dimensionnées avec la commande glScalef(x,y,z). Si vous voulez une police deux fois plus grande, utilisez glScalef(1.0f, 2.0f, 1.0f). Le 2.0f est sur l'axe des Y, ce qui indique à OpenGL de la dessiner deux fois plus grande. Si le 2.0f était sur l'axe des X, les caractères seraient deux fois plus larges.

Sélectionnez
int DrawGLScene(GLvoid)                        // Le code pour dessiner se trouve ici
{
    glClear(GL_COLOR_BUFFER_BIT

 | GL_DEPTH_BUFFER_BIT);    // Nettoie l'écran et le tampon de profondeur
    glLoadIdentity();                    // Réinitialise la vue
    glTranslatef(0.0f,0.0f,-10.0f);                // Se déplacer de 10 unités dans l'écran

Après s'être déplacé dans l'écran, nous voulons que notre texte tourne. Les trois prochaines lignes tournent l'écran sur les trois axes. Je multiplie rot avec des nombres différents pour que chaque rotation ait une vitesse différente.

Sélectionnez
    glRotatef(rot,1.0f,0.0f,0.0f);                // Rotation sur l'axe des X
    glRotatef(rot*1.5f,0.0f,1.0f,0.0f);            // Rotation sur l'axe des Y
    glRotatef(rot*1.4f,0.0f,0.0f,1.0f);            // Rotation sur l'axe des Z

Maintenant le cycle de couleurs psychédéliques. Comme d'habitude, je vais utiliser les variables compteurs (rot). Les couleurs vont avoir des pulsations grâce à COS et SIN. Je divise la valeur de rot par différents nombres pour avoir une vitesse d'incrémentation différente pour chaque couleur. Le résultat final est joli.

Sélectionnez
    // Pulsation des couleurs basée sur la rotation
    glColor3f(1.0f*float(cos(rot/20.0f)),1.0f*float(sin(rot/25.0f)),1.0f-0.5f*float(cos(rot/17.0f)));

Ma partie préférée… Écrire le texte sur l'écran. J'ai utilisé la même commande que nous avions utilisée pour écrire les polices Bitmap à l'écran. Tout ce que vous avez à faire pour écrire le texte à l'écran est glPrint("{n'importe quel texte que vous voulez}"). C'est aussi simple que ça !

Dans le code ci-dessous nous allons afficher NeHe, un espace, un tiret, un espace et finalement, le nombre de la variable rot divisé par 50 (pour ralentir un peu le compteur). Si le nombre est plus grand que 999.99 le quatrième chiffre va être coupé (nous ne demandons que trois chiffres à gauche du symbole décimal). Seulement deux chiffres vont être affichés dans la partie décimale.

Sélectionnez
     glPrint("NeHe - %3.2f",rot/50);                // Affiche du texte OpenGL à l'écran

Ensuite nous incrémentons la variable de rotation pour que les couleurs aient des pulsations et que le texte tourne.

Sélectionnez
    rot+=0.5f;                        // Incrémente la variable de rotation
    return TRUE;                        // Tout s'est bien passé
}

La dernière chose à faire est d'ajouter KillFont() à la fin de KillGLWindow() tout comme je le montre ci-dessous. C'est important d'ajouter cette ligne. Cela nettoie les choses avant de quitter le programme.

Sélectionnez
    if (!UnregisterClass("OpenGL",hInstance))        // Pouvons-nous désenregistrer la classe
    {
        MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
        hInstance=NULL;                    // Définit hInstance à NULL
    }

    KillFont();                        // Détruit la police
}

À la fin de ce tutoriel, vous devez être capable d'utiliser les polices vectorielles dans vos propres projets OpenGL. Tout comme la leçon 13, j'ai recherché sur internet pour trouver un tutoriel similaire à celui-ci, et je n'ai rien trouvé. Peut-être mon site est-il le premier à couvrir ce sujet avec plein de détails, en expliquant tout dans un code C facile à comprendre ? Bon tutoriel, et bon code !

Jeff Molofee (NeHe)

III. Téléchargements

Compte tenu du nombre de versions de codes sources pour les tutoriels NeHe, nous les laissons en anglais. En principe, si vous avez compris le code présenté dans ce tutoriel (et les tutoriels antérieurs), vous n'aurez pas de mal à les comprendre :

  • Borland C++ Builder 6 (Conversion by Christian Kindahl) ;
  • Code Warrior 5.3 (Conversion by Scott Lupton) ;
  • Cygwin (Conversion by John Jolly) ;
  • Delphi (Conversion by Michal Tucek) ;
  • Dev C++ (Conversion by Dan) ;
  • Euphoria (Conversion by Evan Marshall) ;
  • Game GLUT (Conversion by Milikas Anastasios) ;
  • Java (Conversion by Jeff Kirby) ;
  • JoGL (Conversion by Pepijn Van Eeckhoudt) ;
  • LCC Win32 (Conversion by Robert Wishlaw) ;
  • Mac OS (Conversion by Anthony Parker) ;
  • MASM (Conversion by Greg Helps) ;
  • Pelles C (Conversion by Pelle Orinius) ;
  • Visual Basic (Conversion by Ross Dawson) ;
  • Visual Basic (Conversion by Edo) ;
  • Visual C++ ;
  • Visual Studio .NET (Conversion by Grant James).


Application de texture sur les polices vectorielles

  • Application de texture sur lses polices vectorielles

    • I. Contributions
    • II. Tutoriel
    • III. Téléchargements
    • IV. Remerciements
    • V. Liens

    Après avoir posté les deux derniers tutoriels sur les polices bitmap et les polices vectorielles, j'ai reçu beaucoup de courriels venant de personnes se demandant comment elles pouvaient appliquer une texture sur les polices. Vous pouvez utiliser la génération automatique des coordonnées de texture. Cela va vous générer les coordonnées des textures par chaque polygone de la police.

    Article lu -1 fois.

    II. Tutoriel

    Une petite note, ce code est spécifique à Windows. Il utilise les fonctions wgl de Windows pour construire la police. Apparemment Apple a un support de agl qui pourrait faire la même chose, et X a glx. Malheureusement, je ne peux garantir que ce code soit portable. Si quelqu'un a un code plateforme indépendant pour dessiner les polices à l'écran,envoyez-le-moi et j'écrirai un autre tutoriel.

    Nous allons construire notre démo de la police texturée à partir du code de la leçon 14. S'il y a des changements dans une section particulière du programme, je vais réécrire le code entier de la section ce qui rendra les changements plus faciles à voir.

    La partie suivante de code est similaire au code de la leçon 14, mais cette fois nous n'allons pas inclure le fichier stdarg.h.

    Sélectionnez
    #include <windows.h>                        // Fichier d'entête pour Windows
#include <math.h>                        // Fichier d'entête pour la bibliothèque mathématique
#include <stdio.h>                        // Fichier d'entête pour les entrées/sorties
#include <gl\gl.h>                        // Fichier d'entête pour la bibliothèque OpenGL32
#include <gl\glu.h>                        // Fichier d'entête pour la bibliothèque GLu32
#include <gl\glaux.h>                        // Fichier d'entête pour la bibliothèque GLaux

HDC        hDC=NULL;                    // Contexte de périphérique privé GDI
HGLRC        hRC=NULL;                    // Contexte de rendu permanent
HWND        hWnd=NULL;                    // Contient notre ID de la fenêtre
HINSTANCE    hInstance;                    // Contient l'instance de l'application

bool    keys[256];                        // Tableau utilisé pour la fonction du clavier
bool    active=TRUE;                        // Drapeau de fenêtre active définie à TRUE par défaut
bool    fullscreen=TRUE;                    // Drapeau de plein écran défini en plein écran par défaut

    Nous allons ajouter une nouvelle variable entière ici appelée texture[ ]. Elle sera utilisée pour stocker notre texture. Les trois dernières lignes qui étaient dans le tutoriel 14 n'ont pas changé ici.

    Sélectionnez
    GLuint    texture[1];                        // Une texture à appliquer (AJOUT)
GLuint    base;                            // Base pour les listes d'affichage de la police

GLfloat    rot;                            // Utilisée pour faire tourner le texte

LRESULT    CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);        // Déclaration pour WndProc

    La partie suivante comporte quelques modifications. Dans ce tutoriel, je vais utiliser la police Wingdings pour afficher une tête de mort. Si vous voulez afficher du texte à la place, vous pouvez laisser le code comme il était dans la leçon 14, ou changer la police avec la vôtre.

    Certains d'entre vous se demandaient comment utiliser la police Wingdings ce qui me fait une autre raison de ne pas utiliser les polices standards. La police Wingdings est une police de symboles, et requiert quelques modifications pour que cela marche. Ce n'est pas aussi facile de dire à Windows d'utiliser la police Wingdings. Si vous changez le nom de la police pour Wingdings, vous remarquerez que la police n'est pas sélectionnée. Vous devez indiquer à Windows que la police est une police de symboles et non pas une police de caractères normaux. Plus d'informations là-dessus, plus tard.

    Sélectionnez
    GLvoid BuildFont(GLvoid)                    // Construit notre police
{
    GLYPHMETRICSFLOAT    gmf[256];            // Adresse du buffer qui contiendra la police
    HFONT    font;                        // ID de Windows pour la police

    base = glGenLists(256);                    // Emplacement pour 256 caractères
    font = CreateFont(    -12,                // Hauteur de la police
                0,                // Largeur de la police
                0,                // Angle d'échappement
                0,                // Angle d'orientation
                FW_BOLD,            // Poids de la police
                FALSE,                // Italique
                FALSE,                // Souligné
                FALSE,                // Barré

    Ceci est la ligne magique ! À la place d'utiliser ANSI_CHARSET comme nous l'avions fait dans le tutoriel 14, nous allons utiliser SYMBOL_CHARSET. Cela indique à Windows que la police que nous construisons n'est pas une police typiquement faite de caractères. Une police de symboles n'est pas habituellement faite de petites images (symboles). Si vous oubliez de changer cette ligne, Wingdings, Webdings et toute autre police de symboles que vous essayerez d'utiliser ne fonctionneront pas.

    Sélectionnez
                    SYMBOL_CHARSET,            // Identifiant du jeu de caractères (MODIFIÉ)

    Les lignes suivantes n'ont pas changé.

    Sélectionnez
                    OUT_TT_PRECIS,            // Précision du rendu
                CLIP_DEFAULT_PRECIS,        // Précision du découpage
                ANTIALIASED_QUALITY,        // Qualité de rendu
                FF_DONTCARE|DEFAULT_PITCH,    // Famille et Pitch

    Maintenant que nous avons sélectionné l'identifiant du jeu de symboles, nous pouvons sélectionner la police Wingdings !

    Sélectionnez
                    "Wingdings");            // Nom de la police (MODIFIÉ)

    Les lignes restantes n'ont pas changé.

    Sélectionnez
        SelectObject(hDC, font);                // Sélectionne la police que nous avons créée

    wglUseFontOutlines(    hDC,                // Sélectionne le DC courant
                0,                // Caractère de début
                255,                // Nombre de listes d'affichage à construire
                base,                // Début des listes d'affichage

    Je vais permettre plus d'écart. Cela signifie qu'OpenGL ne va pas suivre le contour de la police très exactement. Si vous définissez l'écart à 0.0f, vous allez remarquer certains problèmes avec les textures sur les surfaces fortement courbées. Si vous définissez un écart plus grand, la plupart des problèmes vont disparaître.

    Sélectionnez
                    0.1f,                // Écart sur les contours

    Les trois prochaines lignes restent les mêmes.

    Sélectionnez
                    0.2f,                // Épaisseur de la police dans l'axe des Z
                WGL_FONT_POLYGONS,        // Utilisation de polygones, et non de lignes
                gmf);                // Adresse du tampon où l'on doit recevoir les données
}

    Juste avant ReSizeGLScene() nous allons ajouter la section de code suivante, qui permet de charger notre texture. Vous devez reconnaître ce code venant des tutoriels précédents. Nous créons un espace de stockage pour l'image bitmap. Nous chargeons l'image. Nous disons à OpenGL de générer une texture, puis nous stockons la texture dans texture[0].

    Je crée une texture MIP map parce que cela est plus beau. Le nom de la texture est lights.bmp.

    Sélectionnez
    AUX_RGBImageRec *LoadBMP(char *Filename)            // Charge une image bitmap
{
    FILE *File=NULL;                    // Identifiant de fichier

    if (!Filename)                        // Regarde si un nom de fichier a été donné
    {
        return NULL;                    // Si non, retourne NULL
    }

    File=fopen(Filename,"r");                // Vérifie si le fichier existe

    if (File)                        // Est-ce que le fichier existe ?
    {
        fclose(File);                    // Ferme l'identifiant
        return auxDIBImageLoad(Filename);        // Charge le bitmap et retourne un pointeur
    }

    return NULL;                        // Si échec, retourne NULL
}

int LoadGLTextures()                        // Charge des fichiers bitmap et convertit les textures
{
    int Status=FALSE;                    // Indicateur de statut

    AUX_RGBImageRec * TextureImage[1];            // Crée un espace de stockage pour les textures

    memset(TextureImage,0,sizeof(void *)*1);        // Définit le pointeur à NULL

    if (TextureImage[0]=LoadBMP("Data/Lights.bmp"))        // Charge le bitmap
    {
        Status=TRUE;                    // Définit le statut à TRUE

        glGenTextures(1, &texture[0]);            // Crée la texture

        // Build Linear Mipmapped Texture
        glBindTexture(GL_TEXTURE_2D, texture[0]);
        gluBuild2DMipmaps(GL_TEXTURE_2D, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);

    Les quatre lignes suivantes de code vont générer les coordonnées de texture automatiquement pour n'importe quel objet dessiné sur l'écran. La commande glTexGen() est extrêmement puissante, et complexe, et pour comprendre toutes les formules mathématiques impliquées il faudrait un autre tutoriel. Tout ce que vous devez savoir est que GL_S et GL_T sont les coordonnées de texture. Par défaut elles vont être définies pour prendre les valeurs de la position courante sur X et Y de l'écran et produire un vertex de texture. Vous pouvez remarquer que les objets ne sont pas texturés sur l'axe des Z… seulement des rayures vont apparaître. Les faces avant et arrière sont bien texturées, et c'est tout ce qui compte. X (GL_S) va couvrir l'application de la texture de gauche à droite et Y (GL_T) va couvrir l'application de la texture de haut en bas.

    GL_TEXTURE_GEN_MODE nous permet de sélectionner le mode d'application de la texture que nous voulons utiliser sur les coordonnées S et T de la texture. Nous avons trois choix :

    • GL_EYE_LINEAR - La texture est attachée à l'écran. Elle ne bouge jamais. La texture est appliquée sur l'objet avec le morceau de la texture sur lequel il est au-dessus ;
    • GL_OBJECT_LINEAR - Ce mode est celui que l'on utilise. La texture est attachée à l'objet qui se déplace sur l'écran ; GL_SPHERE_MAP - le favori de tous. Crée un objet avec une réflexion métallique.

    Il est important de noter que j'omets beaucoup de lignes de code. Nous pouvions aussi bien définir le GL_OBJECT_PLANE, mais par défaut il est défini avec les paramètres que l'on désire. Achetez un bon livre, si vous voulez en savoir plus, ou alors regardez l'aide MSDN cd / dvd.

    Sélectionnez
            // Application des textures sur les contours de l'objet
        glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
        // Application des textures sur les contours de l'objet
        glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
        glEnable(GL_TEXTURE_GEN_S);            // Génération automatique des textures
        glEnable(GL_TEXTURE_GEN_T);            // Génération automatique des textures
    }

    if (TextureImage[0])                    // Si la texture existe
    {
        if (TextureImage[0]->data)            // Si l'image de la texture existe
        {
            free(TextureImage[0]->data);        // Libération de la mémoire pour l'image de la texture
        }

        free(TextureImage[0]);                // Libération de la mémoire pour la structure de l'image
    }

    return Status;                        // Renvoie le statut
}

    Il y a quelques nouvelles lignes à la fin du code de InitGL(). BuildFont() qui a été déplacé en dessous du code de chargement de la texture. La ligne glEnable(GL_COLOR_MATERIAL) a été retirée. Si vous prévoyez d'appliquer des couleurs à la texture en utilisant glColor3f(r,g,b) remettez la ligne glEnable(GL_COLOR_MATERIAL) dans le code.

    Sélectionnez
    int InitGL(GLvoid)                        // L'initialisation de OpenGL va ici
{
    if (!LoadGLTextures())                    // Va à la fonction de chargement de la texture
    {
        return FALSE;                    // Si la texture n'a pas été chargée, retourne FALSE
    }
    BuildFont();                        // Construit la police

    glShadeModel(GL_SMOOTH);                // Activation de l'ombre douce
    glClearColor(0.0f, 0.0f, 0.0f, 0.5f);            // Fond noir
    glClearDepth(1.0f);                    // Mise en place du tampon de profondeur
    glEnable(GL_DEPTH_TEST);                // Activation du test de profondeur
    glDepthFunc(GL_LEQUAL);                    // Le type de test de profondeur à faire
    glEnable(GL_LIGHT0);                    // Activation de la lampe 0 (rapide et sale)
    glEnable(GL_LIGHTING);                    // Activation de l'éclairage
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);    // Calcul de jolies perspectives

    Activez le placage de texture 2D, et sélectionnez la première texture. Cela va plaquer la texture sur n'importe quel objet 3D dessiné à l'écran. Si vous voulez plus de contrôle, vous pouvez activer et désactiver le placage de texture vous-même.

    Sélectionnez
        glEnable(GL_TEXTURE_2D);                // Activation du placage de texture
    glBindTexture(GL_TEXTURE_2D, texture[0]);        // Sélectionne la texture
    return TRUE;                        // L'initialisation est OK
}

    Le code de redimensionnement n'a pas changé, mais celui de DrawGLScene, oui.

    Sélectionnez
    int DrawGLScene(GLvoid)                        // Ici l'endroit où nous effectuons le dessin
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    // Nettoyage de l'écran et du tampon de profondeur
    glLoadIdentity();                    // Réinitialisation de la vue

    Ici notre premier changement. À la place de garder un objet au milieu de l'écran, nous allons tourner autour en utilisant COS et SIN (aucune surprise). Nous allons nous déplacer de 3 unités dans l'écran (-3.0f). Sur l'axe des X, nous allons nous décaler de -1.1, tout à gauche, jusqu'à 1.1, tout à droite. Nous utiliserons la variable rot pour contrôler le décalage de gauche à droite. Nous nous décalerons de 0.8, tout en haut, à -0.8, tout en bas. Nous utiliserons aussi la variable rot pour ce décalage (vous pouvez aussi bien utiliser vos propres variables).

    Sélectionnez
        // Positionne le texte
    glTranslatef(1.1f*float(cos(rot/16.0f)),0.8f*float(sin(rot/20.0f)),-3.0f);

    Maintenant nous faisons la rotation des normales. Ceci va provoquer la rotation du symbole sur les axes X, Y et Z.

    Sélectionnez
        glRotatef(rot,1.0f,0.0f,0.0f);                // Tourne sur l'axe des X
    glRotatef(rot*1.2f,0.0f,1.0f,0.0f);            // Tourne sur l'axe des Y
    glRotatef(rot*1.4f,0.0f,0.0f,1.0f);            // Tourne sur l'axe des Z

    Nous nous déplaçons un petit peu sur la gauche et sur le bas ainsi que vers la caméra pour centrer le symbole sur chaque axe. Sinon lorsque l'objet tourne, nous n'avons pas l'impression qu'il tourne autour de son propre centre. -0.35f est juste un nombre qui marche. J'ai dû jouer avec les valeurs pendant un petit moment parce que je ne suis pas sûr de la largeur de la police, qui peut aussi varier avec chaque police. Je ne suis pas sûr de savoir pourquoi les polices ne sont pas construites autour du point central.

    Sélectionnez
        glTranslatef(-0.35f,-0.35f,0.1f);            // Centre sur l'axe des X, Y et Z

    Finalement nous dessinons notre symbole de tête de mort puis nous augmentons la variable rot pour que notre symbole tourne et se déplace sur l'écran. Si vous n'arrivez pas à comprendre comment je peux obtenir une tête de mort en partant de la lettre 'N', faites ceci : démarrez Microsoft Word ou Wordpad. Allez dans le menu des polices. Sélectionnez la police Wingdings. Tapez un 'N' majuscule. Une tête de mort devrait apparaître.

    Sélectionnez
        glPrint("N");                        // Dessine le symbole de tête de mort
    rot+=0.1f;                        // Augmente la variable de rotation
    return TRUE;                        // Continue
}

    La dernière chose à faire est d'ajouter KillFont() à la fin de KillGLWindow() tout comme je le montre ci-dessous. C'est important d'ajouter cette ligne. Cela nettoie les choses avant de quitter notre programme.

    Sélectionnez
        if (!UnregisterClass("OpenGL",hInstance))        // Pouvons-nous désenregistrer la classe
    {
        MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
        hInstance=NULL;                    // Définit hInstance à NULL
    }

    KillFont();                        // Détruit la police
}

    Bien que je n'aie jamais été dans les détails, vous devez avoir une bonne compréhension sur le comment obtenir qu'OpenGL génère les coordonnées de texture pour vous. Vous ne devrez avoir aucun problème pour appliquer des textures sur vos polices par vous-même, ou même n'importe quel autre objet. Et seulement en changeant deux lignes de code, vous pouvez activer le plaquage de texture sur une sphère, ce qui est vraiment un effet cool.

    Jeff Molofee (NeHe)

    III. Téléchargements

    Compte tenu du nombre de versions de codes sources pour les tutoriels NeHe, nous les laissons en anglais. En principe, si vous avez compris le code présenté dans ce tutoriel (et les tutoriels antérieurs), vous n'aurez pas de mal à les comprendre :

    • Borland C++ Builder 6 (Conversion by Christian Kindahl) ;
    • Code Warrior 5.3 (Conversion by Scott Lupton) ;
    • Delphi (Conversion by Michal Tucek) ;
    • Dev C++ (Conversion by Dan) ;
    • Euphoria (Conversion by Evan Marshall) ;
    • GLut (Conversion by David Phillip Oster) ;
    • Java (Conversion by Jeff Kirby) ;
    • LCC Win32 (Conversion by Robert Wishlaw) ;
    • Mac OS (Conversion by David Phillip Oster) ;
    • MASM (Conversion by Greg Helps) ;
    • Pelles C (Conversion by Pelle Orinius) ;
    • Visual Basic (Conversion by Ross Dawson) ;
    • Visual C++ ;
    • Visual Studio .NET (Conversion by Grant James).


Effet de brouillard réaliste

  • Effet de brouillard réaliste

    • I. Contributions
    • II. Tutoriel
    • III. Initialisation des données
    • IV. Initialisation de DrawGLScene
    • V. Évènements clavier
    • VI. Téléchargements
    • VII. Remerciements
    • VIII. Liens

    I. Contributions

    Nous recherchons toujours des traducteurs pour finir ce projet de traduction des articles de NeHe. Votre aide serait appréciée ! Vous êtes intéressé ? Alors contactez-nous à l'adresse suivante : nehe@redaction-developpez.com.

    II. Tutoriel

    Vous voulez donc ajouter du brouillard à votre programme OpenGL. Bien, dans ce tutoriel, je vais vous montrer comment le faire. C'est la première fois que j'écris un tutoriel, et, je suis relativement nouveau à la programmation OpenGL / C++, donc, s'il vous plaît, si vous trouvez quelque chose de faux, faites-le-moi savoir et ne me sautez pas dessus. Ce code est basé sur le code de la leçon 7.

    III. Initialisation des données

    Nous allons commencer par définir toutes les variables nécessaires pour définir le brouillard. La variable fogMode va être utilisée pour contenir les trois types de brouillard : GL_EXP, GL_EXP2 et GL_LINEAR. Je vais vous expliquer les différences entre ces trois variables plus tard. Les variables sont placées au début du code après la ligne GLuint texture[3]. La variable fogfilter va être utilisée pour garder une trace du type de brouillard que nous utilisons. La variable fogColor va contenir la couleur que nous souhaitons avoir pour le brouillard. J'ai aussi ajouté une variable booléenne gp, en haut du code, pour savoir si la touche 'g' a été pressée.

    Sélectionnez
    bool   gp;                        // G Pressée ? (AJOUT)
GLuint filter;                        // Quel filtre utiliser
GLuint fogMode[]= { GL_EXP, GL_EXP2, GL_LINEAR };    // Stockage pour trois types de brouillard
GLuint fogfilter= 0;                    // Quel brouillard utiliser
GLfloat fogColor[4]= {0.5f, 0.5f, 0.5f, 1.0f};        // Couleur du brouillard

    IV. Initialisation de DrawGLScene

    Maintenant que nous avons établi nos variables, nous pouvons passer à InitGL. La ligne glClearColor() a été modifiée pour effacer l'écran avec la même couleur que celle du brouillard afin d'avoir un meilleur effet. Il n'y a pas beaucoup de code pour faire fonctionner le brouillard. Vous allez trouver le code très simpliste par rapport aux autres que vous pourrez trouver.

    Sélectionnez
    glClearColor(0.5f,0.5f,0.5f,1.0f);            // Nous allons effacer l'écran avec la couleur du brouillard (MODIFIÉ)

glEnable(GL_FOG);                    // Active GL_FOG
glFogi(GL_FOG_MODE, fogMode[fogfilter]);        // Mode du brouillard
glFogfv(GL_FOG_COLOR, fogColor);            // Définit la couleur du brouillard
glFogf(GL_FOG_DENSITY, 0.35f);                // Définit la densité du brouillard
glHint(GL_FOG_HINT, GL_DONT_CARE);            // Définit l'indice du brouillard
glFogf(GL_FOG_START, 1.0f);                // Définit la distance où commence le brouillard
glFogf(GL_FOG_END, 5.0f);                // Définit la distance où s'arrête le brouillard

    Prenons d'abord les trois premières lignes. La première ligne glEnable(GL_FOG); est explicite. Cela active le brouillard.

    La deuxième ligne, glFogi(GL_FOG_MODE, fogMode[fogfilter]); définit le mode de filtrage du brouillard. Plus tôt nous avons déclaré le tableau fogMode. Il contient GL_EXP, GL_EXP2 et GL_LINEAR. C'est ici où les variables rentrent en jeu. Laissez-moi vous expliquer chacune d'elles :

    * GL_EXP - rendu basique du brouillard, qui cache tout sur l'écran. Cela ne donne pas vraiment un effet de brouillard, mais fait le travail sur les vieux PC ;

    * GL_EXP2 - c'est la prochaine étape après GL_EXP. Cela va cacher tout sur l'écran, bien que cela va donner plus de profondeur à la scène ;

    * GL_LINEAR - c'est le meilleur rendu de brouillard. Les objets vont entrer et sortir du brouillard beaucoup mieux.

    La troisième ligne, glFogfv(GL_FOG_COLOR, fogcolor); définit la couleur du brouillard. Plus tôt, nous avions défini cela à (0.5f,0.5f,0.5f,1.0f) dans la variable fogcolor, donnant une jolie couleur grise.

    Regardez ensuite les quatre prochaines lignes de code. La ligne glFogf(GL_FOG_DENSITY, 0.35f); définit la densité du brouillard. Augmentez ce nombre et le brouillard deviendra plus dense, réduirez-le et il deviendra moins dense.

    La ligne glHint(GL_FOG_HINT, GL_DONT_CARE); définit l'indice. J'ai utilisé GL_DONT_CARE, parce que la valeur de l'indice m'importe peu.

    Eric Desrosiers ajoute : petite explication sur glHint(GL_FOG_HINT, hintval);

    hintval peut être : GL_DONT_CARE, GL_NICEST ou GL_FASTEST

    • GL_DONT_CARE - laisse OpenGL choisir la nature du brouillard (par vertex ou par pixel) et une formule inconnue ;
    • GL_NICEST - donne un brouillard par pixel (donne un bon effet) ;
    • GL_FASTEST - donne un brouillard par vertex (plus rapide, mais moins beau).

    La prochaine ligne glFogf(GL_FOG_START, 1.0f); va définir la distance à laquelle le brouillard devra commencer. Vous pouvez changer ce nombre par ce que vous voulez, dépendant de l'endroit où vous voulez commencer le brouillard. La prochaine ligne est similaire, glFogf(GL_FOG_END, 5.0f);. Ceci indique au programme OpenGL la distance à laquelle le brouillard devra s'arrêter.

    V. Évènements clavier

    Maintenant que nous avons défini le code du dessin du brouillard, nous allons ajouter la commande clavier pour changer les modes de brouillard. Le code sera situé à la fin du programme avec tout le reste du code de gestion des touches.

    Sélectionnez
    if (keys['G'] && !gp)                    // La touche G est-elle appuyée ?
{
    gp=TRUE;                    // gp est mis à TRUE
    fogfilter+=1;                    // Augmente fogfilter de un
    if (fogfilter>2)                // Si fogfilter est plus grand que 2
    {
        fogfilter=0;                // On redéfinit fogfilter à zéro
    }
    glFogi (GL_FOG_MODE, fogMode[fogfilter]);    // Mode de brouillard
}
if (!keys['G'])                        // La touche G a-t-elle été relâchée ?
{
    gp=FALSE;                    // gp est mis à FALSE
}

    Voilà ! C'est fait ! Vous avez maintenant du brouillard dans votre programme OpenGL. Je voudrais dire que c'était sans peine…

    Christopher Aliotta (Precursor)

    Jeff Molofee (NeHe)

    VI. Téléchargements

    Compte tenu du nombre de versions de codes sources pour les tutoriels NeHe, nous les laissons en anglais. En principe, si vous avez compris le code présenté dans ce tutoriel (et les tutoriels antérieurs), vous n'aurez pas de mal à les comprendre :

    • Borland C++ Builder 6 (Conversion by Christian Kindahl) ;
    • C# (Conversion by Brian Holley) ;
    • Code Warrior 5.3 (Conversion by Scott Lupton) ;
    • Cygwin (Conversion by Stephan Ferraro) ;
    • Delphi (Conversion by Michal Tucek) ;
    • Dev C++ (Conversion by Dan) ;
    • Euphoria (Conversion by Evan Marshall) ;
    • Game GLUT (Conversion by Milikas Anastasios) ;
    • Irix/GLUT (Conversion by Rob Fletcher) ;
    • Java (Conversion by Jeff Kirby) ;
    • JoGL (Conversion by Nicholas Campbell) ;
    • LCC Win32 (Conversion by Robert Wishlaw) ;
    • Linux (Conversion by Daniel Davis) ;
    • Linux/GLX (Conversion by Mihael Vrbanec) ;
    • Linux/SDL (Conversion by Ti Leggett) ;
    • LWJGL (Conversion by Mark Bernard) ;
    • Mac OS (Conversion by Anthony Parker) ;
    • Mac OS X/Cocoa (Conversion by Bryan Blackburn) ;
    • MASM (Conversion by Greg Helps) ;
    • Visual C++/OpenIL (Conversion by Denton Woods) ;
    • Pelles C (Conversion by Pelle Orinius) ;
    • SDL (Conversion by Ken Rockot) ;
    • Visual Basic (Conversion by Ross Dawson) ;
    • Visual Basic (Conversion by Peter De Tagyos) ;
    • Visual C++ ;
    • Visual Studio .NET (Conversion by Grant James).


Texture 2D de police

  • I. Contributions
  • II. Tutoriel
  • III. Téléchargements
  • IV. Remerciements
  • V. Liens

Ce tutoriel vous est présenté par NeHe & Giuseppe D'Agata…

I. Contributions

Nous recherchons toujours des traducteurs pour finir ce projet de traduction des articles de NeHe. Votre aide serait appréciée ! Vous êtes intéressé ? Alors contactez-nous à l'adresse suivante : nehe@redaction-developpez.com.

II. Tutoriel

Je sais que tout le monde en a marre des polices. Les tutoriels que j'ai faits jusqu'à présent n'affichent pas seulement du texte, ils affichent du texte en 3D, du texte avec des textures, et peuvent accepter des variables. Mais qu'est-ce qui se passe si vous portez votre code sur une machine qui ne supporte pas les Bitmaps ou les polices vectorielles ?

Grâce à Giuseppe D'Agata, nous avons un autre tutoriel sur les polices. Vous vous demandez ce qui pourrait manquer !? Si vous vous rappelez dans le premier tutoriel sur les polices, j'ai mentionné l'utilisation de textures pour dessiner les lettres à l'écran. Habituellement, lorsque vous utilisez des textures pour dessiner du texte à l'écran vous utilisez votre logiciel de dessin préféré, vous sélectionnez une police puis vous tapez les lettres que vous voulez afficher. Vous sauvegardez le Bitmap et le chargez dans votre programme comme texture. Ce n'est pas très efficace pour un programme qui requiert beaucoup de texte, ou du texte qui change continuellement !

Ce programme utilise seulement une texture pour afficher n'importe lequel des 256 caractères à l'écran. Gardez à l'esprit que les caractères font 16 pixels de large et environ 16 de hauteur. Si vous prenez votre texture standard de 256x256, il est facile de voir que vous pouvez mettre 16 lettres sur une ligne, et que vous avez un total de 16 lignes. Si vous avez besoin d'une explication plus détaillée : la texture a une largeur de 256 pixels, un caractère une largeur de 16 pixels. 256 divisé par 16 = 16. :)

Donc… Créons une démo d'une texture de police 2D. Ce programme utilise le code de la leçon 1. Dans la première section du programme, nous incluons les bibliothèques math et stdio. Nous avons besoin de la bibliothèque math pour bouger les lettres dans l'écran en utilisant SIN et COS et de la bibliothèque stdio pour être sûr que les Bitmaps que nous voulons utiliser existent avant d'essayer d'en faire des textures.

Sélectionnez
#include <windows.h>                                // Fichier d'entête pour Windows
#include <math.h>                                // Fichier d'entête pour la bibliothèque mathématique        (AJOUT)
#include <stdio.h>                                // Fichier d'entête pour pour les entrées/sorties    (AJOUT)
#include <gl\gl.h>                                // Fichier d'entête pour la bibliothèque OpenGL32
#include <gl\glu.h>                                // Fichier d'entête pour la bibliothèque GLu32
#include <gl\glaux.h>                                // Fichier d'entête pour la bibliothèque GLaux
HDC        hDC=NULL;                            // Contexte de périphérique privé GDI
HGLRC        hRC=NULL;                            // Contexte de rendu permanent
HWND        hWnd=NULL;                            // Contient notre ID de la fenêtre
HINSTANCE    hInstance;                            // Contient l'instance de l'application
bool    keys[256];                                // Tableau utilisé pour la fonction du clavier
bool    active=TRUE;                                // Drapeau de fenêtre active définie à TRUE par défaut
bool    fullscreen=TRUE;                            // Drapeau de plein écran défini en plein écran par défaut

Nous allons ajouter une variable appelée base pour pointer sur nos listes d'affichage. Nous ajouterons aussi texture[2] pour contenir les deux textures que nous allons créer. La texture 1 sera la texture de la police, et la texture 2 va être une texture de relief pour créer notre objet simple 3D.

Nous ajoutons une variable loop qui va être utilisée pour exécuter les boucles. Finalement nous ajoutons cnt1 et cnt2 qui seront utilisées pour déplacer le texte sur l'écran et faire tourner notre objet simple 3D.

Sélectionnez
GLuint    base;                                    // Base pour la liste d'affichage de la police
GLuint    texture[2];                                // Stockage pour les textures de notre police
GLuint    loop;                                    // Variables génériques pour les boucles
GLfloat    cnt1;                                    // Premier compteur utilisé pour déplacer le texte et pour la coloration
GLfloat    cnt2;                                    // Second compteur utilisé pour déplacer le texte et pour la coloration
LRESULT    CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);                // Déclaration de WndProc

Maintenant le code du chargement des textures. C'est exactement le même que celui de la leçon sur le placage de texture (leçon 6).

Sélectionnez
AUX_RGBImageRec *LoadBMP(char *Filename)                    // Charge une image Bitmap
{
    FILE *File=NULL;                            // Descripteur de fichier
    if (!Filename)                                // Vérifie qu'un nom de fichier a été donné
    {
        return NULL;                            // Sinon, retourne NULL
    }
    File=fopen(Filename,"r");                        // Contrôle si le fichier existe
    if (File)                                // Est-ce que le fichier existe ?
    {
        fclose(File);                            // Ferme le descripteur
        return auxDIBImageLoad(Filename);                // Ouvre le Bitmap et renvoie un pointeur
    }
    return NULL;                                // Retourne NULL si le chargement a échoué
}

Le code qui suit a aussi très peu changé par rapport au code des tutoriels précédents. Si vous n'êtes pas sûr de ce que fait chaque ligne, revenez en arrière et révisez.

Notez que TextureImage[ ] va contenir deux images RGB. C'est très important de vérifier deux fois le code qui gère le chargement et le stockage des textures. Un mauvais nombre pourrait provoquer des crashs ou des fuites de mémoire !

Sélectionnez
int LoadGLTextures()                                // Ouvre un Bitmap et le convertit en texture
{
    int Status=FALSE;                            // Indicateur du statut
    AUX_RGBImageRec *TextureImage[2];                    // Crée un espace pour les textures

La ligne suivante est la plus importante à voir. Si vous vouliez remplacer le 2 par n'importe quel autre nombre, de gros problèmes surviendraient. La double vérification ! Ce nombre doit correspondre au nombre que vous utilisez lorsque vous définissez TextureImages[ ].

Les deux textures que nous allons charger sont font.bmp (notre police), et bumps.bmp. La seconde texture peut être remplacée par n'importe quelle texture de votre choix. Je ne me sentais pas très créatif, donc la texture que j'ai décidé d'utiliser peut être un peu terne.

Sélectionnez
    memset(TextureImage,0,sizeof(void *)*2);                // Initialise les pointeurs à NULL
    if ((TextureImage[0]=LoadBMP("Data/Font.bmp")) &&            // Charge le Bitmap de la police
        (TextureImage[1]=LoadBMP("Data/Bumps.bmp")))            // Charge le Bitmap de la texture
    {
        Status=TRUE;                            // Met la variable Status à TRUE

Autre chose d'important à vérifier à deux fois. Je ne peux pas dire combien de courriels j'ai reçus venant de personnes demandant "Pourquoi je ne vois qu'une seule texture, ou pourquoi toutes mes textures sont blanches !?!". Généralement le problème vient de cette ligne. Si vous remplacez le 2 par un 1, seule une texture sera créée et la seconde texture apparaîtra toute blanche. Si vous remplacez le 2 par un 3, votre programme peut planter !

Vous devriez avoir un seul appel à glGenTextures(). Après glGenTextures() vous devriez générer toutes vos textures. J'ai vu des gens mettre une ligne glGenTextures() pour chaque texture qu'ils voulaient créer. Généralement cela peut faire que la nouvelle texture écrasera les textures que vous avez déjà créées. C'est une bonne idée de décider du nombre de textures que vous avez besoin de créer, d'appeler glGenTextures() une seule fois, et de construire toutes les textures. Il n'est pas sage de mettre glGenTextures() dans une boucle si vous n'avez aucune raison pour cela.

Sélectionnez
        glGenTextures(2, &texture[0]);                    // Créer deux textures

        for (loop=0; loop<2; loop++)                    // Boucle pour toutes les textures
        {
            // Construction de toutes les textures
            glBindTexture(GL_TEXTURE_2D, texture[loop]);
            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
            glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[loop]->sizeX, TextureImage[loop]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);
        }
    }

Les lignes suivantes vérifient si le fichier Bitmap que nous avons chargé pour construire notre texture utilise la mémoire RAM. Si c'est le cas, la mémoire est libérée. Remarquez que nous vérifions et libérons les deux images enregistrées. Si nous utilisions trois images différentes pour construire nos textures, nous devrions vérifier et libérer trois images.

Sélectionnez
    for (loop=0; loop<2; loop++)
        {
            if (TextureImage[loop])                        // Si la texture existe
            {
                if (TextureImage[loop]->data)             // Si l'image de la texture existe
                {
                    free(TextureImage[loop]->data);        // Libère l'image de la texture
                }
                free(TextureImage[loop]);            // Libère la structure de l'image
        }
    return Status;                                // Renvoie le statut
}

Maintenant nous allons créer notre police. Je vais parcourir cette section de code en détail. Ce n'est pas vraiment complexe, mais il y a un peu de mathématiques à comprendre, et je sais que les maths ne sont pas une chose que tout le monde apprécie.

Sélectionnez
GLvoid BuildFont(GLvoid)                            // Construit la liste d'affichage de la police
{

Les deux prochaines variables vont être utilisées pour contenir la position de chaque lettre dans la texture de la police. cx contiendra la position de gauche à droite dans la texture, et cy contiendra la position de haut en bas.

Sélectionnez
    float    cx;                                // Contient la coordonnée du caractère sur les X
    float    cy;                                // Contient la coordonnée du caractère sur les Y

Après nous disons à OpenGL que nous voulons construire 256 listes d'affichage. La variable base va pointer sur l'emplacement de la première liste d'affichage. La seconde liste d'affichage sera base+1, la troisième base+2, etc.

La seconde ligne de code ci-dessous sélectionne la texture de la police (texture[0]).

Sélectionnez
    base=glGenLists(256);                            // Crée les 256 listes d'affichage
    glBindTexture(GL_TEXTURE_2D, texture[0]);                // Sélectionne la texture de la police

Maintenant nous commençons la boucle. La boucle va créer les 256 caractères, sauvegardant chaque caractère dans sa propre liste d'affichage.

Sélectionnez
    for (loop=0; loop<256; loop++)                        // Boucle à travers les 256 listes d'affichage
    {

La première ligne ci-dessous peut sembler un peu déroutante. Le symbole % signifie le reste après la division de loop par 16. cx va nous déplacer à travers la texture de la police de gauche à droite. Vous remarquerez plus tard dans le code, que nous enlevons cy à 1 pour nous déplacer de haut en bas au lieu de bas en haut. Le symbole % est assez dur à expliquer mais je vais essayer.

Tout ce qui nous concerne est ce (loop%16). Le /16.0f convertit juste le résultat en coordonnées de texture. Donc si loop était égal à 16… cx serait égal au reste de 16/16 qui est 0. Mais cy serait égal à 16/16 qui est 1. Donc nous descendrions de la hauteur d'un caractère, mais pas du tout sur la droite. Maintenant si loop était égal à 17, cx serait égal à 17/16 ce qui fait 1.0625. La reste .0625 est aussi égal à 1/16. Signifiant que nous nous déplacerions d'un caractère sur la droite. cy sera toujours égal à 1 parce que nous ne nous intéressons qu'au nombre à gauche du symbole décimal. 18/16 nous donnerait 2 sur 16, nous déplaçant de deux caractères sur la droite, et toujours un caractère vers le bas. Si loop était 32, cx serait encore une fois égal à 0, parce qu'il n'y a pas de reste à la division de 32 par 16, mais cy serait égal à 2. Parce le nombre sur la gauche de la décimale serait maintenant 2, nous déplaçant de deux caractères vers le bas sur notre texture. Est-ce que c'est clair ?

Sélectionnez
        cx=float(loop%16)/16.0f;                    // Position du caractère sur X
        cy=float(loop/16)/16.0f;                    // Position du caractère sur Y

Ouf. :) OK. Donc maintenant nous construisons notre police 2D en sélectionnant un caractère sur la texture selon les valeurs de cx et cy. Dans la ligne ci-dessous nous ajoutons la valeur de loop à la valeur de base, si nous ne le faisions pas, chaque lettre serait construite dans la première liste d'affichage. Nous ne voulons certainement pas que cela arrive donc en ajoutant loop à base, chaque caractère que nous créons est sauvegardé dans la liste d'affichage suivante.

Sélectionnez
        glNewList(base+loop,GL_COMPILE);                // Début de la création de la liste

Maintenant que nous avons sélectionné la liste d'affichage que nous voulons construire, nous créons notre caractère. Ceci est réalisé en dessinant un carré, texturé avec un caractère provenant de la texture de la police.

Sélectionnez
            glBegin(GL_QUADS);                    // Utilisation d'un carré pour chaque caractère

cx et cy devraient contenir des petites valeurs décimales de 0.0f à 1.0f. Si cx et cy étaient égaux à 0, la première ligne de code ci-dessous serait alors : glTexCoord2f(0.0f, 1-0.0f - 0.0625f). Rappelez-vous que 0.0625 est exactement 1/16 de notre texture, ou la largeur/hauteur d'un de nos caractères. La coordonnée de texture serait le point en bas à gauche de notre texture.

Remarquez que nous utilisons glVertex2i(x,y) à la place de glVertex3f(x,y,z). Notre police est une police 2D, donc nous n'avons pas besoin d'une valeur pour z. Parce que nous utilisons un écran orthogonal, nous n'avons pas besoin de nous déplacer dans l'écran. Tout ce que vous avez à faire pour dessiner dans un écran orthogonal est de spécifier les coordonnées x et y. Parce que notre écran est en pixels de 0 à 639 et de 0 à 479, nous n'avons pas besoin de nombres flottants ni de nombres négatifs. :)

De la façon dont nous définissons l'écran orthogonal, (0,0) est situé en bas à gauche de l'écran. (640,480) va être en haut à droite de l'écran. 0 est le bord gauche de l'écran sur l'axe des X, 639 le bord droit de l'écran. 0 est le bord bas de l'écran sur l'axe des Y et 479 le bord haut de l'écran. Normalement, nous avons enlevé les coordonnées négatives. C'est aussi pratique pour les gens qui ne s'intéressent pas aux perspectives et qui préfèrent travailler avec des pixels plutôt qu'avec des unités. :)

Sélectionnez
                glTexCoord2f(cx,1-cy-0.0625f);            // Coordonnées de texture (bas gauche)
                glVertex2i(0,0);                // Coordonnées du vecteur (bas gauche)

La prochaine coordonnée de texture est à 1/16 sur la droite de la coordonnée précédente (exactement la largeur d'un caractère). Donc cela va être le point en bas à droite de la texture.

Sélectionnez
                glTexCoord2f(cx+0.0625f,1-cy-0.0625f);        // Coordonnées de texture (bas droit)
                glVertex2i(16,0);                // Coordonnées du vecteur (bas droit)

La troisième coordonnée de texture reste sur le côté droit de notre caractère, mais se déplace vers le haut d'un 1/16 sur notre texture (exactement la hauteur d'un caractère). Cela va être le point en haut à droite de notre caractère.

Sélectionnez
                glTexCoord2f(cx+0.0625f,1-cy);            // Coordonnées de texture (haut droit)
                glVertex2i(16,16);                // Coordonnées du vecteur (haut droit)

Finalement, nous nous déplaçons sur la gauche pour notre dernière coordonnée, en haut à gauche de notre caractère.

Sélectionnez
                glTexCoord2f(cx,1-cy);                // Coordonnées de texture (haut gauche)
                glVertex2i(0,16);                // Coordonnées du vecteur (haut gauche)
            glEnd();                        // Fin de la construction du carré (caractère)

Finalement, nous nous déplaçons de dix pixels sur la droite, nous plaçant à la droite de notre texture. Si nous ne nous déplacions pas, les lettres seraient dessinées les unes sur les autres. Parce que notre police est si étroite, nous ne voulons pas nous déplacer de 16 pixels sur la droite. Si nous le faisions, il y aurait de grands espaces entre chaque lettre. Se déplacer de seulement 10 pixels élimine ces espaces.

Sélectionnez
            glTranslated(10,0,0);                    // Se déplace sur la droite du caractère
        glEndList();                            // Fin de la construction de la liste d'affichage
    }                                    // Boucle jusqu'à la fin de la construction des 256 listes
}

La partie suivante de code est la même que dans les autres tutoriels sur les polices, libérant les listes d'affichage avant la fin du programme. Toutes les 256 listes d'affichage en commençant à base vont être détruites. (Bonne chose à faire !)

Sélectionnez
GLvoid KillFont(GLvoid)                                // Détruit la police de la mémoire
{
    glDeleteLists(base,256);                        // Détruit toutes les 256 listes d'affichage
}

La prochaine section de code est là où tout notre affichage est réalisé. Pratiquement tout est nouveau donc je vais essayer d'expliquer chaque ligne en détail. Juste une petite note : beaucoup de choses peuvent être ajoutées à ce code, comme le support des variables, la taille des caractères, l'espacement, et aussi nombre de vérifications pour restaurer les paramètres dans leurs états initial, d'avant l'affichage.

glPrint() prend trois paramètres. Le premier est la position sur l'axe des X sur l'écran (la position de gauche à droite). Le suivant est la position sur l'axe des Y sur l'écran (de haut en bas… où 0 est le bas). Puis nous avons notre chaîne de caractères (le texte que nous voulons afficher), et finalement une variable appelée 'set'. Si vous regardez le Bitmap que Giuseppe D'Agata a fait, vous remarquerez qu'il y a deux séries de caractères. La première série contient les caractères normaux, et la deuxième les caractères en italique. Si set est à 0, la première série de caractères est sélectionnée. Si set est à 1 ou plus grand, la seconde série de caractères est sélectionnée.

Sélectionnez
GLvoid glPrint(GLint x, GLint y, char *string, int set)                // Où l'affichage se passe
{

La première chose que nous avons à faire est de s'assurer que set est à 0 ou 1. Si set est plus grand que 1, nous le mettrons à 1.

Sélectionnez
    if (set>1)                                // set plus grand que 1
    {
        set=1;                                // Alors, nous le mettons à 1
    }

Maintenant nous sélectionnons notre texture de la police. Nous le faisons juste dans le cas où une autre texture aurait été sélectionnée avant d'appeler la fonction d'affichage de texte.

Sélectionnez
    glBindTexture(GL_TEXTURE_2D, texture[0]);                // Sélectionne notre police de la texture

Maintenant, nous désactivons le test de profondeur. La raison pour laquelle je fais ceci est pour avoir le fondu (blending) fonctionnant correctement. Si vous ne désactivez pas le test de profondeur, le texte peut être caché par quelque chose, ou le fondu ne sera pas correct. Si vous ne prévoyez pas de fondu pour le texte (donc que les espaces noirs ne soient pas visibles autour des lettres) vous pouvez laisser le test de profondeur activé.

Sélectionnez
    glDisable(GL_DEPTH_TEST);                        // Désactive le test de profondeur

Les prochaines lignes sont très importantes ! Nous sélectionnons notre matrice de projection. Juste après cela, nous utilisons la commande appelée glPushMatrix. glPushMatrix() sauvegarde la matrice courante (projection). Un peu comme le bouton de mémoire d'une calculatrice.

Sélectionnez
    glMatrixMode(GL_PROJECTION);                        // Sélectionne la matrice de projection
    glPushMatrix();                                // Sauvegarde la matrice de projection

Maintenant que notre matrice de projection a été sauvegardée, nous réinitialisons la matrice et nous définissons notre écran orthogonal. Le premier et le troisième nombres (0) représentent le coin en bas à gauche de l'écran. Nous pouvions mettre le côté gauche à -640 si nous le voulions, mais pourquoi vouloir travailler avec des nombres négatifs si nous n'en avons pas besoin ? Le second et quatrième paramètres représentent le haut droit de l'écran. Il est sage de faire correspondre ces valeurs avec la résolution que vous utilisez. Il n'y a pas de profondeur donc nous mettons les valeurs de Z à -1 et 1.

Sélectionnez
    glLoadIdentity();                            // Réinitialise la matrice de projection
    glOrtho(0,640,0,480,-1,1);                        // Met en place l'écran orthogonal

Maintenant nous sélectionnons la matrice modelview, et nous sauvegardons les paramètres courants avec glPushMatrix(). Nous réinitialisons alors la matrice modelview pour pouvoir travailler avec en utilisant la vue orthogonale.

Sélectionnez
    glMatrixMode(GL_MODELVIEW);                        // Sélectionne la matrice modelview
    glPushMatrix();                                // Sauvegarde la matrice modelview
    glLoadIdentity();                            // Réinitialise la matrice modelview

Avec nos paramètres de perspective sauvegardés et notre écran orthogonal défini, nous pouvons maintenant dessiner notre texte. Nous commençons par nous déplacer à la position à laquelle nous voulons dessiner notre texte sur l'écran. Nous utilisons glTranslated() à la place de glTranslatef() parce que nous travaillons avec les pixels, donc les valeurs décimales ne sont pas importantes. Après tout, vous ne pouvez pas avoir la moitié d'un pixel. :)

Sélectionnez
    glTranslated(x,y,0);                            // Positionne le texte (0,0 - bas gauche)

La ligne ci-dessous va sélectionner la série de la police à utiliser. Si nous voulions la seconde série , nous ajouterions 128 à la base de la liste d'affichage (128 est la moitié de 256 caractères). En ajoutant 128 nous sautons les 128 premiers caractères.

Sélectionnez
    glListBase(base-32+(128*set));                        // Sélectionne la série de la police (0 ou 1)

Maintenant, tout ce qui nous reste à faire est l'affichage des lettres à l'écran. Nous le faisons de la même façon que ce que nous avions fait dans les autres tutoriels sur les polices. Nous utilisons glCallLists(). strlen(string) est la longueur de notre chaîne de caractères (combien de caractères nous voulons dessiner), GL_UNSIGNED_BYTE signifie que chaque caractère est représenté par un unsigned byte (un octet peut être n'importe quelle valeur entre 0 et 255. Finalement, string contient le texte actuel à afficher à l'écran.

Sélectionnez
    glCallLists(strlen(string),GL_UNSIGNED_BYTE,string);            // Écrit le texte à l'écran

Tout ce qui reste à faire est de remettre la vue en mode perspective. Nous sélectionnons la matrice de projection et utilisons glPopMatrix() pour rappeler les paramètres qui ont été sauvegardés avec glPushMatrix(). C'est important de restaurer les choses dans l'ordre inverse de la sauvegarde.

Sélectionnez
    glMatrixMode(GL_PROJECTION);                        // Sélectionne la matrice de projection
    glPopMatrix();                                // Restaure l'ancienne projection de matrice

Maintenant nous sélectionnons la matrice modelview, et nous faisons la même chose. Nous utilisons glPopMatrix() pour restaurer la matrice modelview aux valeurs qui étaient avant que nous définissions l'affichage orthogonal.

Sélectionnez
    glMatrixMode(GL_MODELVIEW);                        // Sélectionne la matrice modelview
    glPopMatrix();                                // Restaure l'ancienne matrice de projection

Finalement, nous activons le test de profondeur. Si vous ne l'avez pas désactivé précédemment, vous n'avez pas besoin de cette ligne.

Sélectionnez
    glEnable(GL_DEPTH_TEST);                        // Active le test de profondeur
}

Rien n'a changé dans ReSizeGLScene() donc nous passons directement à InitGL().

Sélectionnez
int InitGL(GLvoid)                                // Toute la configuration d'OpenGL se met ici
{

Nous passons à notre code de construction des textures. Si la construction échoue pour une raison quelconque, nous retournons FALSE. Cela permet à notre programme de savoir qu'une erreur est arrivée et se fermer proprement.

Sélectionnez
    if (!LoadGLTextures())                            // Va dans notre routine de chargement de texture
    {
        return FALSE;                            // Si la la texture n'a pas été chargée, retourne FALSE
    }

S'il n'y a pas eu d'erreur, nous passons au code de construction de la police. Peu de choses peuvent mal se passer lorsque nous construisons notre police donc nous ne vérifions pas les erreurs.

Sélectionnez
    BuildFont();                                // Construction de la police

Maintenant faisons notre initialisation classique d'OpenGL. Nous définissons la réinitialisation de la couleur du fond à noir, la réinitialisation du tampon de profondeur à 1.0. Nous choisissons le mode de test de profondeur, ainsi que le mode de fondu. Nous activons les ombres douces, et finalement nous activons l'application de textures 2D.

Sélectionnez
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);                    // Fond d'écran noir
    glClearDepth(1.0);                            // Configuration du tampon de profondeur
    glDepthFunc(GL_LEQUAL);                            // Le type de test de profondeur à faire
    glClearDepth(1.0f);                            // Configuration du tampon de profondeur
    glBlendFunc(GL_SRC_ALPHA,GL_ONE);                    // Sélectionne le type de fondu
    glShadeModel(GL_SMOOTH);                        // Active les ombres douces
    glEnable(GL_TEXTURE_2D);                        // Active le mappage de texture 2D
    return TRUE;                                // Initialisation OK
}

Le code de la section ci-dessous va créer notre scène. Nous dessinons les objets 3D en premier puis le texte pour qu'il apparaisse au-dessus des objets 3D, à la place d'avoir les objets 3D sur le texte. La raison pour laquelle je décide d'ajouter des objets 3D est pour vous montrer que la perspective et la vue orthogonale peuvent être utilisées en même temps.

Sélectionnez
int DrawGLScene(GLvoid)                                // Ici  nous faisons tous les dessins
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);            // Efface l'écran et le tampon de profondeur
    glLoadIdentity();                            // Réinitialise la matrice modelview

Nous sélectionnons notre texture bumps.bmp pour construire notre petit objet 3D. Nous nous déplaçons de cinq unités dans l'écran pour pouvoir voir l'objet 3D. Nous le tournons selon l'axe des Z de 45 degrés. Cela va faire tourner notre carré de 45 degrés, dans le sens des aiguilles d'une montre et faire que le carré ressemblera plus à un diamant qu'à un carré.

Sélectionnez
    glBindTexture(GL_TEXTURE_2D, texture[1]);                // Sélectionne notre deuxième texture
    glTranslatef(0.0f,0.0f,-5.0f);                        // Déplace de cinq unités dans l'écran
    glRotatef(45.0f,0.0f,0.0f,1.0f);                    // Tourne sur l'axe des Z de 45 degrés (sens des aiguilles d'une montre)

Après avoir effectué la rotation de 45 degrés, nous tournons l'objet selon l'axe des X et des Y de 30 fois la variable cnt1. Cela fait que notre objet tourne comme un diamant tournant sur un point.

Sélectionnez
    glRotatef(cnt1*30.0f,1.0f,1.0f,0.0f);                    // Tourne sur l'axe des X et Y de cnt1 * 30 (gauche à droite)

Nous désactivons le fondu (nous voulons que l'objet 3D apparaisse solide), et nous définissons la couleur à blanc. Puis nous dessinons un simple carré avec une texture.

Sélectionnez
    glDisable(GL_BLEND);                            // Désactive le fondu avant de dessiner en 3D
    glColor3f(1.0f,1.0f,1.0f);                        // Blanc
    glBegin(GL_QUADS);                            // Dessine notre premier rectangle avec une texture
        glTexCoord2d(0.0f,0.0f);                    // Premières coordonnées de texture
        glVertex2f(-1.0f, 1.0f);                    // Premier vertex
        glTexCoord2d(1.0f,0.0f);                    // Secondes coordonnées de texture
        glVertex2f( 1.0f, 1.0f);                    // Second vertex
        glTexCoord2d(1.0f,1.0f);                    // Troisièmes coordonnées de texture
        glVertex2f( 1.0f,-1.0f);                    // Troisième vertex
        glTexCoord2d(0.0f,1.0f);                    // Quatrièmes coordonnées de texture
        glVertex2f(-1.0f,-1.0f);                    // Quatrième vertex
    glEnd();                                // Fin du dessin du premier rectangle

Tout de suite après avoir dessiné le premier carré, nous effectuons une rotation de 90 degrés sur les axes X et Y. Puis nous dessinons un nouveau carré. Le second carré va passer au travers du milieu du premier, créant une jolie forme.

Sélectionnez
    glRotatef(90.0f,1.0f,1.0f,0.0f);                    // Rotation sur les axes X et Y de 90 degrés (gauche à droite)
    glBegin(GL_QUADS);                            // Dessin du second carré texturé
        glTexCoord2d(0.0f,0.0f);                    // Premières coordonnées de texture
        glVertex2f(-1.0f, 1.0f);                    // Premier vertex
        glTexCoord2d(1.0f,0.0f);                    // Secondes coordonnées de texture
        glVertex2f( 1.0f, 1.0f);                    // Second vertex
        glTexCoord2d(1.0f,1.0f);                    // Troisièmes coordonnées de texture
        glVertex2f( 1.0f,-1.0f);                    // Troisième vertex
        glTexCoord2d(0.0f,1.0f);                    // Quatrièmes coordonnées de texture
        glVertex2f(-1.0f,-1.0f);                    // Quatrième vertex
    glEnd();                                // Fin du dessin du premier carré

Après le dessin des deux carrés texturés, nous activons le fondu, et nous dessinons le texte.

Sélectionnez
    glEnable(GL_BLEND);                            // Activation du fondu
    glLoadIdentity();                            // Réinitialisation de la vue

Nous utilisons la même coloration fantaisiste que dans les autres tutoriels. La couleur est changée progressivement en suivant la position du mouvement du texte sur l'écran.

Sélectionnez
    // Coloration basée sur la position du texte
    glColor3f(1.0f*float(cos(cnt1)),1.0f*float(sin(cnt2)),1.0f-0.5f*float(cos(cnt1+cnt2)));

Ensuite nous dessinons notre texte. Nous utilisons toujours glPrint(). Le premier paramètre est la position sur les X. Le second paramètre la position sur les Y. Le troisième paramètre '"NeHe") est le texte à écrire à l'écran, et le dernier paramètre est la série de caractères à utiliser (0 - normal, 1 - italique).

Comme vous l'avez certainement deviné, nous déplaçons le texte sur l'écran en utilisant COS et SIN, suivant les deux compteurs cnt1 et cnt2. Si vous ne comprenez pas ce que SIN et COS font, revenez en arrière et lisez les tutoriels précédents.

Sélectionnez
    glPrint(int((280+250*cos(cnt1))),int(235+200*sin(cnt2)),"NeHe",0);    // Affiche le texte OpenGL à l'écran
    glColor3f(1.0f*float(sin(cnt2)),1.0f-0.5f*float(cos(cnt1+cnt2)),1.0f*float(cos(cnt1)));
    glPrint(int((280+230*cos(cnt2))),int(235+200*sin(cnt1)),"OpenGL",1);    // Affiche le texte OpenGL à l'écran

Nous définissons la couleur à bleu foncé et écrivons le nom de l'auteur en bas de l'écran. Nous allons écrire son nom sur l'écran une deuxième fois en utilisant des lettres blanches. Les lettres blanches sont un peu décalées sur la droite par rapport aux lettres bleues. Cela crée un effet d'ombre. (Si le fondu n'était pas désactivé, l'effet ne fonctionnerait pas.)

Sélectionnez
    glColor3f(0.0f,0.0f,1.0f);                        // Définit la couleur à bleu
    glPrint(int(240+200*cos((cnt2+cnt1)/5)),2,"Giuseppe D'Agata",0);    // Affiche le texte à l'écran

    glColor3f(1.0f,1.0f,1.0f);                        // Définit la couleur à blanc
    glPrint(int(242+200*cos((cnt2+cnt1)/5)),2,"Giuseppe D'Agata",0);    // Affiche le texte à l'écran

La dernière chose à faire est d'augmenter les deux compteurs à différentes vitesses. Cela fait se déplacer le texte, et tourner l'objet 3D.

Sélectionnez
    cnt1+=0.01f;                                // Augmente le premier compteur
    cnt2+=0.0081f;                                // Augmente le second compteur
    return TRUE;                                // Tout s'est bien passé
}

Le code dans KillGLWindow(), CreateGLWindow() et WndProc() n'a pas changé donc nous le passons.

Sélectionnez
int WINAPI WinMain(    HINSTANCE    hInstance,                // Instance
            HINSTANCE    hPrevInstance,                // Instance précédente
            LPSTR        lpCmdLine,                // Paramètres de la ligne de commande
            int        nCmdShow)                // L'état d'affichage de la fenêtre
{
    MSG        msg;                            // Structure d'un message Windows
    BOOL    done=FALSE;                            // Variable pour gérer la boucle infinie
    // Demander à l'utilisateur quel mode d'exécution (fenêtre ou plein écran) il préfère
    if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
    {
        fullscreen=FALSE;                        // Mode fenêtré
    }

Le titre de la fenêtre a changé.

Sélectionnez
    // Créer notre fenêtre OpenGL
    if (!CreateGLWindow("NeHe & Giuseppe D'Agata's 2D Font Tutorial",640,480,16,fullscreen))
    {
        return 0;                            // Quitter si la fenêtre n'a pas été créée
    }
    while(!done)                                // Boucle qui s'exécute tant que done=TRUE
    {
        if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))            // Y a-t-il un message en attente ?
        {
            if (msg.message==WM_QUIT)                // Avons-nous reçu un message pour quitter ?
            {
                done=TRUE;                    // Si c'est le cas, on met done a TRUE
            }
            else                            // Sinon on gère les messages
            {
                TranslateMessage(&msg);                // Traduire le message
                DispatchMessage(&msg);                // Envoyer le message
            }
        }
        else                                // S'il n'y a pas de message
        {
            // Dessine la scène. Vérifie si Echap a été appuyée
            if ((active && !DrawGLScene()) || keys[VK_ESCAPE])    // Active? Y avait-il un message reçu pour quitter ?
            {
                done=TRUE;                    // Échap ou DrawGLScene ont signalé qu'il faut quitter
            }
            else                            // On ne quitte pas, mise à jour de l'écran
            {
                SwapBuffers(hDC);                // Échange des buffers (Double Buffering)
            }
        }
    }
    // Fermeture

La dernière chose à faire est d'ajouter KillFont() à la fin de KillGLWindow() tout comme je le montre ci-dessous. C'est important d'ajouter cette ligne. Cela nettoie les choses avant de sortir de notre programme.

Sélectionnez
    if (!UnregisterClass("OpenGL",hInstance))                // Peut-on désenregistrer la classe
    {
        MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
        hInstance=NULL;                            // Définit hInstance à NULL
    }
    KillFont();                                // Détruit la police
}

Je pense que je peux officiellement dire que mon site enseigne toutes les possibilités pour afficher du texte à l'écran {sourire}. Globalement, je pense que c'est un assez bon tutoriel. Le code peut être utilisé sur n'importe quel ordinateur qui peut exécuter OpenGL, il est facile à utiliser et écrire du texte à l'écran en utilisant cette méthode nécessite très peu de puissance du processeur.

J'aimerais remercier Giuseppe D'Agata pour la version originale du tutoriel. Je l'ai grandement modifié, converti au nouveau code de base, mais sans son code, je ne l'aurais probablement pas écrit. Sa version a quelques options supplémentaires, comme l'espacement des caractères, etc., mais je l'ai remplacé par un superbe objet 3D {sourire}.

J'espère que tout le monde va apprécier ce tutoriel. Si vous avez des questions, envoyez un courriel à Giuseppe D'Agata ou à moi-même.

Giuseppe D'Agata

Jeff Molofee (NeHe)


Quadriques

  • I. Contributions
  • II. Tutoriel
  • III. Téléchargements
  • IV. Remerciements
  • V. Liens

Les Quadriques sont une manière de représenter des objets composés complexes qui nécessiteraient autrement beaucoup de calculs trigonométriques et d'itérations successives.

I. Contributions

Nous recherchons toujours des traducteurs pour finir ce projet de traduction des articles de NeHe. Votre aide serait appréciée ! Vous êtes intéressé ? Alors contactez-nous à l'adresse suivante : nehe@redaction-developpez.com.

II. Tutoriel

Nous réutilisons le code de la leçon 7. Nous y adjoindrons sept variables et modifierons la texture pour faire dans la diversité. :)

Sélectionnez
#include <windows.h>                 // Fichier d'entête pour Windows
#include <stdio.h>                     // Fichier d'entête pour les entrées/sorties
#include <gl\gl.h>                     // Fichier d'entête pour la bibliothèque OpenGL32
#include <gl\glu.h>                     // Fichier d'entête pour la bibliothèque GLu32
#include <gl\glaux.h>                 // Fichier d'entête pour la bibliothèque GLaux
HDC        hDC=NULL;                 // Contexte de périphérique privé GDI
HGLRC        hRC=NULL;                 // Contexte de rendu permanent
HWND        hWnd=NULL;                 // Contient notre ID de la fenêtre
HINSTANCE    hInstance;                 // Contient l'instance de l'application
bool    keys[256];                     // Tableau utilisé pour la fonction du clavier
bool    active=TRUE;                     // Drapeau de fenêtre active définie à TRUE par défaut
bool    fullscreen=TRUE;                 // Drapeau de plein écran défini à plein écran par défaut
bool    light;                         // Éclairage allumé/éteint
bool    lp;                         // Touche L appuyée ?
bool    fp;                         // Touche F appuyée ?
bool sp;                         // Barre d'espace appuyée ?    (AJOUT)
int    part1;                         // Début du disque    (AJOUT)
int    part2;                         // Fin du disque    (AJOUT)
int    p1=0;                         // Incrémentation 1        (AJOUT)
int    p2=1;                         // Incrémentation 2        (AJOUT)
GLfloat    xrot;                         // Rotation selon X
GLfloat    yrot;                         // Rotation selon Y
GLfloat xspeed;                         // Vitesse de rotation selon X
GLfloat yspeed;                         // Vitesse de rotation selon Y
GLfloat    z=-5.0f;                 // Profondeur dans l'écran
GLUquadricObj *quadratic;                 // Stockage pour nos objets quadriques (AJOUT)
GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f };     // Valeurs de lumière ambiante
GLfloat LightDiffuse[]=     { 1.0f, 1.0f, 1.0f, 1.0f };     // Valeurs de lumière diffuse
GLfloat LightPosition[]= { 0.0f, 0.0f, 2.0f, 1.0f };     // Position de la lumière
GLuint    filter;                         // Filtre à utiliser
GLuint    texture[3];                     // Espace de stockage des 3 textures
GLuint object=0;                     // Objet à dessiner    (AJOUT)
LRESULT    CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);     // Déclaration de WndProc

Ceci fait, allons jusqu'à la fonction InitGL(), nous allons ajouter trois variables pour initialiser notre quadrique. Ajoutez ces trois lignes après avoir activé light1, mais avant de retourner true. La première ligne du code initialise la quadrique et crée un pointeur sur la zone mémoire qui le contiendra. S'il ne peut pas être créé, et la zone est non allouée, le pointeur prend la valeur 0. La seconde ligne de code crée un lissage normal sur la quadrique, pour obtenir un bon éclairage. D'autres valeurs possibles sont GLU_NONE et GLU_FLAT. En dernier, activons-le mapping de texture sur notre quadrique. Le mapping de texture est le genre de chose qui ne fait jamais ce que vous aviez explicitement prévu en créant la texture comme vous avez pu le constater avec la texture de la caisse.

Sélectionnez
    quadratic=gluNewQuadric();             // Crée un pointeur sur l'objet quadrque (AJOUT)
    gluQuadricNormals(quadratic, GLU_SMOOTH);     // Crée les normales de lissage (AJOUT)
    gluQuadricTexture(quadratic, GL_TRUE);         // Crée les coordonnées de texture (AJOUT)

Maintenant, j'ai décidé de garder ce cube dans ce tutoriel pour que vous puissiez voir comment les textures sont dessinées sur l'objet quadratique. J'ai décidé de bouger le cube à l'intérieur de sa propre fonction donc lorsque nous écrirons la fonction de dessin, il apparaîtra plus clair. Tout le monde devrait reconnaître ce code. =P

Sélectionnez
GLvoid glDrawCube()                     // Dessine un cube
{
        glBegin(GL_QUADS);             // Débute le dessin des carrés
         // Face avant
        glNormal3f( 0.0f, 0.0f, 1.0f);         // Normale face avant
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);     // Bas gauche de la texture et du carré

        glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);     // Bas droit de la texture et du carré
        glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);     // Haut droit de la texture et du carré
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);     // Haut gauche de la texture et du carré
         // Face arrière
        glNormal3f( 0.0f, 0.0f,-1.0f);         // Normale face arrière
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);     // Bas droit de la texture et du carré
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);     // Haut droit de la texture et du carré
        glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);     // Haut gauche de la texture et du carré
        glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);     // Bas gauche de la texture et du carré
         // Face de dessus
        glNormal3f( 0.0f, 1.0f, 0.0f);         // Normale face de dessus
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);     // Haut gauche de la texture et du carré
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, 1.0f, 1.0f);     // Bas gauche de la texture et du carré
        glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, 1.0f, 1.0f);     // Bas droit de la texture et du carré
        glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);     // Haut droit de la texture et du carré
         // Face de dessous
        glNormal3f( 0.0f,-1.0f, 0.0f);         // Normale face de dessous
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);     // Haut droit de la texture et du carré
        glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);     // haut gauche de la texture et du carré
        glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);     // Haut gauche de la texture et du carré
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);     // Bas droit de la texture et du carré
         // Face droite
        glNormal3f( 1.0f, 0.0f, 0.0f);         // Normale face droite
        glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);     // Bas droit de la texture et du carré
        glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, -1.0f);     // Hait droit de la texture et du carré
        glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 1.0f);     // Haut gauche de la texture et du carré
        glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);     // Bas gauche de la texture et du carré
         // Face gauche
        glNormal3f(-1.0f, 0.0f, 0.0f);         // Normale face gauche
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);     // Bas gauche de la texture et du carré
        glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);     // Bas droit de la texture et du carré
        glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 1.0f);     // Haut droit de la texture et du carré
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, -1.0f);     // Haut gauche de la texture et du carré
    glEnd();                     // Dessin des carrés terminé
}

La suite concerne la fonction DrawGLScene, ici j'ai juste écrit une simple déclaration pour dessiner les différents objets. J'ai également utilisé une variable statique (une variable locale qui garde sa valeur entre chaque appel) pour un effet sympathique lors de la construction du disque partiel. Je réécrirai en entier la fonction DrawGLScene pour plus de clarté.

Vous remarquez que lorsque je parle des paramètres utilisés, je ne parle pas du premier paramètre (quadratique). Ce paramètre est utilisé pour tous les objets que nous dessinons à côté du cube, donc je l'ignore en parlant des paramètres.

Sélectionnez
int DrawGLScene(GLvoid)                         // C'est ici que nous dessinons tout
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);     // Efface l'écran et le tampon de profondeur
    glLoadIdentity();                     // Réinitialise la vue
    glTranslatef(0.0f,0.0f,z);                 // Effectue une translation selon l'axe des Z
    glRotatef(xrot,1.0f,0.0f,0.0f);                 // Effectue une rotation selon l'axe X
    glRotatef(yrot,0.0f,1.0f,0.0f);                 // Effectue une rotation selon l'axe des Y
    glBindTexture(GL_TEXTURE_2D, texture[filter]);         // Sélectionne une texture filtrée
     // Cette section de code est nouvelle (AJOUT)
    switch(object)                         // Vérifie l'objet à dessiner
    {
    case 0:                             // On dessine l'objet 1
        glDrawCube();                     // On dessine notre cube
        break;                         // Terminé

Le second objet que nous créons sera un cylindre. Le premier paramètre (1.0f) est le rayon du cylindre à la base (bottom). Le second paramètre (1.0f) est le rayon du cylindre au sommet. Le troisième paramètre (3.0f) est la hauteur du cylindre (sa longueur). Le quatrième paramètre (32) est le nombre de subdivisions autour de l'axe Z, et finalement, le cinquième paramètre (32) est le nombre de subdivisions le long de l'axe des Z. Plus il y a de subdivisions, plus l'objet est détaillé. En augmentant le nombre de subdivisions, vous ajoutez des polygones à l'objet. Donc vous sacrifiez à la vitesse d'exécution pour avoir une meilleure qualité visuelle. La plupart du temps, on arrive à trouver un juste milieu satisfaisant.

Sélectionnez
    case 1:                             // On dessine l'objet 2
        glTranslatef(0.0f,0.0f,-1.5f);             // On centre le cylindre
        gluCylinder(quadratic,1.0f,1.0f,3.0f,32,32);     // On dessine notre cylindre
        break;                         // Terminé

Le troisième objet que nous créons sera inspiré d'un disque compact (CD). Le premier paramètre (0.5f) est le rayon intérieur du disque. Cette valeur peut être zéro, signifiant qu'il n'y aura pas de trou au milieu du disque. Plus grand sera ce rayon intérieur, plus grand sera le trou du milieu du disque. Le second paramètre (1.5f) est le rayon extérieur. Cette valeur devra être plus grande que celui du trou central. Si vous donnez à ce paramètre une valeur proche du rayon intérieur, votre CD ressemblera plus à un anneau. Si le rayon « extérieur » est beaucoup plus grand que le rayon « intérieur », ce sera plus un disque troué. Le troisième paramètre (32) est le nombre de tranches qui constituent le disque. Pensez à des tranches de pizza. C'est la même chose, plus le nombre de morceaux sera important, plus le disque sera lissé. Finalement le quatrième paramètre (32) est le nombre d'anneaux constituant le disque. Les anneaux sont similaires à des chansons sur un disque vinyle. Des cercles à l'intérieur des cercles. Ces anneaux subdivisent le disque depuis le rayon intérieur jusqu'au rayon extérieur, élevant le niveau de détail. Comme toujours, plus il y a de subdivisions, plus le programme sera lent.

Sélectionnez
    case 2:                             // On dessine l'objet 3
        gluDisk(quadratic,0.5f,1.5f,32,32);         // On dessine un disque (forme de CD)
        break;                         // Terminé

Notre quatrième objet est un objet difficile à représenter sur un écran, que je sais beaucoup d'entre vous meurent d'envie de savoir. La sphère ! C'est en réalité très simple. Le premier paramètre est le rayon de la sphère. Dans le cas où vous ne seriez pas habituer aux notions de rayon, diamètre, etc., le rayon est la distance depuis le centre de la sphère jusqu'à la surface de la sphère. Dans l'exemple, notre rayon est de 1.3f. Ensuite nous avons nos subdivisions autour de l'axe Z (32), et nos divisions le long de l'axe Z (32). De la même manière, plus vous avez de subdivisions, plus la sphère a un aspect lissé, mais toujours au détriment de la vitesse d'exécution du programme. Généralement, les sphères ont besoin de peu de subdivisions pour avoir un aspect lisse.

Sélectionnez
    case 3:                         // On dessine l'objet 4
        gluSphere(quadratic,1.3f,32,32);         // On dessine une sphère
        break;                         // Terminé

Notre cinquième objet créé utilise la même commande que celle du cylindre. Si vous vous rappelez bien, en créant le cylindre nous passions deux rayons de base, un pour la base supérieure et l'autre pour le socle. Pour faire un cône, il suffit qu'un des deux rayons passés en argument soit égal à zéro. Ceci va créer un point à une des deux extrémités et la figure résultante est un cône.

Sélectionnez
    case 4:                             // On dessine l'objet 5
        glTranslatef(0.0f,0.0f,-1.5f);             // On centre le cône
        gluCylinder(quadratic,1.0f,0.0f,3.0f,32,32);     // Un cône de rayon base 0.5 et hauteur 2
        break;                         // Terminé

Notre sixième objet est créé par gluPartialDisc(). L'objet que nous créons aura la même apparence que le disque que nous avons créé précédemment mais la commande gluPartialDisc() prend deux paramètres supplémentaires. Le cinquième paramètre (part1) est l'angle à partir duquel nous voulons dessiner le disque. Le sixième paramètre est l'angle de balayage, qui est l'arc parcouru depuis l'angle courant passé en cinquième paramètre. Nous allons incrémenter cet angle de balayage, ce qui fera se dessiner le disque dans le sens des aiguilles d'une montre. Une fois atteint 360 degrés, nous augmenterons l'angle de départ, faisant ainsi disparaître le disque, et ainsi de suite !

Sélectionnez
    case 5:                         // On dessine l'objet 6
        part1+=p1;                     // On augmente l'angle de départ
        part2+=p2;                     // On augmente l'angle de balayage
        if(part1>359)                     // 360 degrés
        {
            p1=0;                     // On arrête d'augmenter l'angle de départ
            part1=0;                 // On met l'angle de départ à zéro
            p2=1;                     // On commence à augmenter l'angle de balayage
            part2=0;                 // On met l'angle de balayage à zéro
        }
        if(part2>359)                     // 360 degrés
        {
            p1=1;                     // On commence à augmenter l'angle de départ
            p2=0;                     // On arrête d'augmenter l'angle de balayage
        }
        gluPartialDisk(quadratic,0.5f,1.5f,32,32,part1,part2-part1);     // Un disque comme le précédent
        break;                         // Terminé
    };
    xrot+=xspeed;                     // Augmente la rotation selon l'axe X
    yrot+=yspeed;                     // Augmente la rotation selon l'axe Y
    return TRUE;                         // Continuer
}

Dans la section de code de KillGLWindow(), nous avons besoin de supprimer la quadrique pour libérer des ressources systèmes. Ceci est réalisé par la commande gluDeleteQuadratic.

Sélectionnez
GLvoid KillGLWindow(GLvoid)                     //
{
    gluDeleteQuadric(quadratic);                 // Détruit la quadrique – libère les ressources

Maintenant pour la partie finale, nous entrons la clé. Ajoutez seulement ceci là où nous vérifions le reste des touches.

Sélectionnez
                if (keys[' '] && !sp)         // La barre d'espace est-elle enfoncée ?
                {
                    sp=TRUE;         // Si oui, mettre sp à TRUE
                    object++;         // Parcours des objets
                    if(object>5)         // objet est-il plus grand que 5 ?
                        object=0;     // Si oui, le remettre à zéro
                }
                if (!keys[' '])             // La barre d'espace a-t-elle été relâchée ?
                {
                    sp=FALSE;         // Si oui, mettre sp à FALSE
                }

C'est tout ! Maintenant vous pouvez dessiner des quadriques avec OpenGL. Quelques objets impressionnants peuvent être réalisés avec le morphing et les quadriques.

Le disque animé est un exemple simple de morphing.

À tout le monde, si vous avez le temps, visitez mon site web, TipTup.com 2000.

GB Schmick (TipTup)

Jeff Molofee (NeHe)

III. Téléchargements

Compte tenu du nombre de versions de codes sources pour les tutoriels NeHe, nous les laissons en anglais. En principe, si vous avez compris le code présenté dans ce tutoriel (et les tutoriels antérieurs), vous n'aurez pas de mal à les comprendre :

  • Borland C++ Builder 6 (Conversion by Christian Kindahl) ;
  • C# (Conversion by Brian Holley) ;
  • Code Warrior 5.3 (Conversion by Scott Lupton) ;
  • Cygwin (Conversion by Stephan Ferraro) ;
  • Delphi (Conversion by Michal Tucek) ;
  • Dev C++ (Conversion by Dan) ;
  • Euphoria (Conversion by Evan Marshall) ;
  • Game GLUT (Conversion by Milikas Anastasios) ;
  • Irix/GLUT (Conversion by Rob Fletcher) ;
  • Java (Conversion by Jeff Kirby) ;
  • JoGL (Conversion by Abdul Bezrati) ;
  • LCC Win32 (Conversion by Robert Wishlaw) ;
  • Linux (Conversion by Simon Werner) ;
  • Linux/GLX (Conversion by Mihael Vrbanec) ;
  • Linux/SDL (Conversion by Ti Leggett) ;
  • LWJGL (Conversion by Mark Bernard) ;
  • Mac OS (Conversion by Anthony Parker) ;
  • Mac OS X/Cocoa (Conversion by Bryan Blackburn) ;
  • MASM (Conversion by Christophe) ;
  • Visual C++ / OpenIL (Conversion by Denton Woods) ;
  • Pelles C (Conversion by Pelle Orinius) ;
  • SDL (Conversion by Ken Rockot) ;
  • Visual Basic (Conversion by The Gilb) ;
  • Visual C++ ;
  • Visual Studio .NET (Conversion by Grant James).


Moteur de particules utilisant les chaînes de triangles

  • I. Contributions
  • II. Tutoriel
  • III. Téléchargements
  • IV. Remerciements
  • V. Liens

Bienvenue dans le tutoriel 19. Vous avez appris beaucoup et maintenant vous voulez jouer. Je vais introduire une nouvelle commande dans ce tutoriel... Les chaînes de triangles. C'est très facile à utiliser et cela peut aider à accélérer vos programmes dessinant beaucoup de triangles.

Article lu -1 fois.

I. Contributions

Nous recherchons toujours des traducteurs pour finir ce projet de traduction des articles de NeHe. Votre aide serait appréciée ! Vous êtes intéressé ? Alors contactez-nous à l'adresse suivante : nehe@redaction-developpez.com.

II. Tutoriel

Dans ce tutoriel, je vais vous apprendre comment faire un système de particules semi-complexes. Une fois que vous aurez compris comment le système de particules fonctionne, créer des effets de feu, fumée, fontaines d'eau et plein d'autres sera très facile !

Toutefois, je dois vous prévenir ! Jusqu'à présent je n'ai jamais écrit de système de particules. J'ai eu l'idée que ce « fameux » système de particules était un code très complexe. J'ai fait plusieurs essais dans le passé, mais j'avais abandonné en réalisant que je ne pouvais pas contrôler tous les points sans devenir fou.

Vous pouvez ne pas me croire lorsque je vous dis ceci, mais le tutoriel est écrit sans aucune base. Je n'ai emprunté aucune idée, et je n'avais aucune information technique devant moi. J'ai commencé à penser aux particules et tout d'un coup ma tête s'est remplie d'idées (cerveau qui se mettait en marche ?). À la place de penser chaque particule comme étant un pixel devant aller du point« A » au point« B », et faire ceci ou cela, j'ai décidé qu'il serait mieux de penser chaque particule comme un objet individuel répondant à l'environnement autour de lui. J'ai donné une vie à chaque particule, un comportement aléatoire, une couleur, une vitesse, appliqué une gravité et plus encore.

Très vite, j'eus un projet terminé. Je levai les yeux vers l'horloge et réalisai que les « aliens » étaient venus me chercher une fois de plus. Quatre autres heures de passées ! Je me rappelle m'être arrêté de temps en temps pour boire du café et vaciller, mais quatre heures... ?

Donc, bien que le programme, d'après moi semble bien et marche comme je le souhaitais, il se peut que ce ne soit pas la bonne façon de faire un système de particules. Je m'en fiche, tant que cela fonctionne bien et que je peux l'utiliser dans mes projets ! Si vous êtes le genre de personne qui nécessite de savoir que vous êtes conforme, alors prenez quelques heures pour parcourir le net à la recherche d'informations. Soyez prévenu. Les quelques extraits de code que vous pourrez trouver pourront sembler énigmatiques.

Le tutoriel se base sur la leçon 1. Par contre, il y a plein de nouveau code. Donc je vais réécrire toutes les sections de code qui contiennent des changements (et faire que cela soit plus facile à comprendre).

En utilisant le code de la leçon 1, nous allons ajouter cinq nouvelles lignes au tout début du programme. La première ligne (stdio.h) nous permet de lire les données à partir de fichiers. C'est la même ligne que nous avions ajoutée dans les tutoriels précédents sur l'application des textures. La deuxième ligne définit le nombre de particules que nous allons créer et afficher à l'écran. Define, indique à notre programme que MAX_PARTICLES sera égal à la valeur que nous spécifions. Dans ce cas, 1000. La troisième ligne sera utilisée pour enclencher ou non le mode« arc-en-ciel ». Nous allons le définir activé par défaut. « qp » et« rp » sont des variables utilisées pour éviter la barre espace et la touche « Entrée » de se répéter lorsqu'elles sont maintenues enfoncées.

Sélectionnez
#include <windows.h>            // Fichier d'entête pour Windows
#include <stdio.h>            // Fichier d'entête pour les entrées/sorties     (AJOUT)
#include <gl\gl.h>            // Fichier d'entête pour la bibliothèque OpenGL32
#include <gl\glu.h>            // Fichier d'entête pour la bibliothèque GLu32
#include <gl\glaux.h>            // Fichier d'entête pour la bibliothèque GLaux

#define    MAX_PARTICLES    1000        // Nombre de particules à créer  (AJOUT)

HDC        hDC=NULL;        // Contexte de périphérique privé GDI
HGLRC        hRC=NULL;        // Contexte de rendu permanent
HWND        hWnd=NULL;        // Contient notre ID de la fenêtre
HINSTANCE    hInstance;        // Contient l'instance de l'application

bool    keys[256];            // Tableau utilisé pour la fonction du clavier
bool    active=TRUE;            // Drapeau de fenêtre active défini à TRUE par défaut
bool    fullscreen=TRUE;        // Drapeau de plein écran défini à plein écran par défaut
bool    rainbow=true;            // Mode arc-en-ciel ?  (AJOUT)
bool    sp;                // Barre espace pressée ?  (AJOUT)
bool    rp;                // Touche « Entrée » pressée ?  (AJOUT)

Les quatre prochaines lignes sont des variables diverses. La variable« slowdown » contrôle la vitesse du mouvement des particules. Plus le nombre est grand, plus les particules sont lentes. Plus le nombre est petit, plus les particules bougent vite. Si la valeur est définie trop petite, les particules bougeront trop vite ! La vitesse de déplacement des particules va affecter leurs mouvements sur l'écran. Des particules lentes n'iront pas aussi loin. Gardez ça à l'esprit.

Les variables « xspeed » et « yspeed » nous permettent de contrôler la direction de la queue. « xspeed » va être ajoutée à la vitesse courante de la particule sur l'axe des X. Si « xspeed » est positif notre particule va se déplacer plus sur la droite. Si « xspeed » est négatif, notre particule va se déplacer sur la gauche. Plus grande est la valeur, plus elle se déplace dans cette direction. « yspeed » fonctionne de la même façon, mais sur l'axe des Y. La raison pour laquelle je dis « PLUS » est spécifique dans une direction parce que d'autres facteurs affectent la direction du déplacement de nos particules. « xpseed » et « yspeed » nous aident à faire se déplacer les particules dans la direction souhaitée.

Finalement nous avons une variable « zoom ». Nous utilisons cette variable pour se déplacer dans ou en dehors de la scène. Avec les systèmes de particules, il est quelquefois agréable de pouvoir s'éloigner ou bien d'effectuer un zoom très proche.

Sélectionnez
float    slowdown=2.0f;            // Ralentissement des particules
float    xspeed;                // Vitesse sur l'axe des X  (pour permettre le contrôle de la direction de la queue par le clavier)
float    yspeed;                // Vitesse sur l'axe des Y  (pour permettre le contrôle de la direction de la queue par le clavier)
float    zoom=-40.0f;            // Utilisé pour le dézoomage

Maintenant nous définissons une variable appelée « loop ». Nous allons l'utiliser pour prédéfinir les particules et pour les dessiner à l'écran. « col » sera utilisée pour garder une trace de la couleur assignée aux particules. « delay » sera utilisée pour parcourir les couleurs pour le mode arc-en-ciel.

Finalement, nous définissons à par un espace pour une texture (la texture des particules). J'ai décidé d'utiliser une texture plutôt que les points OpenGL pour quelques raisons. La plus importante est parce que les points ne sont pas aussi rapides que ça, et qu'ils ne sont pas beaux. Deuxièmement, les textures sont beaucoup plus cool. :) Vous pouvez utiliser des particules carrées, une petite image de vous, une image d'une étoile, etc. Plein de possibilités !

Sélectionnez
GLuint    loop;                // Variable pour les diverses boucles
GLuint    col;                // Couleur courante
GLuint    delay;                // Délai de l'effet arc-en-ciel
GLuint    texture[1];            // Emplacement de stockage pour la texture des particules

Ok, maintenant la partie amusante. La prochaine partie de code crée une structure pour décrire une seule particule. C'est l'endroit où nous allons donner aux particules certaines caractéristiques.

Nous commençons par la variable booléenne « active ». Si la variable est TRUE, notre particule est vivante et bien portante. Si elle est FALSE notre particule est morte et nous l'avons éteinte ! Dans le programme, je n'utilise pas « active », mais c'est une variable pratique à inclure.

Les variables « life » et « fade » contrôlent le temps d'affichage d'une particule et sa brillance tant qu'elle est vivante. La variable « life » est graduellement décrémentée par la valeur gardée dans « fade ». Dans ce programme cela va faire que certaines particules vont brûler plus longtemps que d'autres.

Sélectionnez
typedef struct                        // Crée une structure pour une particule
{
    bool    active;                    // Active  (Oui / Non)
    float    life;                    // Vie de la particule
    float    fade;                    // Vitesse d'extinction

Les variables « r », « g » et « b » contiennent l'intensité rouge, l'intensité verte et l'intensité bleue de notre particule. Plus « r » est proche de 1.0f, plus la particule va être rouge. Mettre les 3 variables à 1.0f va créer une particule blanche.

Sélectionnez
    float    r;                    // Valeur du rouge
    float    g;                    // Valeur du vert
    float    b;                    // Valeur du bleu

Les variables « x », « y » et « z » contrôlent l'emplacement de la particule qui va être dessinée sur l'écran. « x » contient la position de notre particule sur l'axe des X. « y » contient la position de notre particule sur l'axe des Y, et finalement « z » contient la position de notre particule sur l'axe des Z.

Sélectionnez
    float    x;                    // Position en X
    float    y;                    // Position en Y
    float    z;                    // Position en Z

Les trois prochaines variables sont importantes. Ces trois variables contrôlent la vitesse de déplacement et la direction de la particule spécifiquement aux axes. Si « xi » est une valeur négative, notre particule se déplacera sur la gauche. Si elle est positive la particule se déplacera sur la droite. Si « yi » est une valeur négative, notre particule se déplacera vers le bas. Si elle est positive, la particule se déplacera vers le haut. Finalement, si « zi » est négative la particule se déplacera dans l'écran, si elle est positive, la particule se déplacera vers nous.

Sélectionnez
    float    xi;                    // Direction en X
    float    yi;                    // Direction en Y
    float    zi;                    // Direction en Z

Enfin, les trois dernières variables ! Chacune de ces variables peut être pensée comme de la gravité. Si « xg » est une valeur positive, notre particule va être tirée sur la droite. Si c'est une valeur négative notre particule va être tirée sur la gauche. Donc si notre particule se déplace sur la gauche (négative) et que nous appliquons une gravité positive, la vitesse va finir par ralentir à tel point que notre particule va commencer à se déplacer dans la direction opposée. « yg » tire vers le haut ou le bas et « zg » vers nous ou au loin de l'observateur.

Sélectionnez
    float    xg;                    // Gravité en X
    float    yg;                    // Gravité en Y
    float    zg;                    // Gravité en Z

« Particles » est le nom de notre structure.

Sélectionnez
}
particles;                        // Structure pour les particules

Maintenant nous créons un tableau appelé « particle ». Ce tableau va stocker MAX_PARTICLES. Dit en français : nous créons un emplacement de stockage pour 1000 (MAX_PARTICLES) particules. Cet espace va stocker les informations de chaque particule.

Sélectionnez
particles particle[MAX_PARTICLES];            // Tableau de particules (place pour les informations sur les particules)

Nous raccourcissons le code requis par le programme en stockant 12 couleurs différentes dans un tableau de couleurs. Pour chaque couleur de 0 à 11 nous stockons l'intensité en rouge, l'intensité en vert et finalement l'intensité en bleu. La table de couleur ci-dessous stocke 12 couleurs différentes de dégradé du rouge au violet.

Sélectionnez
static GLfloat colors[12][3]=                // Couleurs de l'arc-en-ciel
{
    {1.0f,0.5f,0.5f},{1.0f,0.75f,0.5f},{1.0f,1.0f,0.5f},{0.75f,1.0f,0.5f},
    {0.5f,1.0f,0.5f},{0.5f,1.0f,0.75f},{0.5f,1.0f,1.0f},{0.5f,0.75f,1.0f},
    {0.5f,0.5f,1.0f},{0.75f,0.5f,1.0f},{1.0f,0.5f,1.0f},{1.0f,0.5f,0.75f}
};

LRESULT    CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);    // Déclaration de WndProc

Notre code de chargement de Bitmap n'a pas changé.

Sélectionnez
AUX_RGBImageRec *LoadBMP(char *Filename)        // Charge une image Bitmap
{
    FILE *File=NULL;                // Descripteur de fichier
    if (!Filename)                    // Vérifie qu'un nom de fichier a été donné
    {
        return NULL;                // Si non, retourne NULL
    }

    File=fopen(Filename,"r");            // Contrôle si le fichier existe
    if (File)                    // Est-ce que le fichier existe ?
    {
        fclose(File);                // Ferme le descripteur
        return auxDIBImageLoad(Filename);    // Charge le Bitmap et renvoie un pointeur
    }
    return NULL;                    // Retourne NULL si le chargement a échoué
}

Cette section de code charge le fichier Bitmap (appelant le code ci-dessus) et le convertit en une texture. « Status » est utilisée pour garder une trace de si la texture a été créée et chargée.

Sélectionnez
int LoadGLTextures()                        // Ouvre un Bitmap et le convertit en texture
{
    int Status=FALSE;                    // Indicateur du statut

    AUX_RGBImageRec *TextureImage[1];            // Crée un espace de stockage pour la texture

    memset(TextureImage,0,sizeof(void *)*1);        // Initialise le pointeur à NULL

Notre code de chargement de texture va charger le Bitmap pour les particules et le convertir en une texture filtrée linéairement.

Sélectionnez
    if (TextureImage[0]=LoadBMP("Data/Particle.bmp"))    // Charge le Bitmap des particules
    {
        Status=TRUE;                    // Met la variable Status à TRUE
        glGenTextures(1, &texture[0]);            // Crée une texture

        glBindTexture(GL_TEXTURE_2D, texture[0]);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
        glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[0]->sizeX, TextureImage[0]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[0]->data);
    }

    if (TextureImage[0])                    // Si la texture existe
    {
        if (TextureImage[0]->data)            // Si l'image de la texture existe
        {
            free(TextureImage[0]->data);        // Libère l'image de la texture
        }
        free(TextureImage[0]);                // Libère la structure de l'image
    }
    return Status;                        // Renvoie le statut
}

Le seul changement que j'ai fait au code de redimensionnement est de mettre une distance de profondeur plus grande. À la place de 100.0f, nous pouvons voir les particules jusqu'à 200.0f unités dans l'écran.

Sélectionnez
GLvoid ReSizeGLScene(GLsizei width, GLsizei height)        // Redimensionne et initialise la fenêtre OpenGL
{
    if (height==0)                        // Évite une division par zéro
    {
        height=1;                    // La hauteur est de 1
    }

    glViewport(0, 0, width, height);            // Réinitialise la vue courante

    glMatrixMode(GL_PROJECTION);                // Sélectionne la matrice de projection
    glLoadIdentity();                    // Réinitialise la matrice de projection

    // Calcul du rapport largeur/hauteur de la fenêtre
    gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,200.0f);      (MODIFIÉ)

    glMatrixMode(GL_MODELVIEW);                // Sélectionne la matrice de modelview
    glLoadIdentity();                    // Réinitialise la matrice de modelview
}

Si vous utilisez le code de la leçon 1, remplacez-le par le code ci-dessous. J'ai ajouté le code pour charger notre texture et pour initialiser le fondu pour nos particules.

Sélectionnez
int InitGL(GLvoid)                                // Toute la configuration d'OpenGL se met ici
{
    if (!LoadGLTextures())                            // Va dans notre routine de chargement de texture
    {
        return FALSE;                            // Si la texture n'a pas été chargée, retourner FALSE
    }

Nous vérifions que les ombres douces sont activées, définissons la couleur du fond à noir, désactivons le test de profondeur et activons le fondu et le mappage des textures. Après avoir activé le mappage des textures nous sélectionnons notre texture pour les particules.

Sélectionnez
    glShadeModel(GL_SMOOTH);                              // Active les ombres douces
    glClearColor(0.0f, 0.0f, 0.0f, 0.5f);                    // Fond d'écran noir
    glClearDepth(1.0f);                                // Configuration du tampon de profondeur
    glDisable(GL_DEPTH_TEST);                        // Désactive le test de profondeur
    glEnable(GL_BLEND);                            // Active le fondu
    glBlendFunc(GL_SRC_ALPHA,GL_ONE);                    // Type du fondu à appliquer
    glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);            // Pour avoir des jolis calculs de perspective
    glHint(GL_POINT_SMOOTH_HINT,GL_NICEST);                    // Pour avoir des jolis points
    glEnable(GL_TEXTURE_2D);                        // Active l'application de texture
    glBindTexture(GL_TEXTURE_2D,texture[0]);                // Sélectionne notre texture

Le code ci-dessous va initialiser chacune des particules. Nous commençons par activer toutes les particules. Si une particule n'est pas active, elle n'apparaîtra pas à l'écran, peu importe combien de vie elle a.

Après avoir activé les particules, nous leur donnons la vie. Je doute que la façon dont j'applique la vie et teins les particules soit la meilleure, mais une fois de plus, cela marche bien ! 1.0f équivaut à la vie pleine. La particule est aussi à son maximum de luminosité.

Sélectionnez
    for (loop=0;loop<MAX_PARTICLES;loop++)                    // Initialise toutes les particules
    {
        particle[loop].active=true;                    // Active toutes les particules
        particle[loop].life=1.0f;                    // Remplit toutes jauges de vie des particules

Nous définissons la vitesse à laquelle la particule va s'assombrir en donnant à « fade » une valeur aléatoire. On va soustraire « fade » à la variable « life » à chaque fois que la particule va être dessinée. La valeur que nous allons avoir est une valeur aléatoire entre 0 et 99. Nous la divisons par 1000 pour avoir une très faible valeur décimale. Finalement nous ajoutons 0.003 pour que la vitesse d'assombrissement ne soit jamais à 0.

Sélectionnez
        particle[loop].fade=float(rand()%100)/1000.0f+0.003f;        // Vitesse d'assombrissement aléatoire.

Maintenant que notre particule est active et que nous lui avons donné vie, il est temps de lui donner une couleur. Pour l'effet initial, nous voulons que chaque particule ait une couleur différente. Ce que je fais c'est définir la couleur selon l'une des douze que nous avons défini dans la table au début de ce programme. Les mathématiques sont simples. Nous prenons notre variable « loop » et la multiplions par le nombre de couleurs de notre table puis la divisons par le maximum de particules (MAX_PARTICLES). Cela évite d'avoir la valeur finale plus grande que le nombre maximum de couleurs (12).

Quelques exemples rapides : 900*(12/900)=12. 1000*(12/1000)=12, etc.

Sélectionnez
        particle[loop].r=colors[loop*(12/MAX_PARTICLES)][0];        // Sélectionne la couleur rouge de l'arc-en-ciel
        particle[loop].g=colors[loop*(12/MAX_PARTICLES)][1];        // Sélectionne la couleur verte de l'arc-en-ciel
        particle[loop].b=colors[loop*(12/MAX_PARTICLES)][2];        // Sélectionne la couleur bleue de l'arc-en-ciel

Maintenant nous allons définir la direction pour que chaque particule se déplace selon une vitesse. Nous allons multiplier le résultat par 10.0f pour créer une impressionnante explosion lorsque le programme démarre.

Nous finirons avec une valeur aléatoire positive ou négative. Cette valeur va être utilisée pour déplacer les particules avec une direction aléatoire à une vitesse aléatoire.

Sélectionnez
        particle[loop].xi=float((rand()%50)-26.0f)*10.0f;        // Vitesse aléatoire sur l'axe des X
        particle[loop].yi=float((rand()%50)-25.0f)*10.0f;        // Vitesse aléatoire sur l'axe des Y
        particle[loop].zi=float((rand()%50)-25.0f)*10.0f;        // Vitesse aléatoire sur l'axe des Z

Finalement, nous définissons la gravité agissant sur chaque particule. Contrairement à l'habituelle gravité qui tire les choses vers le bas, notre gravité peut tirer vers le haut, bas, gauche, droit, en avant, en arrière. Pour commencer nous nous ne voulons pas une gravité trop forte tirant vers le bas. Pour ce faire nous définissons « xg » à 0.0f. Pas de mouvement sur l'axe des X. Nous mettons « yg » à -0.8f. Cela crée un tirage vers le bas. Si la valeur était positive cela tirerait vers le haut. Nous ne voulons pas de tirage vers l'avant ou l'arrière donc nous définissons « zg » à 0.0f.

Sélectionnez
        particle[loop].xg=0.0f;                        // Définit le tirage horizontal à 0
        particle[loop].yg=-0.8f;                    // Définit un tirage vertical vers le bas
        particle[loop].zg=0.0f;                        // Définit un tirage sur l'axe des Z à 0
    }
    return TRUE;                                // L'initialisation s'est bien passée
}

Maintenant la partie amusante. La prochaine section de code est l'endroit où nous dessinons les particules, vérifions la gravité, etc. C'est important que vous compreniez ce qu'il se passe, donc lisez attentivement. :)

Nous réinitialisons la matrice de modelview seulement une fois. Nous allons positionner les particules en utilisant la commande glVertex3f() au lieu d'utiliser les translations, ce qui fait que nous n'altérons pas la matrice de modelview pendant que l'on dessine les particules.

Sélectionnez
int DrawGLScene(GLvoid)                                // C'est ici que nous faisons tous les rendus
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);            // Vide l'écran et le tampon de profondeur
    glLoadIdentity();                            // Remet à zéro la matrice courante

Nous commençons par créer une boucle. Cette boucle va mettre à jour chacune de nos particules.

Sélectionnez
    for (loop=0;loop<MAX_PARTICLES;loop++)                    // Boucle au travers de toutes les particules
    {

La première chose, que nous faisons, est de vérifier si la particule est active. Si elle n'est pas active, elle ne sera pas mise à jour. Dans ce programme elles sont toutes actives, tout le temps. Mais dans votre propre programme, vous pouvez faire en sorte que certaines particules soient inactives.

Sélectionnez
        if (particle[loop].active)                    // Si la particule est active
        {

Les trois prochaines variables « x », « y » et « z » sont des variables temporaires que nous allons utiliser pour garder la position x, y, z. Remarquez que nous ajoutons le « zoom » à la position en z pour que notre scène soit déplacée dans l'écran en se basant sur la variable gardée par « zoom ». particle[loop].x contient la position en x pour n'importe quelle particule que nous dessinons (boucle des particules). particle[loop].y contient la position en y de la particule et particle[loop].z contient la position en z.

Sélectionnez
            float x=particle[loop].x;                // Prend la position de notre particule sur l'axe des X
            float y=particle[loop].y;                // Prend la position de notre particule sur l'axe des Y
            float z=particle[loop].z+zoom;                // Prend la position de notre particule sur l'axe des Z + Zoom

Maintenant que nous avons la position de la particule, nous pouvons la colorer. particle[loop].r contient l'intensité rouge de notre particule, particle[loop].g contient l'intensité verte et particle[loop].b contient l'intensité bleue. Remarquez que j'utilise la vie des particules comme valeur de l'alpha. Plus la particule se meurt, plus elle devient transparente, jusqu'à ce qu'elle n'existe plus. C'est pourquoi la vie des particules ne devrait jamais être plus grande que 1.0f. Si vous avez besoin de particules qui brûlent plus longtemps, essayez de réduire la vitesse d'extinction pour que les particules ne disparaissent pas aussi vite.

Sélectionnez
            // Dessine la particule en utilisant les valeurs RGB. Éteint la particule en se basant sur sa vie
            glColor4f(particle[loop].r,particle[loop].g,particle[loop].b,particle[loop].life);

Nous avons la position des particules et leur couleur définie. Tout ce qui nous reste à faire est de dessiner notre particule. Au lieu d'utiliser un rectangle texturé, j'ai décidé d'utiliser une chaîne de triangles texturés pour accélérer un peu le programme. La plupart des cartes 3D dessinent les triangles beaucoup plus rapidement que les quadrilatères. Certaines cartes 3D vont convertir le quadrilatère en 2 triangles pour vous, mais d'autres ne le feront pas. Donc nous allons faire le travail nous-même. Nous commençons par dire à OpenGL que nous voulons dessiner une chaîne de triangles.

Sélectionnez
            glBegin(GL_TRIANGLE_STRIP);                // Construit le carré à partir d'une chaine de triangles
Image non disponible

Citation venant directement du livre rouge (red book) d'OpenGL : « Une chaîne de triangles dessine une série de triangles (polygone à 3 cotés) en utilisant les vertex V0, V1, V2 puis V2, V1, V3 (regardez l'ordre), puis V2, V3, V4, etc. ». L'ordre permet de s'assurer que les triangles vont toujours être dessinés avec la même orientation donc que la chaîne peut correctement former une surface. Conserver l'orientation est importante pour la plupart des opérations comme le culling. Il faut au moins 3 points pour dessiner quelque chose.

Donc le premier triangle est dessiné en utilisant les vertex 0, 1 et 2. Si vous regardez l'image vous allez voir que les points 0, 1 et 2 construisent effectivement le premier triangle (haut droit, haut gauche, bas droit). Le deuxième triangle est dessiné en utilisant les vertex 2, 1 et 3. Encore une fois, si vous regardez l'image, les vertex 2, 1 et 3 créent le deuxième triangle (bas droit, haut gauche, bas gauche). Remarquez que les deux triangles sont dessinés dans le même sens (sans inverse des aiguilles d'une montre). J'ai vu quelques sites qui proclament que tous les deuxièmes triangles sont dans la direction opposée. Ce n'est pas le cas. OpenGL va réarranger les vertex pour être sûr que les triangles soient dans le même sens !

Il y a deux bonnes raisons d'utiliser les chaînes de triangles. Premièrement, après avoir spécifié les trois premiers vertex pour le premier triangle, vous n'avez plus qu'à spécifier un unique point pour chaque nouveau triangle. Ce point va être combiné avec les 2 vertex précédents pour créer un triangle. Deuxièmement, en réduisant les données nécessaires pour créer un triangle votre programme va aller plus vite, et le nombre de lignes de code, ou de données requises pour dessiner un objet est grandement réduit.

Note : le nombre de triangles que vous allez voir sur l'écran est le nombre de vertex que vous spécifiez moins 2. Dans le code ci-dessous nous avons quatre vertex et nous ne voyons que deux triangles.

Sélectionnez
                glTexCoord2d(1,1); glVertex3f(x+0.5f,y+0.5f,z); // Haut droit
                glTexCoord2d(0,1); glVertex3f(x-0.5f,y+0.5f,z); // Haut gauche
                glTexCoord2d(1,0); glVertex3f(x+0.5f,y-0.5f,z); // Bas droit
                glTexCoord2d(0,0); glVertex3f(x-0.5f,y-0.5f,z); // Bas gauche

Finalement nous indiquons à OpenGL que nous avons fini de dessiner notre chaîne de triangles.

Sélectionnez
            glEnd();                        // Fin de la construction de la chaîne de triangles

Maintenant nous pouvons déplacer les particules. Les mathématiques ci-dessous peuvent sembler étranges, mais une fois encore, c'est très simple. Premièrement, nous prenons la position en X de la particule courante. Puis nous ajoutons le mouvement, sur l'axe des X à la particule, divisé par le ralentissement multiplié par 1000. Donc si notre particule était au centre de l'écran sur l'axe des X (0), notre variable de mouvement (xi) pour l'axe des X était +10 (déplacement sur la droite) et le ralentissement (slowdown) égal à 1, nous nous déplacerions sur la droite de 10/(1*1000), ou 0.01f. Si nous augmentions le ralentissement à 2, nous nous déplacerions seulement de 0.005f. J'espère que cela vous aide à comprendre comment marche le ralentissement.

C'est aussi pourquoi multiplier la valeur de départ par 10.0f a fait que les pixels bougeaient beaucoup plus vite, créant une explosion.

Nous utilisons la même formule pour les axes Y et Z pour déplacer les particules sur l'écran.

Sélectionnez
            particle[loop].x+=particle[loop].xi/(slowdown*1000);    // Déplace la particule sur l'axe des X par la vitesse
            particle[loop].y+=particle[loop].yi/(slowdown*1000);    // Déplace la particule sur l'axe des Y par la vitesse
            particle[loop].z+=particle[loop].zi/(slowdown*1000);    // Déplace la particule sur l'axe des Z par la vitesse

Après avoir calculé le nouvel emplacement de la particule, nous devons ajouter la gravité ou résistance. Dans la première ligne ci-dessous, nous le faisons en ajoutant notre résistance (xg) à la vitesse de déplacement (xi).

Disons que notre vitesse de déplacement était 10 et que notre résistance était 1. Chaque fois que notre particule était dessinée la résistance aurait agi dessus. Donc la deuxième fois qu'on aurait dessiné, la résistance aurait agi, et la vitesse de déplacement serait passée de 10 à 9. Cela ralentissant un peu la particule. La troisième fois que la particule aurait été dessinée, la résistance aurait encore agi et notre vitesse de déplacement serait descendue à 8. Si la particule brûle pendant plus de 10 boucles, elle va certainement se déplacer dans la direction opposée, car la vitesse de déplacement sera devenue négative.

La résistance est appliquée sur la vitesse de déplacement sur les axes Y et Z de la même façon que pour l'axe des X.

Sélectionnez
            particle[loop].xi+=particle[loop].xg;            // Prend en compte le tirage sur l'axe des X
            particle[loop].yi+=particle[loop].yg;            // Prend en compte le tirage sur l'axe des Y
            particle[loop].zi+=particle[loop].zg;            // Prend en compte le tirage sur l'axe des Z

La prochaine ligne enlève de la vie à la particule. Si nous ne faisions pas ceci, la particule ne s'éteindrait jamais. Nous prenons la vie de la particule courante et nous lui soustrayons sa valeur d'extinction. Chaque particule va avoir une valeur d'extinction différente, donc elles vont brûler à différentes vitesses.

Sélectionnez
            particle[loop].life-=particle[loop].fade;        // Réduit la vie de la particule de la valeur de « fade »

Maintenant nous vérifions si la particule est encore vivante après lui avoir pris de la vie.

Sélectionnez
            if (particle[loop].life<0.0f)                    // Si la particule s'est éteinte
            {

Si la particule est morte (complètement brûlée), nous allons la ressusciter. Nous faisons cela en lui redonnant le plein de vie et une nouvelle vitesse d'extinction.

Sélectionnez
                particle[loop].life=1.0f;                // Donne une nouvelle vie
                particle[loop].fade=float(rand()%100)/1000.0f+0.003f;    // Valeur aléatoire d'extinction

Nous réinitialisons aussi la position de la particule au centre de l'écran. Nous faisons cela en réinitialisant les positions en x, y et z à zéro.

Sélectionnez
                particle[loop].x=0.0f;                    // Centre sur l'axe des X
                particle[loop].y=0.0f;                    // Centre sur l'axe des Y
                particle[loop].z=0.0f;                    // Centre sur l'axe des Z

Après que la particule ait été replacée au centre de l'écran, nous lui donnons une nouvelle vitesse de déplacement/direction. Remarquez que j'ai augmenté la vitesse maximale et la vitesse minimale à laquelle la particule peut se déplacer de 50 à 60, mais nous n'allons pas multiplier la vitesse de déplacement par 10. Nous ne voulons pas une explosion pour cette fois, nous voulons des particules se déplaçant plus lentement.

Remarquez aussi que j'ai ajouté « xspeed » à la vitesse de déplacement sur l'axe des X, et « yspeed » à la vitesse de déplacement sur l'axe des Y. Cela nous donne le contrôle sur la direction du déplacement des particules plus tard dans le programme.

Sélectionnez
                particle[loop].xi=xspeed+float((rand()%60)-32.0f);    // Vitesse et direction sur l'axe des X
                particle[loop].yi=yspeed+float((rand()%60)-30.0f);    // Vitesse et direction sur l'axe des Y
                particle[loop].zi=float((rand()%60)-30.0f);        // Vitesse et direction sur l'axe des Z

Finalement nous assignons une nouvelle couleur à la particule. La variable « col » contient un nombre de 0 à 11 (12 couleurs). Nous utilisons cette variable pour obtenir l'intensité rouge, verte et bleue à partir de notre table que nous avons définie au début du programme. La première ligne ci-dessous définit l'intensité rouge (r) à la valeur conservée dans colors[col][0]. Donc si « col » était 0, l'intensité rouge serait 1.0f. Les valeurs vertes et bleue sont lues de la même façon.

Si vous ne comprenez pas comment j'obtiens les valeurs de 1.0f pour l'intensité rouge lorsque « col » est égal à 0, Je vais ré-expliquer plus en détails. Regardez au tout début du programme. Trouvez la ligne : static GLfloat colors[12][3]. Remarquez qu'il y a 12 groupes de 3 nombres. Le premier des trois nombres est l'intensité rouge. Le deuxième est l'intensité verte et le troisième, l'intensité bleue. [0], [1] et [2] ci-dessous représentent les trois premières valeurs que je viens de mentionner. Si « col » est égal à 0, nous voulons avoir le premier groupe. 11 est le dernier groupe (12ème couleur).

Sélectionnez
                particle[loop].r=colors[col][0];            // Sélectionne le rouge dans la table
                particle[loop].g=colors[col][1];            // Sélectionne le vert dans la table
                particle[loop].b=colors[col][2];            // Sélectionne le bleu dans la table
            }

La ligne ci-dessous contrôle la gravité. En appuyant sur la touche 8 du pavé numérique, nous augmentons « yg » (gravité sur y). Cela va provoquer un tirage vers le haut. Le code est placé ici dans le programme, car cela rend notre vie plus facile en appliquant la gravité à toutes les particules grâce à la boucle. Si le code avait été en dehors nous aurions créé une autre boucle pour faire la même chose, donc nous le faisons ici tout aussi bien.

Sélectionnez
            // Si la touche 8 du pavé numérique est appuyée et la gravité est plus petite que 1.5 alors on augmente le tirage vers le haut
            if (keys[VK_NUMPAD8] && (particle[loop].yg<1.5f)) particle[loop].yg+=0.01f;

Cette ligne a l'effet contraire. En appuyant sur 2 nous diminuons « yg » créant une poussée vers le bas plus forte.

Sélectionnez
            // Si la touche 2 du pavé numérique est appuyée et la gravité est plus grande que -1.5 alors on augmente la poussée vers le bas
            if (keys[VK_NUMPAD2] && (particle[loop].yg>-1.5f)) particle[loop].yg-=0.01f;

Maintenant nous modifions le tirage vers la droite. Si la touche 6 du pavé numérique est appuyée, nous augmentons le tirage.

Sélectionnez
            // Si la touche 6 est appuyée et la gravité en X est plus petite que 1.5, alors on augmente le tirage vers la droite
            if (keys[VK_NUMPAD6] && (particle[loop].xg<1.5f)) particle[loop].xg+=0.01f;

Finalement, si la touche 4 du clavier est pressée, nos particules vont être tirées sur la gauche. Ces touches nous donnent des résultats vraiment cool. Par exemple, vous pouvez faire un flux de particules tirant vers le haut. En ajoutant de la gravité vers le bas, vous pouvez transformer le flux de particules en une fontaine à eau !

Sélectionnez
            // Si la touche 4 est appuyée et la gravité en X plus grande que -1.5 alors on augmente le tirage vers la gauche
            if (keys[VK_NUMPAD4] && (particle[loop].xg>-1.5f)) particle[loop].xg-=0.01f;

J'ai ajouté ce code juste pour m'amuser. Mon frère pensait que l'explosion était un superbe effet. :) En appuyant sur la touche tabulation toutes les particules vont être réinitialisées au centre de l'écran. La vitesse de déplacement sera une fois encore multipliée par 10, créant une super explosion de particules. Après la mort des particules, votre effet d'origine va de nouveau réapparaître.

Sélectionnez
            if (keys[VK_TAB])                        // La touche de tabulation provoque une explosion
            {
                particle[loop].x=0.0f;                    // Centre sur l'axe des X
                particle[loop].y=0.0f;                    // Centre sur l'axe des Y
                particle[loop].z=0.0f;                    // Centre sur l'axe des Z
                particle[loop].xi=float((rand()%50)-26.0f)*10.0f;    // Vitesse aléatoire sur l'axe des X
                particle[loop].yi=float((rand()%50)-25.0f)*10.0f;    // Vitesse aléatoire sur l'axe des Y
                particle[loop].zi=float((rand()%50)-25.0f)*10.0f;    // Vitesse aléatoire sur l'axe des Z
            }
        }
    }
    return TRUE;                                    // Tout s'est bien passé
}

Le code dans KillGLWindow(), CreateGLWindow() et WndProc() n'a pas changé, donc nous allons le sauter pour passer directement à WinMain(). Je vais réécrire toute la section de code pour rendre la lecture plus facile à comprendre.

Sélectionnez
int WINAPI WinMain(    HINSTANCE    hInstance,              // Instance
            HINSTANCE    hPrevInstance,              // Instance précédente
            LPSTR        lpCmdLine,                  // Paramètres de ligne de commande
            int        nCmdShow)                   // L'état d'affichage de la fenêtre
{

    MSG    msg;                                  // Structure d'un message Windows
    BOOL done=FALSE;                          // Variable pour gérer la boucle infinie

    // Demander à l'utilisateur quel mode d'exécution (fenêtre ou plein écran) il préfère
    if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
    {
        fullscreen=FALSE;                    // Mode fenêtré
    }

    // Créer notre fenêtre OpenGL
    if (!CreateGLWindow("NeHe's Particle Tutorial",640,480,16,fullscreen))
    {
        return 0;                        // Quitte si la fenêtre n'est pas créée
    }

C'est notre première modification dans WinMain(). J'ai ajouté du code pour savoir si l'utilisateur veut exécuter en plein écran ou en mode fenêtré. S'il décide d'utiliser le plein écran, je change la variable « slowdown » (ralentissement) à 1.0f à la place de 2.0f. Vous pouvez laisser ce morceau de code si vous voulez. Je l'ai ajouté pour accélérer le mode plein écran sur ma 3dfx (qui s'exécute BEAUCOUP plus lentement qu'en mode fenêtré pour certaines raisons).

Sélectionnez
    if (fullscreen)                            // Si nous sommes en mode plein écran  (AJOUT)
    {
        slowdown=1.0f;                        // Accélère les particules (problèmes 3dfx)  (AJOUT)
    }

    while(!done)                            // Boucle qui s'exécute tant que done=TRUE
    {
        if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))        // Y a-t-il un message en attente ?
        {
            if (msg.message==WM_QUIT)            // Avons-nous reçu un message pour quitter ?
            {
                done=TRUE;                // Si c'est le cas, on met « done » à TRUE
            }
            else                        // Sinon on gère les messages
            {
                TranslateMessage(&msg);            // Traduire le message
                DispatchMessage(&msg);            // Envoyer le message
            }
        }
        else                            // S'il n'y a pas de message
        {
            if ((active && !DrawGLScene()) || keys[VK_ESCAPE])    // Met à jour la fenêtre seulement si le programme est actif
            {
                done=TRUE;                // ESC ou DrawGLScene() signale la fin du programme
            }
            else                        // On ne quitte pas encore, on va dessiner
            {
                SwapBuffers(hDC);            // Échange les tampons (double tampon)

J'ai été un peu négligeant avec le prochain morceau de code. Habituellement je ne mets pas tout sur une ligne, mais cela rend le code un peu plus propre. :)

La ligne ci-dessous vérifie si la touche « + » du pavé numérique est appuyée. Si c'est le cas et que « slowdown » est plus grand que 1.0f nous décrémentons « slowdown » de 0.01f. Cela va faire que les particules vont bouger plus vite. Rappelez-vous du code ci-dessus, lorsque j'ai parlé de ralentissement et de son effet sur la vitesse de déplacement des particules.

Sélectionnez
                if (keys[VK_ADD] && (slowdown>1.0f)) slowdown-=0.01f;        // Accélère les particules

Cette ligne vérifie si la touche « - » du pavé numérique est appuyée. Si c'est le cas et que « slowdown » est plus petit que 4.0f nous augmentons la valeur de « slowdown ». Cela va faire que les particules vont bouger plus lentement. J'ai limité à 4.0f, car je ne voulais pas trop les ralentir. Vous pouvez changer les vitesses minimum et maximum par ce que vous voulez. :)

Sélectionnez
                if (keys[VK_SUBTRACT] && (slowdown<4.0f)) slowdown+=0.01f;    // Ralentit les particules

La ligne ci-dessus vérifie si la touche Page haut est appuyée. Si c'est le cas, la variable « zoom » est incrémentée. Cela déplace les particules vers nous.

Sélectionnez
                if (keys[VK_PRIOR]) zoom+=0.1f;        // Zoom

La ligne suivante a l'effet contraire. En appuyant sur Page bas, « zoom » est décrémentée et la scène se déplace dans l'écran. Cela nous permet de voir plus de choses à l'écran, mais les particules sont plus petites.

Sélectionnez
                if (keys[VK_NEXT]) zoom-=0.1f;        // Dézoom

La prochaine section de code vérifie si la touche « Entrée » a été appuyée. Si elle l'a été et qu'elle n'est pas maintenue, nous allons dire à l'ordinateur que la touche a été appuyée en mettant « rp » à true. Alors nous allons faire basculer le mode arc-en-ciel. Si « rainbow » était à true, elle va devenir false. Si elle était à false, elle va devenir true. La dernière ligne vérifie si la touche « Entrée » a été relâchée. Si elle a été relâchée, « rp » est mis à false, disant à l'ordinateur que la touche n'est plus appuyée.

Sélectionnez
                if (keys[VK_RETURN] && !rp)        // Entrée appuyée
                {
                    rp=true;            // Définit le drapeau pour indiquer que la touche a été appuyée
                    rainbow=!rainbow;        // Active/désactive le mode arc-en-ciel
                }
                if (!keys[VK_RETURN]) rp=false;        // Si « Entrée » est relâchée, nous enlevons le drapeau

Le code ci-dessous est un peu déroutant. La première ligne vérifie si la barre espace est appuyée, mais pas maintenue. Cela vérifie aussi si le mode arc-en-ciel est activé, et s'il l'est, on vérifie si la variable « delay » est plus grande que 25. « delay » est un compteur que j'utilise pour créer l'effet d'arc-en-ciel. Si vous changiez la couleur à chaque trame, les particules seraient toutes d'une couleur différente. En créant un retard, un groupe de particules prendra une couleur, avant que cette couleur ne change.

Si la barre espace a été appuyée ou si le mode arc-en-ciel est activé et le retard plus grand que 25, la couleur va être modifiée !

Sélectionnez
                if ((keys[' '] && !sp) || (rainbow && (delay>25)))    // Espace ou mode arc-en-ciel
                {

La ligne ci-dessous a été ajoutée pour que l'arc-en-ciel soit désactivé si la barre d'espace est appuyée. Si nous n'éteignions pas le mode arc-en-ciel, les couleurs continueraient de boucler jusqu'à ce que la touche « Entrée » soit appuyée. Il est logique que quelqu'un qui appuie sur la barre espace au lieu de la touche « Entrée » veuille changer les couleurs.

Sélectionnez
                    if (keys[' ']) rainbow=false;    // Si la touche espace est appuyée on désactive le mode arc-en-ciel

Si la touche espace est appuyée ou si le mode arc-en-ciel est activé et le retard est plus grand que 25, nous allons faire savoir à l'ordinateur que la barre espace a été appuyée en mettant « sp » à true. Puis nous remettons le retard à 0 pour qu'il recommence à compter jusqu'à 25. Finalement nous allons augmenter la variable « col » pour que la couleur prenne l'entrée suivante de la table des couleurs.

Sélectionnez
                    sp=true;            // Définit un drapeau pour dire que la barre espace est appuyée
                    delay=0;            // Réinitialise le retard de rotation des couleurs de l'arc-en-ciel
                    col++;                // Change la couleur des particules

Si la couleur est plus grande que onze, nous devons la remettre à zéro. Si nous ne le faisions pas, notre programme essayerait de trouver une 13ème couleur. Nous n'avons que douze couleurs ! Essayer d'obtenir des informations sur une couleur qui n'existe pas planterait le programme.

Sélectionnez
                    if (col>11) col=0;        // Si la variable est trop grande, nous la réinitialisons
                }

Finalement, si la barre espace est relâchée, nous le faisons savoir à l'ordinateur en remettant « sp » à false.

Sélectionnez
                if (!keys[' '])    sp=false;        // Si la barre espace est relâchée, on réinitialise le drapeau

Maintenant nous allons prendre le contrôle sur les particules. Vous rappelez-vous que nous avons créé deux variables au début de notre programme ? La première s'appelait « xspeed » la seconde « yspeed ». Rappelez-vous aussi que lorsqu'une particule a complètement brûlé, nous lui donnons une nouvelle vitesse de déplacement en ajoutant « xpeed » ou « yspeed ». En faisant cela nous pouvons influencer la direction des particules lors de leur création.

Par exemple, disons que notre particule a une vitesse de déplacement de 5 sur l'axe des X et de 0 sur l'axe des Y. Si nous diminuons « xspeed » jusqu'à -10, nous nous déplacerions à une vitesse de -10 (xspeed) +5 (vitesse de déplacement initiale). Donc au lieu de se déplacer à une vitesse de 10 sur la droite, nous nous déplacerions à une vitesse de -5 sur la gauche. Logique ?

Peu importe. La ligne ci-dessous vérifie si la flèche haut est appuyée. Si c'est le cas, « yspeed » va être incrémentée. Cela va faire que nos particules vont se déplacer vers le haut. Les particules se déplaceront vers le haut à une vitesse maximale de 200. Quelque chose de plus rapide ne sera pas beau.

Sélectionnez
                // Si la flèche haut est appuyée et la vitesse sur Y est plus petite que 200, nous augmentons la vitesse
                if (keys[VK_UP] && (yspeed<200)) yspeed+=1.0f;

La ligne suivante vérifie si la flèche bas est appuyée. Si oui, « yspeed » va être diminuée. Cela va faire les particules se déplacer vers le bas. Encore une fois, la vitesse maximum vers le bas sera de 200.

Sélectionnez
                // Si la flèche bas est appuyée et que la vitesse en Y est plus grande que -200, nous augmentons la vitesse vers le bas
                if (keys[VK_DOWN] && (yspeed>-200)) yspeed-=1.0f;

Maintenant nous vérifions si la flèche droite est appuyée. Si oui, « xspeed » va être incrémentée. Cela va faire les particules se déplacer vers la droite. Une limite de vitesse à 200 est appliquée.

Sélectionnez
                // Si la flèche droite est appuyée et la vitesse sur X plus petite que 200 nous augmentons la vitesse
                if (keys[VK_RIGHT] && (xspeed<200)) xspeed+=1.0f;

Finalement nous vérifions si la flèche gauche est appuyée. Si oui... vous l'avez deviné... « xspeed » est diminuée et les particules vont commencer à se déplacer sur la gauche. Une limite de vitesse à 200 est appliquée.

Sélectionnez
                // Si la flèche gauche est appuyée et si la vitesse sur X est plus grande que -200 alors nous augmentons la vitesse vers la gauche
                if (keys[VK_LEFT] && (xspeed>-200)) xspeed-=1.0f;

La dernière chose que nous devons faire est d'incrémenter la variable « delay ». Comme je l'ai dit avant, « delay » est utilisée pour contrôler la vitesse du changement des couleurs lorsque nous sommes en mode arc-en-ciel.

Sélectionnez
                delay++;            // Incrémente le retard sur le cycle des couleurs du mode arc-en-ciel

Comme tous les tutoriels précédents, assurez-vous que le titre de la fenêtre est correct.

Sélectionnez
                if (keys[VK_F1])        // Est-ce que F1 est appuyée ?
                {
                    keys[VK_F1]=FALSE;    // On passe la touche à FALSE
                    KillGLWindow();        // Fermer la fenêtre OpenGL
                    fullscreen=!fullscreen;    // Basculer entre le plein écran/mode fenêtre

                    // Recréer la fenêtre OpenGL
                    if (!CreateGLWindow("NeHe's Particle Tutorial",640,480,16,fullscreen))
                    {
                        return 0;    // Si la fenêtre n'a pas été créée
                    }
                }
            }
        }
    }
    // Fermeture
    KillGLWindow();                        // Fermer la fenêtre
    return (msg.wParam);                    // Quitter le programme
}

Dans cette leçon, j'ai essayé d'expliquer en détail toutes les étapes nécessaires pour faire un simple, mais impressionnant système de particules. Le système de particules peut être utilisé dans les jeux pour créer vos propres effets comme le feu, l'eau, la neige, les explosions, les étoiles filantes et bien plus. Le code peut être facilement modifié pour avoir plus de paramètres et de nouveaux effets (feux d'artifice par exemple).

Merci à Richard Nutman pour avoir suggéré de placer les particules avec glVertex3f() à la place de réinitialiser la matrice modelview et de repositionner chaque particule avec glTranslated(). Les deux méthodes fonctionnent, mais sa méthode réduit le travail que l'ordinateur doit effectuer avant de dessiner chaque particule, donc le programme tourne plus vite.

Merci à Antoine Valentim pour avoir suggéré d'utiliser les chaînes de triangles qui aident à accélérer le programme et d'introduire une nouvelle commande dans ce tutoriel. Les retours sur ce tutoriel sont bons, j'apprécie !

J'espère que vous avez aimé ce tutoriel. Si vous avez eu des problèmes pour le comprendre, ou que vous avez trouvé une erreur dans le tutoriel, s'il vous plaît, faites-le-moi savoir. Je voudrais faire le meilleur tutoriel possible. Vos retours sont importants !

Jeff Molofee (NeHe)

III. Téléchargements

Compte tenu du nombre de versions de codes sources pour les tutoriels NeHe, nous les laissons en anglais. En principe, si vous avez compris le code présenté dans ce tutoriel (et les tutoriels antérieurs), vous n'aurez pas de mal à les comprendre :

  • Borland C++ Builder 6 (Conversion by Christian Kindahl) ;
  • C# (Conversion by Brian Holley) ;
  • Code Warrior 5.3 (Conversion by Scott Lupton) ;
  • Cygwin (Conversion by Stephan Ferraro) ;
  • Delphi (Conversion by Michal Tucek) ;
  • Dev C++ (Conversion by Dan) ;
  • Euphoria (Conversion by Evan Marshall) ;
  • Game GLUT (Conversion by Milikas Anastasios) ;
  • Irix (Conversion by Dimitrios Christopoulos) ;
  • Java (Conversion by Jeff Kirby) ;
  • Jedi-SDL (Conversion by Dominique Louis) ;
  • JoGL (Conversion by Irene Kam) ;
  • LCC Win32 (Conversion by Robert Wishlaw) ;
  • Linux (Conversion by Ken Rockot) ;
  • Linux/GLX (Conversion by Mihael Vrbanec) ;
  • Linux/SDL (Conversion by Ti Leggett) ;
  • LWJGL (Conversion by Mark Bernard) ;
  • Mac OS (Conversion by Owen Borstad) ;
  • Mac OS X/Cocoa (Conversion by Bryan Blackburn) ;
  • MASM (Conversion by Christophe) ;
  • Visual C++ / OpenIL (Conversion by Denton Woods) ;
  • Pelles C (Conversion by Pelle Orinius) ;
  • Visual Basic (Conversion by Edo) ;
  • Visual C++ ;
  • Visual Studio .NET (Conversion by Grant James).


Masquage

  • I. Contributions
  • II. Tutoriel
  • III. Téléchargements
  • IV. Remerciements
  • V. Liens

Bienvenue dans le 20e tutoriel. Le format d'image Bitmap est supporté sur tous les ordinateurs et sur tous les systèmes d'exploitation. Non seulement il est facile de travailler avec lui, mais il est aussi très facile de le charger et l'utiliser comme une texture. Jusqu'à présent, nous avons utilisé le fondu pour placer du texte et autres images sur l'écran sans effacer ce qu'il y avait en dessous. C'est efficace, mais les résultats ne sont pas toujours jolis.

I. Contributions

Nous recherchons toujours des traducteurs pour finir ce projet de traduction des articles de NeHe. Votre aide serait appréciée ! Vous êtes intéressé ? Alors contactez-nous à l'adresse suivante : nehe@redaction-developpez.com.

II. Tutoriel

La plupart du temps une texture fondue l'est de trop ou pas assez. Lorsque vous faites un jeu en utilisant des sprites, vous ne voulez pas que la scène derrière le personnage brille à travers son corps. Lorsque vous écrivez du texte à l'écran, vous voulez le texte solide et facile à lire.

C'est ici que le masquage (masking) devient pratique. Le masquage est un processus en deux étapes. Premièrement nous plaçons une image noire et blanche de notre texture sur le dessus de la scène. Le blanc représente la partie transparente de la texture. Le noir représente la partie solide. Dû au type de masquage que nous utilisons, seul le noir va apparaître sur la scène. Presque comme un effet d'emporte-pièce. Nous changeons alors les modes de fondu, et nous appliquons notre texture au-dessus de la coupure noire. Toujours à cause du mode de fondu utilisé, les seules parties de la texture qui vont être copiées sont les parties qui se trouvent au-dessus du masque noir.

Je vais récrire tout le programme de ce tutoriel à part les parties qui n'ont pas changé. Donc si vous êtes prêt pour apprendre quelque chose de nouveau, commençons !

Sélectionnez
#include <windows.h>                                // Fichier d'entête pour Windows
#include <math.h>                                // Fichier d'entête pour la bibliothèque mathématique
#include <stdio.h>                                // Fichier d'entête pour les entrées/sorties
#include <gl\gl.h>                                // Fichier d'entête pour la bibliothèque OpenGL32
#include <gl\glu.h>                                // Fichier d'entête pour la bibliothèque GLU32
#include <gl\glaux.h>                                // Fichier d'entête pour la bibliothèque GLaux

HDC        hDC=NULL;                            // Contexte de périphérique privé GDI
HGLRC        hRC=NULL;                            // Contexte de rendu permanent
HWND        hWnd=NULL;                            // Contient notre ID de la fenêtre
HINSTANCE    hInstance;                            // Contient l'instance de l'application

Nous allons utiliser sept variables globales dans ce programme. 'masking' est une variable booléenne (TRUE / FALSE) qui va garder l'état du masquage : activé ou non. 'mp' est utilisée pour être sûr que la touche 'M' n'est pas maintenue enfoncée. 'sp' est utilisée pour être sûr que la barre espace n'est pas maintenue enfoncée et la variable 'scene' va déterminer si nous dessinons la première ou la deuxième scène.

Nous définissons un espace pour cinq textures en utilisant la variable texture[5]. 'loop' est un compteur générique, nous allons l'utiliser plusieurs fois dans notre programme pour définir les textures, etc. Finalement nous avons une variable 'roll'. Nous utilisons 'roll' pour déplacer les textures sur l'écran. Cela va créer un effet soigné ! Nous allons aussi l'utiliser pour faire tourner l'objet de la scène 2.

Sélectionnez
bool    keys[256];                                // Tableau utilisé pour la fonction du clavier
bool    active=TRUE;                                // Drapeau de fenêtre active défini à TRUE par défaut
bool    fullscreen=TRUE;                            // Drapeau de plein écran défini à plein écran par défaut
bool    masking=TRUE;                                // Masquage activé/désactivé
bool    mp;                                    // Touche M pressée ?
bool    sp;                                    // Barre espace pressée ?
bool    scene;                                    // Quelle scène on dessine
GLuint    texture[5];                                // Espace pour cinq textures
GLuint    loop;                                    // Variable générique de boucle
GLfloat    roll;                                    // Texture roulante
LRESULT    CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);                // Déclaration pour WndProc

Le code du chargement des Bitmaps n'a pas changé. C'est le même que dans la leçon 6, etc.

Dans le code ci-dessous, nous créons un espace pour cinq images. Nous nettoyons l'espace et nous chargeons les cinq Bitmaps. Nous bouclons sur chaque image et la convertissons en une texture utilisable par notre programme. Les textures sont stockées dans 'texture[0-4]'.

Sélectionnez
int LoadGLTextures()                                // Ouvre les Bitmaps et les convertit en textures
{
    int Status=FALSE;                            // Indicateur du statut
    AUX_RGBImageRec *TextureImage[5];                    // Crée un espace pour les textures
    memset(TextureImage,0,sizeof(void *)*5);                // Initialise le pointeur à NULL

    if ((TextureImage[0]=LoadBMP("Data/logo.bmp")) &&            // Texture du logo
        (TextureImage[1]=LoadBMP("Data/mask1.bmp")) &&            // Premier masque
        (TextureImage[2]=LoadBMP("Data/image1.bmp")) &&            // Première image
        (TextureImage[3]=LoadBMP("Data/mask2.bmp")) &&            // Deuxième masque
        (TextureImage[4]=LoadBMP("Data/image2.bmp")))            // Deuxième image
    {
        Status=TRUE;                            // Définit le statut à TRUE
        glGenTextures(5, &texture[0]);                    // Crée cinq textures

        for (loop=0; loop<5; loop++)                    // Boucle à travers les cinq textures
        {
            glBindTexture(GL_TEXTURE_2D, texture[loop]);
            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
            glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[loop]->sizeX, TextureImage[loop]->sizeY,
                0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);
        }
    }
    for (loop=0; loop<5; loop++)                        // Boucle à travers les cinq textures
    {
        if (TextureImage[loop])                        // Si la texture existe
        {
            if (TextureImage[loop]->data)                // Si l'image de la texture existe
            {
                free(TextureImage[loop]->data);            // Libère la mémoire de l'image de la texture
            }
            free(TextureImage[loop]);                // Libère la structure de l'image
        }
    }
    return Status;                                // Retourne le statut
}

Le code de ReSizeGLScene() n'a pas changé donc nous le passons.

Le code de Init est réduit à l'essentiel. Nous chargeons nos textures, définissons la couleur de fond, définissons et activons le test de profondeur, activons les ombres douces et activons le placage des textures. Un programme simple, donc pas besoin d'initialisation complexe. :)

Sélectionnez
int InitGL(GLvoid)                                // Toute la configuration d'OpenGL se met ici
{
    if (!LoadGLTextures())                            // Va dans notre routine de chargement de texture
    {
        return FALSE;                            // Si les textures n'ont pas été chargées, retourne FALSE
    }

    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);                    // Fond d'écran noir
    glClearDepth(1.0);                            // Configuration du tampon de profondeur
    glEnable(GL_DEPTH_TEST);                        // Active le test de profondeur
    glShadeModel(GL_SMOOTH);                        // Active les ombres douces
    glEnable(GL_TEXTURE_2D);                        // Active l'application de texture
    return TRUE;                                // L'initialisation s'est bien passée
}

Maintenant la partie amusante. Notre code de dessin ! Nous commençons avec le même code que d'habitude. Nous réinitialisons la couleur de fond et le tampon de profondeur. Puis nous réinitialisons la matrice modelview, et nous reculons de deux unités pour que nous puissions voir notre scène.

Sélectionnez
int DrawGLScene(GLvoid)                                // C'est ici que nous faisons tous les rendus
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);            // Vide l'écran et le tampon de profondeur
    glLoadIdentity();                            // Remet à zéro la matrice courante (modelview)
    glTranslatef(0.0f,0.0f,-2.0f);                        // Déplace de deux unités dans l'écran

La première ligne ci-dessous sélectionne la texture 'logo'. Nous allons appliquer la texture sur l'écran en utilisant un carré. Nous spécifions les quatre coordonnées de texture avec les quatre vertex.

Description revue par Jonathan Roy : rappelez-vous qu'OpenGL est un système graphique basé sur les vertex. La plupart des paramètres que vous définissez sont enregistrés comme des attributs d'un vertex particulier. Les coordonnées de texture sont l'un de ces attributs. Vous spécifiez simplement les bonnes coordonnées de texture pour chaque vertex d'un polygone, et OpenGL va automatiquement remplir la surface entre les vertex avec la texture, à travers un processus connu sous le nom d'interpolation. L'interpolation est une technique géométrique standard qui laisse OpenGL déterminer comment les paramètres varient entre les vertex juste en connaissant les valeurs des paramètres aux vertex eux-mêmes.

Comme dans les leçons précédentes, nous supposons que nous avons un carré et que nous assignons les coordonnées de la texture comme suit : (0.0, 0.0) au coin en bas à gauche, (0.0, 1.0) au coin en haut à gauche, (1.0, 0.0) au coin en bas à droite et (1.0, 1.0) au coin en haut à droite. Maintenant que nous avons ces paramètres, pouvez-vous dire quelles coordonnées de texture correspondent au milieu du carré ? C'est juste, (0.5, 0.5). Mais nulle part dans le code vous n'avez spécifié ces coordonnées, n'est-ce pas ? Lorsque OpenGL dessine le carré, il le calcule pour vous. Et la vraie magie est qu'il le fait pour n'importe quelles position, taille ou orientation du polygone !

Dans cette leçon nous ajoutons une nouvelle notion en assignant aux coordonnées de texture des valeurs différentes de 0.0 et 1.0. Les coordonnées de texture sont dites normalisées. La valeur 0.0 correspond à un bord de la texture, et la valeur 1.0 au bord opposé, parcourant toute la largeur et la hauteur de la texture de l'image par pas de une unité, sans se soucier de la taille du polygone ou de la taille de l'image en pixels (ce dont nous n'avons pas besoin de s'inquiéter lorsque l'on applique une texture, et c'est ce qui rend la vie beaucoup plus facile). Au-dessus de 1.0, le placage de la texture recommence de l'autre côté et la texture se répète. En d'autres mots, les coordonnées de texture (0.3, 0.5) par exemple, correspondent au même pixel de l'image que les coordonnées (1.3, 0.5), ou (12.3, -2.5). Dans cette leçon, nous faisons un effet de tuiles en spécifiant une valeur de 3.0 à la place de 1.0, en faisant se répéter neuf fois la texture (3x3 tuiles) sur la surface du rectangle.

De plus, nous utilisons une variable 'roll' pour déplacer (ou faire glisser) la texture sur la surface du carré. Une valeur de 0.0 pour 'roll', qui est ajoutée à la coordonnée de texture verticale, signifie que le placage du bas de la texture du rectangle commence en bas de l'image de la texture, comme on peut le voir dans la figure de gauche. Lorsque 'roll' est égal à 0.5, le placage du bas du rectangle commence au milieu de la hauteur de l'image (voir la figure sur la droite). Le roulement de texture peut être utilisé pour faire de jolis effets comme des nuages qui se déplacent, ou des mots qui tournent autour d'un objet, etc.

Image non disponible → Image non disponible

Sélectionnez
    glBindTexture(GL_TEXTURE_2D, texture[0]);                // Sélectionne notre texture du logo
    glBegin(GL_QUADS);                            // Commence le dessin d'un carré texturé
        glTexCoord2f(0.0f, -roll+0.0f); glVertex3f(-1.1f, -1.1f,  0.0f);    // Bas gauche
        glTexCoord2f(3.0f, -roll+0.0f); glVertex3f( 1.1f, -1.1f,  0.0f);    // Bas droit
        glTexCoord2f(3.0f, -roll+3.0f); glVertex3f( 1.1f,  1.1f,  0.0f);    // Haut droit
        glTexCoord2f(0.0f, -roll+3.0f); glVertex3f(-1.1f,  1.1f,  0.0f);    // Haut gauche
    glEnd();                                // Fin du dessin du carré

Peu importe… retour à la réalité. Maintenant nous activons le fondu. Pour avoir l'effet correct, nous devons désactiver le test de profondeur. C'est très important de le faire. Si vous ne désactivez pas le test de profondeur, vous allez sûrement ne rien voir. Votre image va disparaître !

Sélectionnez
    glEnable(GL_BLEND);                            // Active le fondu
    glDisable(GL_DEPTH_TEST);                        // Désactive le test de profondeur

La première chose à faire après avoir activé le fondu et désactivé le test de profondeur est de vérifier si nous allons masquer notre image ou la fondre avec l'ancienne méthode. La ligne de code ci-dessous vérifie si 'masking' est à TRUE. Si oui, nous allons définir le fondu pour avoir le masque dessiné correctement.

Sélectionnez
    if (masking)                                // Le masquage est-il  activé ?
    {

Si 'masking' est à TRUE la ligne ci-dessous va définir le fondu de notre masque. Un masque n'est qu'une copie de la texture que nous voulons dessiner à l'écran mais en noir et blanc. Toutes les parties blanches vont être transparentes. Toutes les parties noires vont être SOLIDES.

La commande de fondu ci-dessous fait la chose suivante : la couleur de destination (couleur de l'écran) va être mise en noir si la section du masque qui est à copier est noire. Cela signifie que les sections de l'écran qui sont recouvertes par les parties noires de notre masque seront coloriées en noir. Tout ce qui se situait à l'écran sous ce masque aura été noirci. La section de l'écran en dessous des parties blanches du masque ne va pas changer.

Sélectionnez
        glBlendFunc(GL_DST_COLOR,GL_ZERO);                // Fondu de la couleur avec zéro (noir)
    }

Maintenant nous vérifions la scène qu'il faut dessiner. Si 'scene' est à TRUE nous allons dessiner la deuxième scène. Si la scène est à FALSE nous dessinerons la première.

Sélectionnez
    if (scene)                                // Devons-nous dessiner la deuxième scène ?
    {

Nous ne voulons pas de choses trop grandes donc nous nous déplaçons d'une unité de plus dans l'écran. Cela réduit la taille des objets.

Après ce déplacement, nous faisons une rotation de 0 à 360 degrés selon la valeur de 'roll'. Si 'roll' est à 0.0 nous faisons une rotation de 0 degré. Si 'roll' est à 1.0 nous faisons une rotation de 360 degrés. Rotation plutôt rapide, mais je ne voulais pas créer une nouvelle variable juste pour faire une rotation de l'image au centre de l'écran. :)

Sélectionnez
        glTranslatef(0.0f,0.0f,-1.0f);                    // Déplace d'une unité dans l'écran
        glRotatef(roll*360,0.0f,0.0f,1.0f);                // Tourne sur l'axe des Z de 360 degrés

Nous avons déjà le logo qui glisse sur l'écran et nous avons tourné la scène sur l'axe des Z provoquant une rotation dans le sens antihoraire des objets que nous dessinons, maintenant il ne nous reste plus qu'à vérifier si le masquage est activé. S'il l'est nous dessinons notre masque puis notre objet. Si le masquage est désactivé nous allons juste dessiner notre objet.

Sélectionnez
        if (masking)                            // Est-ce que le masquage est activé ?
        {

Si le masquage est à TRUE le code ci-dessous va dessiner notre masque sur l'écran. Notre mode de fondu devrait être défini comme il faut, car nous avons déjà vérifié le masquage lorsque nous avons défini le fondu. Maintenant tout ce que nous avons à faire est de dessiner le masque à l'écran. Nous sélectionnons le masque 2 (parce que c'est la deuxième scène). Après avoir sélectionné le masque de texture, nous plaquons la texture sur un carré. Le carré fait 1.1 unité de long pour qu'il remplisse un peu plus que l'écran. Nous voulons seulement avoir une texture de visible donc nos coordonnées de texture vont de 0.0 à 1.0.

Après le dessin de notre masque sur l'écran, une copie noire de notre texture finale va apparaître sur l'écran. Le résultat final va ressembler à une image découpée au cutter dans laquelle on aurait enlevé notre texture finale de l'écran, laissant un espace noir.

Sélectionnez
            glBindTexture(GL_TEXTURE_2D, texture[3]);        // Sélectionne la seconde texture du masque
            glBegin(GL_QUADS);                    // Commence le dessin du carré
                glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.1f, -1.1f,  0.0f);    // Bas gauche
                glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.1f, -1.1f,  0.0f);    // Bas droit
                glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.1f,  1.1f,  0.0f);    // Haut droit
                glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.1f,  1.1f,  0.0f);    // Haut gauche
            glEnd();                        // Fin du dessin du carré
        }

Maintenant que nous avons dessiné notre masque à l'écran, il est temps de rechanger de mode de fondu. Cette fois nous allons dire à OpenGL que nous voulons copier n'importe quelle partie de notre texture qui n'est pas noire à l'écran. Parce que la texture finale est une copie exacte de notre masque mais avec des couleurs, les seules parties de notre texture qui vont être dessinées sont les parties au-dessus de la partie noire du masque. Parce que le masque est noir, rien de l'écran ne va ressortir sur notre texture. Cela nous laisse avec une texture solide semblant flotter sur le dessus de l'écran.

Remarquez que nous sélectionnons la deuxième image après avoir sélectionné le mode de fondu final. Cela sélectionne notre image colorée (l'image du second masque est basée sur celle-ci). Remarquez aussi que nous dessinons l'image pile au-dessus du masque. Mêmes coordonnées, mêmes vertex.

Si nous ne fixons pas un masque, notre image sera toujours copiée à l'écran, mais elle sera fondue avec le reste de l'écran.

Sélectionnez
        glBlendFunc(GL_ONE, GL_ONE);                    // Copie les couleurs de l'image 2 à l'écran
        glBindTexture(GL_TEXTURE_2D, texture[4]);            // Sélectionne la texture de la seconde image
        glBegin(GL_QUADS);                        // Commence à dessiner un carré texturé
            glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.1f, -1.1f,  0.0f);    // Bas gauche
            glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.1f, -1.1f,  0.0f);    // Bas droit
            glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.1f,  1.1f,  0.0f);    // Haut droit
            glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.1f,  1.1f,  0.0f);    // Haut gauche
        glEnd();                            // Fin du dessin du carré
    }

Si 'scene' est à FALSE, nous dessinons la première scène (ma favorite).

Sélectionnez
    else                                    // Sinon
    {

Nous commençons par vérifier si le masquage est à TRUE ou FALSE, comme précédemment.

Sélectionnez
        if (masking)                            // Le masquage est-il activé ?
        {

Si le masquage est à TRUE, nous dessinons le masque 1 à l'écran (le masque pour la scène 1). Remarquez que la texture glisse de la droite vers la gauche ('roll' est ajouté aux coordonnées horizontales de texture). Nous voulons que la texture remplisse l'écran c'est pourquoi nous ne nous déplaçons jamais plus loin que l'écran.

Sélectionnez
            glBindTexture(GL_TEXTURE_2D, texture[1]);        // Sélectionne la texture du premier masque
            glBegin(GL_QUADS);                    // Dessine un carré texturé
                glTexCoord2f(roll+0.0f, 0.0f); glVertex3f(-1.1f, -1.1f,  0.0f);    // Bas gauche
                glTexCoord2f(roll+4.0f, 0.0f); glVertex3f( 1.1f, -1.1f,  0.0f);    // Bas droit
                glTexCoord2f(roll+4.0f, 4.0f); glVertex3f( 1.1f,  1.1f,  0.0f);    // Haut droit
                glTexCoord2f(roll+0.0f, 4.0f); glVertex3f(-1.1f,  1.1f,  0.0f);    // Haut gauche
            glEnd();                        // Fin du dessin du carré
        }

Encore une fois nous activons le fondu et sélectionnons la texture pour la scène 1. Nous appliquons la texture sur le dessus de son masque. Remarquez que nous faisons glisser aussi la texture sinon le masque et l'image finale ne seraient pas alignés.

Sélectionnez
        glBlendFunc(GL_ONE, GL_ONE);                    // Copie l'image colorée 1 à l'écran
        glBindTexture(GL_TEXTURE_2D, texture[2]);            // Sélectionne la première texture image
        glBegin(GL_QUADS);                        // Commence le dessin de carré texturé
            glTexCoord2f(roll+0.0f, 0.0f); glVertex3f(-1.1f, -1.1f,  0.0f);    // Bas gauche
            glTexCoord2f(roll+4.0f, 0.0f); glVertex3f( 1.1f, -1.1f,  0.0f);    // Bas droit
            glTexCoord2f(roll+4.0f, 4.0f); glVertex3f( 1.1f,  1.1f,  0.0f);    // Haut droit
            glTexCoord2f(roll+0.0f, 4.0f); glVertex3f(-1.1f,  1.1f,  0.0f);    // Haut gauche
        glEnd();                            // Fin du dessin du carré
    }

Ensuite nous activons le test de profondeur, et désactivons le fondu. Cela évite d'étranges effets dans le reste du programme. :)

Sélectionnez
    glEnable(GL_DEPTH_TEST);                        // Active le test de profondeur
    glDisable(GL_BLEND);                            // Désactive le fondu

Finalement tout ce qu'il nous reste à faire est d'incrémenter la valeur de 'roll'. Si 'roll' est plus grand que 1.0 nous enlevons 1.0. Cela évite d'avoir des valeurs de 'roll' élevées.

Sélectionnez
    roll+=0.002f;                                // Incrémente notre variable 'roll' pour les textures
    if (roll>1.0f)                                // 'roll' est-il plus grand que 1 ?
    {
        roll-=1.0f;                            // Enlève 1 à 'roll'
    }

    return TRUE;                                // Tout est OK
}

Les fonctions KillGLWindow(), CreateGLWindow() et WndProc() n'ont pas changé donc nous les passons.

La première différence que vous allez remarquer dans le code WinMain() est le titre de la fenêtre. Maintenant elle s'appelle "NeHe's Masking Tutorial". Changez-le en ce que vous voulez. :)

Sélectionnez
int WINAPI WinMain(    HINSTANCE    hInstance,                // Instance
            HINSTANCE    hPrevInstance,                // Instance précédente
            LPSTR        lpCmdLine,                // Paramètres de ligne de commande
            int        nCmdShow)                // L'état d'affichage de la fenêtre
{
    MSG    msg;                                // Structure d'un message Windows
    BOOL    done=FALSE;                            // Variable pour gérer la boucle infinie

    // Demander à l'utilisateur quel mode d'exécution (fenêtre ou plein écran) il préfère
    if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
    {
        fullscreen=FALSE;                        // Mode fenêtré
    }

    // Créer notre fenêtre OpenGL
    if (!CreateGLWindow("NeHe's Masking Tutorial",640,480,16,fullscreen))
    {
        return 0;                            // Quitte si la fenêtre n'est pas créée
    }

    while(!done)                                // Boucle qui s'exécute tant que done=TRUE
    {
        if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))            // Y a-t-il un message en attente ?
        {
            if (msg.message==WM_QUIT)                // Avons-nous reçu un message pour quitter ?
            {
                done=TRUE;                    // Si c'est le cas, on met 'done' a TRUE
            }
            else                            // Sinon on gère les messages
            {
                TranslateMessage(&msg);                // Traduire le message
                DispatchMessage(&msg);                // Envoyer le message
            }
        }
        else                                // S'il n'y a pas de message
        {
            // Dessine la scène. Vérifie la touche 'Échap' et les messages pour quitter venant de DrawGLScene()
            if ((active && !DrawGLScene()) || keys[VK_ESCAPE])    // Met à jour la fenêtre si seulement le programme est actif
            {
                done=TRUE;                    // ESC ou DrawGLScene() signale la fin du programme
            }
            else                            // On ne quitte pas encore, on va dessiner
            {
                SwapBuffers(hDC);                // Échange les tampons (Double tampon)

Maintenant notre gestion simple des touches. Nous regardons si la barre espace a été pressée. Si oui, nous définissons la variable 'sp' à TRUE. Si 'sp' est à TRUE, le code ci-dessous ne sera pas exécuté une deuxième fois, tant que la barre espace n'aura pas été relâchée. Cela évite que notre programme boucle sur les différentes scènes trop rapidement. Après avoir mis 'sp' à TRUE, nous basculons la scène. Si la variable était à TRUE, elle devient FALSE, et si elle était à FALSE elle devient TRUE. Dans notre code de dessin ci-dessus, si 'scene' est à FALSE la première scène est dessinée. Si 'scène' est à TRUE la seconde est dessinée.

Sélectionnez
                if (keys[' '] && !sp)                // Si la barre espace est pressée ?
                {
                    sp=TRUE;                // Dit au programme que la barre espace est maintenue
                    scene=!scene;                // Change la scène pour l'autre
                }

Le code ci-dessous vérifie si nous avons relâché la barre espace (si PAS ' '). Si la barre espace a été relâchée, nous mettons 'sp' à FALSE disant au programme que la barre espace n'est PAS maintenue appuyée. En mettant 'sp' à FALSE, le code ci-dessus va vérifier si la barre espace a été de nouveau appuyée, et si c'est le cas, le cycle recommence.

Sélectionnez
                if (!keys[' '])                    // La barre espace est-elle relâchée ?
                {
                    sp=FALSE;                // Indique au programme que la barre espace a été relâchée
                }

La prochaine section de code vérifie si la touche 'M' a été appuyée. Si elle a été appuyée, nous mettons 'mp' à TRUE, disant au programme de ne pas revérifier ceci tant que la touche n'a pas été relâchée, et nous changeons 'masking' de TRUE à FALSE ou de FALSE à TRUE. Si 'masking' est à TRUE, le code de dessin va utiliser le masquage. Si elle est à FALSE le masquage sera désactivé. Si 'masking' est éteint, les objets vont être fondus sur l'écran en utilisant la vieille méthode de fondu que nous avions utilisée jusqu'à présent.

Sélectionnez
                if (keys['M'] && !mp)                // Si 'M' a été pressée
                {
                    mp=TRUE;                // Indique au programme que 'M' est maintenue
                    masking=!masking;            // Change le mode de masquage
                }

La dernière partie de code vérifie si nous avons arrêté d'appuyer sur 'M'. Si c'est le cas, 'mp' devient FALSE indiquant au programme que nous n'appuyons plus sur 'M'. Une fois la touche 'M' relâchée, nous pouvons appuyer dessus une autre fois pour changer le masquage.

Sélectionnez
                if (!keys['M'])                    // 'M' a-t-elle  été relâchée ?
                {
                    mp=FALSE;                // Indique au programme que 'M' a été relâchée
                }

Comme tous les tutoriels précédents, vérifiez que le titre de la fenêtre est correct.

Sélectionnez
                if (keys[VK_F1])                // Est-ce que F1 est pressée ?
                {
                    keys[VK_F1]=FALSE;            // On passe la touche à FALSE
                    KillGLWindow();                // Fermer la fenêtre OpenGL
                    fullscreen=!fullscreen;            // Basculer entre le plein écran / mode fenêtré
                    // Recréer la fenêtre OpenGL
                    if (!CreateGLWindow("NeHe's Masking Tutorial",640,480,16,fullscreen))
                    {
                        return 0;            // Quitte, si la fenêtre n'a pas été créée
                    }
                }
            }
        }
    }
    // Shutdown
    KillGLWindow();                                // Fermer la fenêtre
    return (msg.wParam);                            // Quitter le programme
}

Créer un masque n'est pas trop difficile. Cela prend un peu de temps. La meilleure façon de faire un masque si vous avez déjà une image est de charger votre image dans un programme d'art ou un programme pratique comme InfranView, et de réduire l'image à une image en niveaux de gris. Après avoir fait cela, augmentez le contraste pour que les pixels gris deviennent noirs. Vous pouvez aussi essayer de diminuer la clarté, etc. C'est important que le blanc soit un blanc clair et que le noir soit un noir pur. Si vous avez des pixels gris dans votre masque, la section de l'image va apparaître transparente. La façon la plus sûre d'avoir un masque qui est une parfaite copie de votre image est d'effectuer un tracé noir sur l'image. C'est aussi très important que votre image ait un fond NOIR et le masque un fond BLANC ! Si vous créez un masque et que vous remarquez une forme carrée autour de votre texture, soit votre blanc n'est pas assez clair (255 ou FFFFFF) soit votre noir n'est pas assez noir (0 ou 000000). Ci-dessous, vous pouvez voir un exemple d'un masque et de l'image qui va au-dessus du masque. L'image peut être de n'importe quelle couleur que vous voulez tant que le fond est noir. Le masque doit avoir un fond blanc et une copie noire de votre image.

Ceci est le masque -> Image non disponible Ceci est l'image -> Image non disponible

Eric Desrosiers a remarqué que vous pouviez aussi vérifier les valeurs de chaque pixel de votre image au moment du chargement. Si vous voulez le pixel transparent vous pouvez lui donner une valeur alpha de 0. Pour toutes les autres couleurs vous pouvez donner une valeur alpha de 255. Cette méthode va aussi fonctionner mais requiert un peu plus de code. Ce tutoriel est simple et ne nécessite que peu de code supplémentaire. Je ne ferme pas les yeux devant les autres techniques, mais lorsque j'écris un tutoriel, j'essaie de faire un code simple à comprendre et facile à utiliser. Je voulais juste montrer qu'il y a toujours d'autres façons de faire. Merci pour le retour d'Éric.

Dans ce tutoriel, je vous ai montré une simple, mais efficace façon de dessiner une partie d'une texture sans utiliser le canal alpha. Le fondu normal rend mal, habituellement (les textures sont transparentes ou pas), et le mappage de texture avec un canal alpha requiert que votre image supporte le canal alpha. C'est pratique de travailler avec les Bitmaps, mais elles ne supportent pas le canal alpha et ce programme nous montre comment contourner cette limitation des fichiers Bitmap, tout en montrant une façon pour créer des effets de recouvrement.

Merci à Rob Sante pour l'idée et le code d'exemple. Je n'avais jamais entendu parler de cette astuce jusqu'à ce qu'il me la montre. Il voulait me montrer que même si cette astuce fonctionne, cela prend deux passes, ce qui entraîne une chute de performance. Il recommande d'utiliser des textures qui supportent le canal alpha pour les scènes complexes.

J'espère que vous avez aimé ce tutoriel. Si vous avez n'importe quel problème pour le comprendre, ou que vous avez trouvé une erreur dans le tutoriel, s'il vous plaît, faites-le-moi savoir. Je veux faire les meilleurs tutoriels possible. Vos retours sont importants !

Jeff Molofee (NeHe)

III. Téléchargements

Compte tenu du nombre de versions de codes sources pour les tutoriels NeHe, nous les laissons en anglais. En principe, si vous avez compris le code présenté dans ce tutoriel (et les tutoriels antérieurs), vous n'aurez pas de mal à les comprendre :

  • Borland C++ Builder 6 (Conversion by Christian Kindahl) ;
  • C# (Conversion by Brian Holley) ;
  • Code Warrior 5.3 (Conversion by Scott Lupton) ;
  • Cygwin (Conversion by Stephan Ferraro) ;
  • Delphi (Conversion by Michal Tucek) ;
  • Dev C++ (Conversion by Dan) ;
  • Euphoria (Conversion by Evan Marshall) ;
  • Game GLUT (Conversion by Alexandre Ribeiro de Sá) ;
  • Irix/GLUT (Conversion by Rob Fletcher) ;
  • Java (Conversion by Jeff Kirby) ;
  • LCC Win32 (Conversion by Robert Wishlaw) ;
  • Linux (Conversion by Daniel Davis) ;
  • Linux/GLX (Conversion by Mihael Vrbanec) ;
  • Linux/SDL (Conversion by Ti Leggett) ;
  • LWJGL (Conversion by Mark Bernard) ;
  • Mac OS (Conversion by Anthony Parker) ;
  • Mac OS X/Cocoa (Conversion by Bryan Blackburn) ;
  • MASM (Conversion by Christophe) ;
  • Visual C++ / OpenIL (Conversion by Denton Woods) ;
  • Pelles C (Conversion by Pelle Orinius) ;
  • Visual Basic (Conversion by Edo) ;
  • Visual C++ ;
  • Visual Studio .NET (Conversion by Grant James).


ASM Mode Reel

III. Contributions

Nous recherchons toujours des traducteurs pour finir ce projet de traduction des articles de NeHe. Votre aide serait appréciée ! Vous êtes intéressé ? Alors contactez-nous à l'adresse suivante : nehe@redaction-developpez.com .

II.1. Remerciements

Merci à toute l'équipe de Developpez.com qui a participé et participe encore à cette traduction !

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2007 Nehe Gamedev.net. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Responsable bénévole de la rubrique 2D - 3D - Jeux : LittleWhite -
  • Nous contacter
  • Participez
  • Hébergement
  • Informations légales

Copyright © 2000-2016 - www.developpez.com


Le guide du routard dans OpenGL 1.2

Orion (F. Gasser)

Gritche (J-F. Herouard)

Sommaire

1. Introduction______________________________________________________________________ 4
1.1. Qu’est ce que OpenGL__________________________________________________________ 4
1.2. Les bonnes adresses____________________________________________________________ 5
1.3. Les bons documents____________________________________________________________ 5
2. GLUT, l’OpenGL Utility Toolkit_______________________________________________________ 5
2.1. Un exemple__________________________________________________________________ 6
2.2. L’initialisation________________________________________________________________ 6
2.3. Les callbacks_________________________________________________________________ 7
2.4. Travail en background_________________________________________________________ 7
2.5. Les fonctions de dessin 3D évoluées_______________________________________________ 7
2.6. Exécuter le programme________________________________________________________ 8
3. Initialisation de la vue______________________________________________________________ 8
3.1. Exemple d’initialisation 2D______________________________________________________ 8
3.2. Présentation des matrices d’OpenGL______________________________________________ 8
4. Dessiner__________________________________________________________________________ 9
4.1. Un classique : le triangle________________________________________________________ 9
4.2. Les Buffers__________________________________________________________________ 9
4.3. Bouger le triangle, utilisation de la matrice de vue__________________________________ 12
5. Dessiner en 3D____________________________________________________________________ 13
5.1. Initialisation________________________________________________________________ 13
5.2. Exemple : un Cube____________________________________________________________ 14
5.3. Depth-Buffer________________________________________________________________ 15
6. Paramètres du rendering____________________________________________________________ 15
6.1. Antialiasing_________________________________________________________________ 15
6.2. Culling_____________________________________________________________________ 15
7. Eclairage_______________________________________________________________________ 16
7.1. Activation__________________________________________________________________ 16
7.2. Paramètres_________________________________________________________________ 17
7.3. Fonctions OpenGL___________________________________________________________ 18
7.4. Les normales________________________________________________________________ 20
7.5. Exemple____________________________________________________________________ 21
8. Fog (brouillard)__________________________________________________________________ 22
8.1. Le fonctionnement du brouillard________________________________________________ 22
8.2. L’utilisation du brouillard_____________________________________________________ 22
8.3. Les équations________________________________________________________________ 22
9. Transparence et alpha-blending______________________________________________________ 23
9.1. Activation__________________________________________________________________ 23
9.2. Exemple____________________________________________________________________ 23
9.3. Paramètres_________________________________________________________________ 23
9.4. Ordre des primitives et depth-buffer______________________________________________ 24
10. Listes________________________________________________________________________ 25
10.1. Commandes OpenGL_______________________________________________________ 25
10.2. Exemple_________________________________________________________________ 25
11. Textures______________________________________________________________________ 26
11.1. Principe général___________________________________________________________ 26
11.2. En OpenGL_______________________________________________________________ 26
11.3. Exemple_________________________________________________________________ 28
11.4. MipMapping______________________________________________________________ 29
11.5. Chargement dans la carte vidéo_______________________________________________ 29
11.6. Exemple_________________________________________________________________ 30
11.7. Priorités des textures_______________________________________________________ 30
12. Les opérations sur les pixels_______________________________________________________ 31
12.1. Introduction______________________________________________________________ 31
12.2. La Raster Position_________________________________________________________ 31
12.3. Dessiner le bitmap__________________________________________________________ 31
12.4. Lire, écrire et copier des images_______________________________________________ 32
12.5. Agrandissement, réduction et réflexion_________________________________________ 33
13. Tests de validation et stencil buffer__________________________________________________ 33
13.1. Chaîne de tests_____________________________________________________________ 33
13.2. Scissor test________________________________________________________________ 33
13.3. Alpha test________________________________________________________________ 33
13.4. Stencil test________________________________________________________________ 33
14. Ombres et reflets________________________________________________________________ 35
14.1. Reflets___________________________________________________________________ 35
14.2. Ombres__________________________________________________________________ 38
15. Courbes et Surfaces calculées______________________________________________________ 41
15.1. Présentation______________________________________________________________ 41
15.2. Quadrics_________________________________________________________________ 41
15.3. Bézier___________________________________________________________________ 42
16. Ce qui n’a pas de place ailleurs____________________________________________________ 45
16.1. Dessiner deux fois au même endroit____________________________________________ 45
16.2. Clipping_________________________________________________________________ 45
16.3. Multitexturing_____________________________________________________________ 46

1. Introduction

Qu’est ce que OpenGL

OpenGL est une interface de programmation vers le matériel graphique. Cette interface compte environ 150 commandes qui sont utilisées dans les programmes interactifs en 3D. Etant donné que cette API est à l’origine professionnelle, il y a beaucoup de fonctions qui ne sont toujours pas câblées sur les cartes accélératrices 3D pour le grand public, et qui seront donc émulées en software.

La première particularité d’OpenGL est que la programmation est indépendante du matériel graphique utilisé, et que son domaine d’utilisation n’est pas confiné aux PC/Windows ou Mac. A l’origine développé par Silicon Graphics, OpenGL est aujourd’hui disponible sur les plates-formes x86 et 68000 entre autres, que ce soit sous Windows, Linux ou Mac. La conséquence immédiate est qu’aucune commande de fenêtrage, ou d’une manière générale toute commande dépendant de l’OS, n’est incluse et que seuls des commandes graphiques de bas niveaux existent (dessiner un point, un segment mais pas de sphère par exemple). Tout objet complexe est composé à partir d’un nombre limité de primitives graphiques.

Deuxièmement, OpenGL se base toujours sur d’autres librairies graphiques qui sont en général spécifiques au système d’exploitation ou qui apportent des fonctions complexes par dessus la librairie OpenGL. L’OpenGL Utility Library (GLU) ainsi que l’OpenGL Utility Toolkit (GLUT) en sont des exemples.

Une autre particularité est que certaines implémentations d’OpenGL sont conçues pour que la station sur laquelle on affiche ne soit pas celle sur laquelle tourne le programme. Cette fonctionnalité n’est utilisé que sur les systèmes basés sur l’X, et ne sera donc pas détaillée ici (en gros, cela est possible car OpenGL utilise une architecture client (un programme) – serveur (la carte vidéo et son driver OpenGL)).

Silicon Graphics n’a pas implémenté de version pour Linux de OpenGL. Il convient donc d’utiliser Mesa, qui est une librairie compatible OpenGL (attention, la compatibilité n’est ni garantie ni totale, .... Gritche tire une sale gueule la parce qu’il est sûr que Mesa 3.1 est compatible OpenGL 1.2, il dit aussi que ça s’appelle Mesa parce que l’emploi du nom OpenGL™ n’est pas possible en GNU).

Les bonnes adresses

Voici quelques bonnes adresse à visiter pour avoir accès à des informations complémentaires et aux manuels de référence :

· OpenGL :

http://www.OpenGL.org : news, documentation, liens…

http://trant.sgi.com/OpenGL : des exemples, des astuces

http://reality.sgi.com/OpenGL : idem

· GLU :

http://www.OpenGL.org/Documentation/GLX.html

· GLUT :

http://reality.sgi.com/mjk/spec3/spec3.html

· Mesa :

http://www.mesa3d.org

· AGL (Extension OpenGL pour Apple) :

http://www.sd.tgs.com/Products/OpenGL.htm

· PGL (Extension OpenGL pour IBM OS/2 Warp

http://www.austin.ibm.com/software/OpenGL/

Sur la machine de Game, il y a déjà dans les 250 Mo de download provenant de ces sites, alors avant de dans lancer un wget sauvage à l’école, vérifiez :-). En plus les exemples sont déjà compilés.

Si vous trouvez quelque chose d’intéressant qui n’est pas sur la machine, n’hésitez pas à en faire part à tout le monde.

Les bons documents

Il y a quelques documents de références en ligne qui sont bons à avoir : OpenGL1.2.pdf, glu1.3.pdf, glut.spec.ps et les man pages d’OpenGL en Html (une page html par fonction).

2. GLUT, l’OpenGL Utility Toolkit

OpenGL ne s’occupant pas des opérations relatives au système ou au fenêtrage, il laisse la place à d’autres API, dont GLUT fait parti. Il existe des implémentations de GLUT pour Windows NT, X-Window et OS/2, ce qui permet de compiler le même code source sur toutes les plates-formes.

GLUT est une interface entre le programme et l’humanoïde qui se trouve derrière l’écran, la souris, ou le joystick. Sa programmation est de type événementielle et chaque agitation de l’humain est considéré comme un événement qui sera géré par des fonctions personnalisées (callback).

Un exemple

Voici un premier exemple de programme utilisant OpenGL :

#include <GL/glut.h>

int main(int argc, char** argv)

{

Glint Xscreen = 800;

Glint Yscreen = 600;

glutInit(&argc, argv);

glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);

glutInitWindowSize (Xscreen, Yscreen);

glutCreateWindow (argv[0]);

glutDisplayFunc (display);

glutReshapeFunc (reshape);

glutIgnoreKeyRepeat(1);

glutKeyboardFunc(keyboard);

glutKeyboardUpFunc(keyboardup);

glutSpecialFunc(special);

glutSpecialUpFunc(specialup) ;

glutMouseFunc(mouse);

glutMotionFunc(motion);

glutPassiveMotionFunc(motion);

glutIdleFunc (IntelligenceArtificielle);

glutMainLoop ();

return(0);

}

L’initialisation

On remarque que l’on fait plusieurs appels à des fonctions GLUT pour créer une fenêtre. Dans l’ordre, on a :

· glutInit(int *argc, char **argv) : A être appelé avant tout autre fonction GLUT. Cette fonction gere sous X les paramètres tels que –display et –geometry.

· glutInitDisplayMode(unsigned int mode) : différencie les mode RGBA et couleur indexé, ainsi que le double-buffering et les buffers à utiliser (depth, stencil, accumulation), que nous étudierons par la suite

· glutInitWindowPostion(int x, int y) : no comment

· glutInitWindowSize(int width, int size) : no comment

· int glutCreatWindow(char *string) : crée la fenêtre (et un contexte OpenGL) avec tous les paramètres spécifiés avant. Elle renvoie un identifiant unique pour la nouvelle fenêtre. Attention : la fenêtre ne sera pas affichée avant l’appel à glutMainLoop()

Les callbacks

Viennent ensuite les « callback ». Il s’agit de spécifier quels sont les fonctions à appeler suivant les événements qui se produisent. On peut changer les fonctions appelées à n’importe quel moment. Si on spécifie la valeur NULL comme adresse de fonction à utiliser, cela a pour effet de désactiver le callback correspondant. Elles sont par défaut à NULL. Les plus utilisés des callback sont :

· glutDisplayFunc(void(*func)(void)) : indique la fonction à utiliser lors de l’affichage graphique.

· glutReshapeFunc(void(*func)(int w, int h)) : si la fenêtre est redimentionnée

· glutKeyboardFunc(void(*func)(unsigned char key, int x, int y)) et glutMouseFunc(void(*func)(int button, int state, int x, int y)) : événement clavier et souris

· glutMotionFunc(void (*func)(int x, int y))

Travail en background

Lorsque le programme n’a plus rien a faire, ie l’affichage a été traité, et les callback aussi, on peut spécifier de rentrer dans un fonction qui peut se charger par exemple de faire évoluer le monde virtuel. On utilise alors la fonction :

· GlutIdleFunc(void (*func)(void)) : comme d’hab, NULL passé en paramètre désactive cette fonction.

Les fonctions de dessin 3D évoluées

GLUT peut se charger de dessiner des objets 3D centrés sur l’origine en mode fil de fer (wire) ou solide (solid). Les objets sont :

cone icosahedron teapot

cube octahedron tetrahedron

dodecahedron sphere torus

Pour dessiner un cube ou une sphère, on utilise par exemple les fonctions :

void glutWireCube(Gldouble size) ;

void glutSolidCube(Gldouble size) ;

void glutWireSphere(Gldouble radius, Glint slices, Glint stacks) ;

void glutSolidSphere(Gldouble radius, Glint slices, Glint stacks) ;

Exécuter le programme

Pour exécuter le programme et voir réellement quelque chose s’afficher après l’initialisation, il faut appeler la fonction :

· glutMainLoop(void) Ici, on rentre dans la boucle infinie d’exécution. Ca doit être à peut de choses prêt la dernière ligne du programme. On ne pourra sortir de cette boucle que par un exit().

3. Initialisation de la vue

Exemple d’initialisation 2D

OpenGL est plus connu pour son utilisation 3D, mais il est aussi prévu pour fonctionner en 2D. Comme c’est un peu plus facile à appréhender, on commence par là.

Une fois que le « contexte » OpenGL est crée, il reste à créer la vue. Cela se fait ainsi :

GLsizei width = cx, height = cy; // width, height : taille de la fenêtre

GLdouble aspect;

if (cy == 0)

aspect = (GLdouble)width;

else

aspect = (GLdouble)width/(GLdouble)height;

glMatrixMode(GL_PROJECTION); // initialisation de la matrice de projection

glLoadIdentity();

gluOrtho2D(-1.0f * aspect, 1.0f * aspect, -1.0f, 1.0f);

glMatrixMode(GL_MODELVIEW); // initialisation de la matrice de vue

glLoadIdentity();

Il s’agit ici d’une initialisation d’une vue 2D.

Présentation des matrices d’OpenGL

En OpenGL, toutes les transformations géométriques sont entrées sous forme de matrice, la vue est définie par la position de la caméra (matrice de changement de repère) et par la matrice de projection.

La matrice de projection s’appelle GL_PROJECTION. Il n’est possible d’accéder qu’à une matrice à la fois, donc pour modifier une matrice, il faut d’abord l’activer avec glMatrixMode. La matrice choisie devient la « matrice courante ». Il reste à définir la matrice de projection, mais inutile de se revoir le cours de prépa, car heureusement des fonctions de glu permettent de spécifier les matrices les plus courantes. La fonction

void gluOrtho2D( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top )

permet dans l’exemple de mettre une matrice de projection 2D de la dimension voulue.

GlLoadIdentity initialise la matrice à l’identité.

La matrice GL_MODELVIEW est en quelque sorte la matrice de positionnement de la vue. C’est le plus souvent une matrice de changement de repère. Il faut la considérer comme une matrice qui positionne les objets dans le repère du monde, et éviter de la considérer comme la position de la caméra, même si c’est aussi son rôle.

Il existe une troisième matrice, GL_TEXTURE, mais celle-ci n’est jamais utilisée directement.

Toutes sont des matrices 4x4 de float.

4. Dessiner

Un classique : le triangle

Les fonctions suivantes permettent de dessiner un triangle à l’écran :

glClear(GL_COLOR_BUFFER_BIT); // efface l’écran

glBegin(GL_POLYGON); // début d’un dessin d’un polygone

glColor3f(1.0f, 0.0f, 0.0f); // changement de couleur

glVertex2f(-0.5f, -0.5f); // entrée d’une coordonnée

glColor3f(0.0f, 1.0f, 0.0f);

glVertex2f( 0.5f, -0.5f);

glColor3f(0.0f, 0.0f, 1.0f);

glVertex2f( 0.5f, 0.5f);

glEnd(); // fin du polygone

glFlush(); // affichage

Ce qui donne (imprimé en noir & blanc c’est moche, mais tant pis) :

Les Buffers

Il existe dans OpenGL des « buffers », qui d’une manière générale ne sont que des espaces mémoires contenant des données propres à l’OpenGL.

Le principal est le « COLOR_BUFFER », qui correspond à ce que l’on voit sur l’écran.

La fonction glClear permet de remplir tout un buffer avec la même valeur. La valeur par défaut est 0. Un appel à glClear sur le COLOR_BUFFER rempli l’écran de noir. GlClearColor permet de changer la couleur de remplissage.

Un autre buffer, le back-buffer, fonctionne différemment : pour ne pas voir se dessiner la scène à l’écran, on utilise la technique du « double-buffering ». Le COLOR_BUFFER est en fait dédoublé : l’un est affiché (front-buffer), l’autre est celui sur lequel on travaille (back-buffer). A chaque fois qu’une image est calculée, on inverse (swap) les buffers. Pour le programmeur, tout marche comme s’il dessinait toujours sur le même buffer. L’avantage est que l’utilisateur ne vois pas se dessiner l’image.

Les autres buffers standards sont le Depth-buffer, le Stencil-buffer, l’Accumulation-buffer. Chaque buffer joue un rôle précis, mais ils ne seront présentés que lorsque nous en aurons besoin. D’autres sont des extensions propriétaires (T-Buffer, W-Buffer, …).

Le début de l’entrée d’une primitive se fait avec glBegin : ici un polygone. Les possibilités sont (tirées de la man page de glBegin) :

GL_POINTS Treats each vertex as a single point. Vertex n defines point n.

N points are drawn.

GL_LINES Treates each pair of vertices as an independent line segment.

Vertices 2n-1 and 2n define line n. N/2 lines are drawn.

GL_LINE_STRIP

Draws a connected group of line segments from the first vertex to

the last. Vertices n and n+1 define line n. N-1 lines are

drawn.

GL_LINE_LOOP

Draws a connected group of line segments from the first vertex to

the last, then back to the first. Vertices n and n+1 define line

n. The last line, however, is defined by vertices N and 1. N

lines are drawn.

GL_TRIANGLES

Treates each triplet of vertices as an independent triangle.

Vertices 3n-2, 3n-1, and 3n define triangle n. N/3 triangles are

drawn.

GL_TRIANGLE_STRIP

Draws a connected group of triangles. One triangle is defined

for each vertex presented after the first two vertices. For odd

n, vertices n, n+1, and n+2 define triangle n. For even n,

vertices n+1, n, and n+2 define triangle n. N-2 triangles are

drawn.

GL_TRIANGLE_FAN

Draws a connected group of triangles. One triangle is defined

for each vertex presented after the first two vertices. Vertices

1, n+1, and n+2 define triangle n. N-2 triangles are drawn.

GL_QUADS Treats each group of four vertices as an independent

quadrilateral. Vertices 4n-3, 4n-2, 4n-1, and 4n define

quadrilateral n. N/4 quadrilaterals are drawn.

GL_QUAD_STRIP

Draws a connected group of quadrilaterals. One quadrilateral is

defined for each pair of vertices presented after the first pair.

Vertices 2n-1, 2n, 2n+2, and 2n+1 define quadrilateral n. N/2-1

quadrilaterals are drawn. Note that the order in which vertices

are used to construct a quadrilateral from strip data is

different from that used with independent data.

GL_POLYGON

Draws a single, convex polygon. Vertices 1 through N define this

polygon.

glVertex permet d’entrer un point.

Le format général des fonctions d’OpenGL est : {nom}{1234}{b s i d ub us ui}{v}, respectivement le nom de la fonction, le nombre de paramètres, leur type, passé directement ou par pointeur (vecteur). Tous les caractéristiques de la fonction sont optionnelles sauf le nom.

glVertex existe par exemple sous les formes glVertex2d, glVertex2f, glVertex2i, glVertex2s, glVertex3d, glVertex3f, glVertex3i, glVertex3s, glVertex4d, glVertex4f, glVertex4i, glVertex4s, glVertex2dv, glVertex2fv, glVertex2iv, glVertex2sv, glVertex3dv, glVertex3fv, glVertex3iv…

glColor fixe la couleur courante. De même elle existe sous de nombreuses formes.

glEnd indique la fin de l’entrée de coordonnées.

Cependant jusqu’ici rien n’est affiché : OpenGL a mis tout ce qu’il y a à faire dans un buffer, mais ne l’affiche que lorsqu’on lui demande, avec glFlush, qui rend la main immédiatement, ou avec glFinish, qui attend que tout soit affiché (peu d’intêret).

Bouger le triangle, utilisation de la matrice de vue

Rotation et translation

Pour bouger la « caméra » ou un objet (c’est la même chose de bouger tout les objets ou la caméra), il faut utiliser la matrice de vue (GL_MODELVIEW).

Comme calculer directement une matrice de changement de repère n’est pas vraiment évidente, il existe des fonctions prédéfinies : glRotate et glTranslate.

void glRotated( GLdouble angle, GLdouble x, GLdouble y, GLdouble z )

void glRotatef( GLfloat angle, GLfloat x, GLfloat y, GLfloat z )

void glTranslated( GLdouble x, GLdouble y, GLdouble z )

void glTranslatef( GLfloat x, GLfloat y, GLfloat z )

Ces fonctions multiplient à droite la matrice de vue. Il faut donc faire attention à l’ordre dans lequel les transformations sont entrées : c’est la dernière transformation entrée qui sera appliquée en premier.

Rappels :

· soient f de matrice L, g de matrice M et h de matrice N. h°g°f a pour matrice NML donc il faut entrer les matrices dans l'ordre N,M,L

· les rotations et les translations ne sont pas commutatives

Par exemple, pour faire tourner un objet sur lui-même, il faut entrer en dernier la matrice de rotation. Pour simplifier, l’origine du repère local d’un objet est par convention son centre de rotation (d’inertie) pour permettre de le faire tourner sur lui-même sans calcul complexe.

Tout cela peut paraître compliqué, mais un ordinateur ne sait comprendre que les matrices, et un énorme avantage est le temps de calcul : même si de nombreuses transformations sont entrées, il ne reste qu’une seule matrice à appliquer à tout les points entrés.

Pile de matrice

Mais comment inverser les transformations entrées ? Calculer les inverses est une perte de temps. Une pile est en effet associée à chaque matrice. Les fonctions sont :

void glPushMatrix( void )

void glPopMatrix( void )

qui respectivement empile et dépile la matrice courante. Même s’il n’y a que deux fonctions, il existe bien trois piles différentes (une par matrice). Comme pour les transformations, seule la matrice active peut-être empilée / dépilée (ce qui est logique, puisque que une matrice est empilée pour la sauvegarder avant une transformation).

Le plus souvent, un objet est caractérisé par ses angles de rotation (ax, ay, az) et sa position (x, y, z). Son affichage est alors du type :

glPushMatrix() ;

glTranslate3f(x,y,z) ;

glRotate3f(ax,ay,az) ;

glBegin(…)

…

glEnd() ;

glPopMatrix() ;

Exemple d’utilisation : un soleil avec ses satellites, et les lunes des satellites. Le principe est le même :

· Dessin du soleil

· PushMatrix

§ Translation dans le repère local d’une planète

§ Dessin de la planète

§ PushMatrix

· Translation dans le repère local d’une lune

· Dessin de la lune

§ PopMatrix

§ PushMatrix

· Translation dans le repère local d’une autre lune

· Dessin de la lune

§ PopMatrix

· PopMatrix

· PushMatrix

§ Translation dans le repère local d’une autre planète

§ Dessin de la planète

§ PushMatrix

· Translation dans le repère local d’une lune

· Dessin de la lune

§ PopMatrix

· PopMatrix

· etc.

5. Dessiner en 3D

Initialisation

Quelques changements dans l’initialisation :

glMatrixMode(GL_PROJECTION);

glLoadIdentity();

gluPerspective(45, aspect, 1.0f, 10.0f);

gluLookAt(0.0f, 0.0f, 0.0f,

0.0f, 1.0f, 0.0f,

0.0f, 0.0f, 1.0f);

glEnable(GL_DEPTH_TEST);

gluPerspective construit une matrice de projection de champ de vue de 45°, de rapport largeur/hauteur aspect, de profondeur de champ (1.0, 10.0), ie seuls les objets situés à une distance comprise entre 1.0 et 10. de la caméra sont affichés.

gluLookAt construit une matrice de changement de repère à partir de trois vecteurs : la position de la caméra, la direction où elle regarde, un vecteur indiquant le « haut » (juste la direction, il sera modifié pour faire un repère orthonormé).

Exemple : un Cube

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

total %= 360*500; // temps écoulé en ms

glPushMatrix();

glTranslatef(0.0f, 5.0f+(GLfloat)sin(GLfloat(total)/500.0f)*3.0f, 0.0f);

glRotatef(total/50.0f, 0.3f, 0.5f, 0.8f);

glBegin(GL_QUADS);

glColor3f(1.0f, 0.0f, 0.0f);

glVertex3f(-0.5f, -0.5f, -0.5f);

glVertex3f( 0.5f, -0.5f, -0.5f);

glVertex3f( 0.5f, 0.5f, -0.5f);

glVertex3f(-0.5f, 0.5f, -0.5f);

glColor3f(1.0f, 1.0f, 0.0f);

glVertex3f(-0.5f, -0.5f, 0.5f);

glVertex3f( 0.5f, -0.5f, 0.5f);

glVertex3f( 0.5f, 0.5f, 0.5f);

glVertex3f(-0.5f, 0.5f, 0.5f);

glColor3f(0.0f, 0.0f, 1.0f);

glVertex3f( 0.5f, -0.5f, 0.5f);

glVertex3f( 0.5f, -0.5f, -0.5f);

glVertex3f( 0.5f, 0.5f, -0.5f);

glVertex3f( 0.5f, 0.5f, 0.5f);

glColor3f(0.0f, 1.0f, 1.0f);

glVertex3f(-0.5f, -0.5f, 0.5f);

glVertex3f(-0.5f, -0.5f, -0.5f);

glVertex3f(-0.5f, 0.5f, -0.5f);

glVertex3f(-0.5f, 0.5f, 0.5f);

glColor3f(1.0f, 0.0f, 1.0f);

glVertex3f(-0.5f, 0.5f, 0.5f);

glVertex3f(-0.5f, 0.5f, -0.5f);

glVertex3f( 0.5f, 0.5f, -0.5f);

glVertex3f( 0.5f, 0.5f, 0.5f);

glColor3f(0.0f, 1.0f, 0.0f);

glVertex3f(-0.5f, -0.5f, 0.5f);

glVertex3f(-0.5f, -0.5f, -0.5f);

glVertex3f( 0.5f, -0.5f, -0.5f);

glVertex3f( 0.5f, -0.5f, 0.5f);

glEnd();

glPopMatrix();

Depth-Buffer

En 3D, tout n’est pas affiché : il y a des faces cachées. Comment déterminer ce qui est affiché ? En affichant ce qui est la plus proche de la caméra.

C’est la rôle du depth-buffer : il sauvegarde la distance du point actuellement affiché. Si un point plus proche arrive ensuite, il remplace le point et la valeur dans la depth-buffer est changée. Le depth-buffer à les même dimensions que le color-buffer.

Le depth-buffer doit être effacé avec glClear.

6. Paramètres du rendering

Antialiasing

L’OpenGL est une « state machine » : les paramètres de dessin consistent en une série de variables (très nombreuses, voir les annexes de la spécification OpenGL pour une liste complète).

La plupart sont modifiables par :

void glEnable( GLenum cap )

void glDisable( GLenum cap )

Pour l’antialiasing, les paramètres sont : GL_POINT_SMOOTH, GL_LINE_SMOOTH, et GL_POLYGON_SMOOTH. Le but de l’antialiasing est de rendre apparemment plus lisse les lignes en faisant un dégradé sur les contours des points.

Culling

Le culling consiste à ne dessiner que les triangles dont les points sont dans le sens des aiguilles d’une montre (« clockwise ») ou dans l’autre sens (« counter-clockwise »). L’intérêt est l’optimisation : l’objectif est de toujours dessiner les faces extérieures d’un objet dans le sens contraire des aiguilles d’une montre, et de filtrer les faces cachées avec le culling.

Prenons l’exemple du cube : les faces sont toujours affichées dans le même ordre, peut importe si elles sont visibles ou pas. C’est le depth-buffer qui permet d’afficher les bonnes faces. Cependant le test du depth-buffer est long : il faut calculer la distance de chaque point de chaque face par rapport à la caméra, puis tester le depth pour chaque point (et mettre à jour le depth si le point est plus près). C’est un test beaucoup trop important pour les faces dont on est sûr qu’elles ne seront pas affichées. Il est plus rapide d’éliminer une face entière d’un coup avec le culling.

L’astuce utilisée par le culling pour savoir de quel coté est vue une face consiste à regarder dans quel sens les points des triangles sont dessinés (en OpenGL tout est décomposé en triangles) : dans le sens des aiguilles d’une montre ou dans l’autre sens.

Cette technique est très rapide : en gros il suffit de regarder le signe du produit scalaire de deux cotés d’un triangle. La seule contrainte pour le programmeur est de dessiner dans le bon sens les points.

Le standard est le CCW (counter-clockwise), que ce soit pour les primitives de glu ou glut, ou encore pour 3DS ou Lightwave.

En OpenGL, les fonctions correspondantes sont :

void glCullFace( GLenum mode )

avec mode à GL_FRONT, GL_BACK ou GL_FRONT_AND_BACK, pour indiquer quel coté doit être éliminé au culling.

void glFrontFace( GLenum mode )

avec mode à GL_CW ou GL_CCW pour indiquer quelles sont les faces de devant

Un appel à glEnable / glDisable avec en paramètre GL_CULL_FACE pour activer / désactiver le culling.

7. Eclairage

Activation

C’est pour ce genre de choses que l’on apprécie la simplicité d’OpenGL, car activer l’éclairage se fait par :

glEnable(GL_LIGHTING) ; // active le calcul de l’éclairage

glEnable(GL_LIGHT0) ; // allume la lampe 0

C’est tout.

Pour allumer une autre lampe, mettre GL_LIGHTi au lieu de GL_LIGHT0. Il y a au minimum 8 lampes disponibles. On a toujours GL_LIGHTi = GL_LIGHT0 + i. Pour éteindre, utiliser glDisable.

Le nombre maximal de lumières est obtenu en appelant :

glGetIntegerv(GL_MAX_LIGHTS, &nb_lights)

Paramètres

Il faut quand même donner un minimum d’information sur la lampe, comme la couleur et la position de la lampe.

Un peu de théorie

Dans la réalité, les sources de lumières sont des volumes (par exemple un filament), et la lumière se réfléchit à l’infini sur tous les objets.

Il est impossible de simuler un tel comportement, surtout quand il faut calculer une image en quelques millisecondes. Des simplifications ont été apportées :

· les sources de lumière sont des points

· la lumière ne se réfléchit pas sur les objets

Pour plus de réalisme, les sources disposent de plusieurs paramètres qui n’ont pas forcément d’équivalent réel (voir le paragraphe suivant).

Une chose par contre ne change pas : la couleur visible d’un objet dépend à la fois de sa couleur propre et celle de la lampe, et l’intensité de la lumière qu’il reçoit dépend de l’angle de la surface avec la direction vers la source lumineuse.

Tout sur la lampe

Tiré tout droit de la spécification OpenGL :

Paramètre | Type | Par défaut | Description
acm | Couleur | (0,0,0,0) | Intensité ambiante : la lumière ambiante est présente partout dans la scène, elle n’a pas de direction
dcli | Couleur | (0,0,0,0) sauf lampe 0 : (1,1,1,1) | Intensité de diffusion : c’est l’intensité « réelle » de la source, elle part dans tous les sens depuis la source
scli | Couleur | Idem | Intensité du « spot » : comme la lumière diffuse, sauf qu’elle n’est émise qu’à l’intérieur d’un cône
Ppli | Position | (0,0,1,0) | Position
sdli | Direction | (0,0,-1) | Direction du spot (vecteur)
srli | Réel | 0 | Exposant du spot (entre 0 et 128)
crli | Réel | 180 | Angle du cône du spot (180 ou entre 0 et 90)
k0i | Réel | 1.0 | Atténuation constante (entre 0 et ¥)
k1i | Réel | 0 | Atténuation linéaire
k2i | Réel | 0 | Atténuation quadrique

Et sur les matériaux

Les mêmes paramètres existent pour spécifier la « réponse » de la surface d’un objet :

Paramètre | Type | Par défaut | Description
acm | Couleur | (0.2,0.2,0.2,1.0) | Couleur ambiante
dcm | Couleur | (0.8,0.8,0.8,1.0) | Couleur de diffusion
scm | Couleur | (0,0,0,1) | Couleur au « spot »
ecm | Couleur | (0,0,0,1) | Couleur émise
srm | Réel | 0 | Exposant au « spot »

Fonctions OpenGL

Les fonctions suivantes permettent de spécifier tous les paramètres précédents :

· Pour une source lumineuse :

void glLightf( Glenum light, Glenum pname, GLfloat param )

void glLighti( Glenum light, Glenum pname, GLint param )

light = GL_LIGHTi

pname = GL_SPOT_EXPONENT, GL_SPOT_CUTOFF, GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION, ou GL_QUADRATIC_ATTENUATION

et param = la nouvelle valeur

void glLightfv( GLenum light, GLenum pname, const Glfloat *params)

void glLightiv( GLenum light, GLenum pname, const GLint *params)

light = GL_LIGHTi

pname = GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, GL_POSITION, GL_SPOT_DIRECTION, GL_SPOT_EXPONENT, GL_SPOT_CUTOFF, GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION, ou GL_QUADRATIC_ATTENUATION

param = la nouvelle valeur

Attention à la position de la lampe : comme toutes les coordonnées entrées, elles dont immédiatement multipliées par la matrice ModelView. Si celle-ci change, il faut repositionner les lampes.

void glLightModelf( GLenum pname, GLfloat param )

void glLightModeli( GLenum pname, GLint param )

void glLightModelfv( GLenum pname, const GLfloat *params )

void glLightModeliv( GLenum pname, const GLint *params )

Pname | param
GL_LIGHT_MODEL_AMBIENT (fv ou iv seulement) | Intensité ambiante de la scène, par défaut (0.2, 0.2, 0.2, 1.0)
GL_LIGHT_MODEL_LOCAL_VIEWER | 0 : les reflets des spots sont calculés sur l’axe –z ¹ 0 : par rapport à la position de l’œil
GL_LIGHT_MODEL_TWO_SIDE | 0 : seule la face « front » est calculée ¹ 0 : les deux faces d’une surface sont calculées

· Pour un matériau :

Les propriétés de surface d’un polygone peuvent être différentes suivant la face que la face est « front » ou « back » (voir glFrontFace, ou le paragraphe sur le culling).

void glMaterialfv( GLenum face, GLenum pname, const GLfloat *params )

void glMaterialiv( GLenum face, GLenum pname, const GLint *params )

La page man est tellement bien faite que je ne vois pas l’intérêt de la refaire :

Face | Specifies which face or faces are being updated. Must be one of GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK.
Pname | Specifies the material parameter of the face or faces that is being updated. Must be one of GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, GL_EMISSION, GL_SHININESS, GL_AMBIENT_AND_DIFFUSE, or GL_COLOR_INDEXES.
Params | Specifies a pointer to the value or values that pname will be set to.

glMaterial assigns values to material parameters. There are two matched sets of material parameters. One, the front-facing set, is used to shade points, lines, bitmaps, and all polygons (when two-sided lighting is disabled), or just front-facing polygons (when two-sided lighting is enabled). The other set, back-facing, is used to shade back-facing polygons only when two-sided lighting is enabled. Refer to the glLightModel reference page for details concerning one- and two-sided lighting calculations.

[…]

GL_AMBIENT | params contains four integer or floating-point values that specify the ambient RGBA reflectance of the material. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to -1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default ambient reflectance for both front- and back-facing materials is (0.2, 0.2, 0.2, 1.0).
GL_DIFFUSE | params contains four integer or floating-point values that specify the diffuse RGBA reflectance of the material. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to -1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default diffuse reflectance for both front- and back-facing materials is (0.8, 0.8, 0.8, 1.0).
GL_SPECULAR | params contains four integer or floating-point values that specify the specular RGBA reflectance of the material. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to -1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default specular reflectance for both front- and back-facing materials is (0.0, 0.0, 0.0, 1.0).
GL_EMISSION | params contains four integer or floating-point values that specify the RGBA emitted light intensity of the material. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to -1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default emission intensity for both front- and back-facing materials is (0.0, 0.0, 0.0, 1.0).
GL_SHININESS | params is a single integer or floating-point value that specifies the RGBA specular exponent of the material. Integer and floating-point values are mapped directly. Only values in the range [0,128] are accepted. The default specular exponent for both front- and back-facing materials is 0.
GL_AMBIENT_AND_DIFFUSE | Equivalent to calling glMaterial twice with the same parameter values, once with GL_AMBIENT and once with GL_DIFFUSE.

Les normales

Une fois que la source lumineuse est définie, ainsi que les caractéristiques du matériau, il reste encore une chose : l’angle d’incidence de la lumière. Cela ne change pas la couleur d’une surface, mais l’intensité de l’éclairage qu’elle reçoit.

L’angle d’incidence est l’angle entre la normale à une surface et la droite partant de la source lumineuse et passant par le point d’incidence. Il faut impérativement préciser pour chaque point la « normale » de ce point (plus rigoureusement, la normale à la surface dont ce point fait partie).

Le principe est le même que pour glColor, la normale est une des variables d’état d’OpenGL, qui est modifiée par l’une des fonctions suivantes :

void glNormal3b( GLbyte nx, GLbyte ny, GLbyte nz )

void glNormal3d( GLdouble nx, GLdouble ny, GLdouble nz )

void glNormal3f( GLfloat nx, GLfloat ny, GLfloat nz )

void glNormal3i( GLint nx, GLint ny, GLint nz )

void glNormal3s( GLshort nx, GLshort ny, GLshort nz )

void glNormal3bv( const GLbyte *v )

[...]

Les paramètres correspondent au coordonnées du vecteur normal. ATTENTION : il doit être UNITAIRE (de norme 1).

Il est possible de rendre unitaire automatiquement les vecteurs entrés, mais cela entraîne un calcul supplémentaire :

glEnable(GL_NORMALIZE);

Exemple

L’exemple le plus simple :

void init ()

{

glClearColor (0.0f, 0.0f, 0.0f, 0.0f);

GLfloat ambientProperties[] = {0.9f, 0.9f, 0.9f, 1.0f};

GLfloat positionProperties[] = {0.0f, -5.0f, 0.0f, 0.0f};

glLightfv( GL_LIGHT0, GL_DIFFUSE, ambientProperties);

glLightfv( GL_LIGHT0, GL_POSITION, positionProperties);

glEnable(GL_LIGHT0);

glEnable(GL_LIGHTING);

}

void display (void)

{

static timeb tt ,t;

static int total;

GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 1.0f};

GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 1.0f};

GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 1.0f};

glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

ftime(&t);

total += (t.time - tt.time)*1000 + (t.millitm - tt.millitm);

total %= 360*300;

tt = t;

glPushMatrix();

glRotatef((total/30.0f), 0.5f, 0.3f, 0.8f);

glRotatef((total/50.0f), 0.5f, -0.3f, 0.8f);

glBegin(GL_QUADS);

glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, Rouge);

glNormal3d(0.0,0.0,1.0);

glVertex3d(-1, 1, 1);

glVertex3d(-1, -1, 1);

glVertex3d( 1, -1, 1);

glVertex3d( 1, 1, 1);

glNormal3d(0.0,0.0,-1.0);

glVertex3d( 1, 1, -1);

glVertex3d( 1, -1, -1);

glVertex3d( -1, -1, -1);

glVertex3d( -1, 1, -1);

glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, Vert);

glNormal3d(1.0,0.0,0.0);

glVertex3d( 1, 1, 1);

glVertex3d( 1, -1, 1);

glVertex3d( 1, -1, -1);

glVertex3d( 1, 1, -1);

glNormal3d(-1.0,0.0,0.0);

glVertex3d( -1, 1, 1);

glVertex3d( -1, 1, -1);

glVertex3d( -1, -1, -1);

glVertex3d( -1, -1, 1);

glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, Bleu);

glNormal3d(0.0,-1.0,0.0);

glVertex3d( -1, -1, 1);

glVertex3d( -1, -1, -1);

glVertex3d( 1, -1, -1);

glVertex3d( 1, -1, 1);

glNormal3d(0.0,1.0,0.0);

glVertex3d( -1, 1, 1);

glVertex3d( 1, 1, 1);

glVertex3d( 1, 1, -1);

glVertex3d( -1, 1, -1);

glEnd();

glPopMatrix();

glutSwapBuffers();

}

void reshape (int width, int height)

{

glViewport (0, 0, width, height);

glMatrixMode (GL_PROJECTION);

glLoadIdentity ();

gluPerspective (45.0, ((GLfloat) width) / ((GLfloat) height),1.0,10.0);

glMatrixMode (GL_MODELVIEW);

glLoadIdentity ();

gluLookAt (0.0, -5.0, 0.0,

0.0, 0.0, 0.0,

0.0, 0.0, 1.0);

init();

glEnable(GL_DEPTH_TEST);

glEnable(GL_POLYGON_SMOOTH);

glutPostRedisplay();

}

Pour voir l’effet des différents paramètres, voir les très bons exemples de www.cs.utah.edu/~narobins/tutors/tutor, et le lightlab de www.student.nada.kth.se/~nv91-gta/OpenGL/projects/lightlab/.

8. Fog (brouillard)

C’est ce qui simule les effets atmosphériques (je ne vous apprends rien là) comme la fumée, la pollution etc. On s’en sert aussi pour rendre les effets de vision limitée (en même temps, cela permet de virer de la scène une partie des objets 3D trop loin et d’accélérer le rendu).

Le fonctionnement du brouillard

Lorsque le brouillard est activé, les objets qui sont de plus en plus loin commencent à se fondre dans la couleur du brouillard. On peut contrôler aussi la densité du brouillard pour déterminer le rythme avec lequel les objets sont estompé lorsque la distance augmente.

Le brouillard est appliqué à la scène après les transformations matricielles, la lumière, et les textures, donc il affecte les objets transformés (ie avec texture…).

L’utilisation du brouillard

C’est très simple. On l’active en passant GL_FOG comme attribut de glEnable() et on la couleur et l’équation de contrôle de la densité du brouillard grâce à :

void glFogf( GLenum pname, GLfloat param )

void glFogi( GLenum pname, GLint param )

void glFogfv( GLenum pname, const GLfloat *params )

void glFogiv( GLenum pname, const GLint *params )

avec pname = GL_FOG_MODE, GL_FOG_DENSITY, GL_FOG_START, GL_FOG_END, ou GL_FOG_COLOR, et param la nouvelle valeur.

Exemple :

GlEnable(GL_FOG) ;

GLfloat fogcolor[4] = {0.5, 0.5, 0.5, 1} ;

GLint fogmode = GL_EXP ;

glFogi (GL_FOG_MODE, fogmode) ;

glFogfv(GL_FOG_COLOR, fogcolor) ;

glFogf(GL_FOG_DENSITY, 0.35) ;

glFogf(GL_FOG_START, 1.0) ;

glFogf(GL_FOG_END, 5.0) ;

Les équations

Le brouillard se sert d’une variable f variant de 0 (brouillard complet) à 1 (transparent comme de l’eau de roche). Les 3 équations sont :

· GL_LINEAR : f = (end – z) / (end – start)

· GL_EXP : f = e-density * z

· GL_EXP2 : f = e-(density * z)²

9. Transparence et alpha-blending

Plusieurs fois déjà des couleurs ont été présentée sous forme RGBA : rouge, vert, bleu et alpha. Si les trois premières coordonnées sont évidentes, la quatrième quant à elle sert à indiquer la « transparence ».

Activation

Si pour l’instant rien n’a été dit sur les valeurs de l’alpha, c’est simplement parce que par défaut OpenGL s’en fiche.

Pour activer le canal alpha, il faut appeler comme d’habitude glEnable :

glEnable(GL_BLEND) ;

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) ; // expliqué après

Ensuite il suffit de changer la valeur de A suivant la transparence.

Exemple

void init ()

{

glClearColor (0.0f, 0.0f, 0.0f, 0.0f);

glEnable(GL_BLEND) ;

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

}

void display (void)

{

static timeb tt ,t;

static int total;

GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 0.5f};

GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 0.5f};

GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 0.5f};

// […]

glBegin(GL_QUADS);

glColor4fv(Rouge);

// […] (dessin cube)

glColor4fv(Vert);

// […]

glColor4fv(Bleu);

// […]

glEnd();

glPopMatrix();

glutSwapBuffers();

}

Paramètres

La paramétrage principal est effectué par la fonction :

void glBlendFunc( GLenum sfactor, GLenum dfactor )

Si la fonction s’appelle « blend » (« mélanger » en anglais, ici en parlant des couleurs) et pas « transparency », c’est que la transparence n’est qu’une application de l’alpha-blending. C’est de loin la principale.

Néanmoins ce que fait réellement l’alpha-blending, c’est mélanger la couleur déjà affichée avec une nouvelle. Et comme dans OpenGL, la rapidité du rendu est prioritaire sur la spécialisation des fonctions, il est bon de savoir comment cela fonctionne.

Le mélange est précisé par un appel à glBlendFunc, avec le premier paramètre indiquant par quelle valeur multiplier (canal par canal) la nouvelle couleur, le second la couleur déjà affichée. La nouvelle couleur affichée est la somme de ces deux multiplications.

L’indice « s » indique que la nouvelle couleur est concernée, « d » qu’il s’agit de la couleur déjà affichée.

La couleur affichée est : C = CsS + CdD. S et D étant paramétrés respectivement par sfactor et dfactor.

Principales valeurs de S en fonction de sfactor :

Valeur de sfactor | Valeur de S
ZERO | (0,0,0,0)
ONE | (1,1,1,1)
DST_COLOR | (Rd,Gd,Bd,Ad)
ONE_MINUS_DST_COLOR | (1,1,1,1) - (Rd,Gd,Bd,Ad)
SRC_ALPHA | (As, As, As, As)
ONE_MINUS_SRC_ALPHA | (1,1,1,1) - (As,As,As,As)
DST_ALPHA | (Ad,Ad,Ad,Ad)
ONE_MINUS_DST_ALPHA | (1,1,1,1) - (Ad,Ad,Ad,Ad)
SRC_ALPHA_SATURATE | (f,f,f,1) avec f = min (As, 1 - Ad)

Principales valeurs de D en fonction de dfactor :

Valeur de sfactor | Valeur de D
ZERO | (0,0,0,0)
ONE | (1,1,1,1)
SRC_COLOR | (Rs,Gs,Bs,As)
ONE_MINUS_SRC_COLOR | (1,1,1,1) - (Rs,Gs,Bs,As)
SRC_ALPHA | (As, As, As, As)
ONE_MINUS_SRC_ALPHA | (1,1,1,1) - (As,As,As,As)
DST_ALPHA | (Ad, Ad, Ad, Ad)
ONE_MINUS_DST_ALPHA | (1,1,1,1) - (Ad,Ad,Ad,Ad)
SRC_ALPHA_SATURATE | (f,f,f,1) avec f = min (As, 1 - Ad)

Les paramètres les plus courants sont (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA).

Cela représente bien la transparence codée dans le canal Alpha : la couleur affichée est multipliée par la valeur de transparence (si 0, complètement transparent, si 1 opaque), et la couleur en dessous est multipliée par le complément.

Ordre des primitives et depth-buffer

Le problème avec cette manière de rendre les objets transparents, c’est qu’il faut dessiner les surfaces dans l’ordre d’apparition pour avoir un meilleur rendu. En plus, si le depth-buffer est activé, les surfaces recouvertes sont directement éliminées au lieu d’être transparentes. Le plus simple, c’est de désactiver avec glDisable(GL_DEPTH_TEST) le depth-buffer lorsque des surfaces transparentes sont affichées.

10. Listes

Commandes OpenGL

Certains objets sont toujours les mêmes. Comme OpenGL traduit toutes les séquences entrées, il est intéressant de créer des listes pour regrouper les instructions. Cela accélère légèrement l’affichage et réduit le code. Une liste est alors codée par un entier (un seul entier est alors envoyé pour un objet quelconque).

En pratique :

· d’abord réserver des numéros de listes avec :
GLuint glGenLists( GLsizei range )
où range est le nombre de numéros de listes à réserver. Les numéros réservés vont de la valeur renvoyée à cette valeur + range.

· ensuite appeler :
void glNewList( GLuint list, GLenum mode )
avec pour list le numéro de la liste à créer, et mode = GL_COMPILE ou GL_COMPILE_AND_EXECUTE. Les deux modes créés la liste, mais le second en plus l’éxécute (l’affiche).

· mettre toutes les routines souhaitées (glBegin, glColor, glVertex…), elles sont enregistrées dans la liste. Certaines commandes ne peuvent pas être enregistrées dans une liste, mais il y en a peu et c’est plutôt logique (par exemple glGenList, glFlush, glNewlist…).

· terminer par :
void glEndList( void )

Pour afficher une liste, il faut appeler :

void glCallList( GLuint list )

ou

void glCallLists( GLsizei n, GLenum type, const GLvoid *lists)

pour afficher plusieurs listes d’un coup (type indique le type du tableau passé en paramètre : GL_BYTE, GL_INT…).

Reste à effacer une liste lorsqu’elle n’est plus utile :

void glDeleteLists( GLuint list, GLsizei range )

dont les paramètres sont les même que glGenList.

Exemple

Par exemple le cube peut être mis dans une liste :

void init()

{

GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 0.5f};

GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 0.5f};

GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 0.5f};

cube = glGenLists(1);

glNewList(cube, GL_COMPILE);

glBegin(GL_QUADS);

glColor4fv(Rouge);

glNormal3d(0.0,0.0,1.0);

glVertex3d(-1, 1, 1);

glVertex3d(-1, -1, 1);

glVertex3d( 1, -1, 1);

glVertex3d( 1, 1, 1);

glNormal3d(0.0,0.0,-1.0);

glVertex3d( 1, 1, -1);

// […]

glEnd();

glEndList();

}

void display (void)

{

// […]

glPushMatrix();

glRotatef((total/30.0f), 0.5f, 0.3f, 0.8f);

glRotatef((total/50.0f), 0.5f, -0.3f, 0.8f);

glCallList(cube);

glPopMatrix();

glutSwapBuffers();

}

11. Textures

Jusqu’ici, les surfaces étaient simplement coloriées, ce qui ne permet pas d’avoir des rendus très réalistes. Les textures améliore considérablement le rendu. Le principe est de remplir une surface avec une image bitmap correspondant à une photo de la surface.

Principe général

Un texture est appliquée à une surface de la manière suivante :

· d’abord il faut indiquer quelle image il va falloir appliquer (ce qui revient à donner un pointeur vers l’image).

· ensuite, avant d’entrer chaque point (vertex) de la surface, il faut préciser où se situe ce point dans la texture, c’est le même principe qu’avant avec glColor. Cela revient en quelque sorte à mettre des épingles sur la texture

OpenGL se charge ensuite d’appliquer la texture, en faisant correspondre les vertex avec les « épingles ».

En OpenGL

Dans ce cours, les routines de chargement d’images en mémoire ne seront pas expliquées. Ce dont à besoin OpenGL, c’est d’une image non compressée chargée sous la forme d’un tableau en mémoire. Il existe de nombreuses routines toutes faites pour charger les principaux formats d’images (BMP, PCX, GIF, PNG…).

Spécifier l’image

Pour spécifier la texture à utiliser, appeler :

void glTexImage2D( GLenum target, GLint level, GLint components, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels )

Avec :

Target | GL_TEXTUTE_2D
Level | Indique le niveau de mipmapping, expliqué plus loin dans ce cours
Components | Nombre de composantes dans la texture (1, 2, 3 ou 4). Généralement 3 (pour RGB) ou 4 (pour RGBA).
Width | Largeur de l’image. Doit être 2n + 2 (dépend de bordure).
Height | Hauteur de l’image. Doit être 2m + 2 (dépend de bordure).
Bordure | Indique la largeur de la bordure : 0 ou 1
Format | Codage des couleurs : GL_COLOR_INDEX, GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA, GL_LUMINANCE, ou GL_LUMINANCE_ALPHA
Type | Type du tableau : GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, ou GL_FLOAT
Pixel | Pointeur vers l’image.

Les « épingles »

La première chose à faire, c’est d’activer les textures :

glEnable(GL_TEXTURE_2D) ;

Pour poser une épingle, avant un vertex il faut appeler :

void glTexCoord2f( GLfloat s, GLfloat t )

ou l’une de ses variantes.

Les coordonnées (s, t) indiquent la correspondance entre le prochain vertex entré et la texture. En OpenGL, le repère d’une texture fait toujours [0..1] sur [0..1], mais s et t peuvent sortir des ces intervalles, l’effet dépend alors du paramètrage choisi.

Paramétrage

Application de la texture

Les paramètres d’application d’une texture sont :

Nom | Type | Valeur possibles | Signification
GL_TEXTURE_WRAP_S | Entier | GL_CLAMP, GL_CLAMP_TO_EDGE, GL_REPEAT | Indique ce qui se passe si une coordonnée s sort de l’intervalle [0..1]. Soit la texture est répètée (mosaïque), soit seulement le bord est répété.
GL_TEXTURE_WRAP_T | Entier | Idem | Idem pout t
GL_TEXTURE_MIN_FILTER | Entier | NEAREST, LINEAR, NEAREST_MIPMAP_NEAREST, NEAREST_MIPMAP_LINEAR, LINEAR_MIPMAP_NEAREST, LINEAR_MIPMAP_LINEAR | Indique la qualité de l’agrandissement d’une texture. LINEAR est meilleur que NEAREST, mais plus lent.
GL_TEXTURE_MAG_FILTER | Entier | NEAREST, LINEAR | Idem en réduction

Il existe d’autres paramètres, mais ceux-ci sont de loin les plus importants.

Pour changer un paramètre, il faut appeler :

void glTexParameteri(GLenum target, GLenum pname, GLint param )

avec target = GL_TEXTURE_2D, pname l’un des noms de paramètres, et param la nouvelle valeur.

glColor

La couleur courante (spécifiée par glColor), joue un rôle différent suivant les paramètres passés à :

void glTexEnvi( GLenum target, GLenum pname, Glint param )

avec target = GL_TEXTURE_ENV, pname = GL_TEXTURE_ENV_MODE, et param = GL_REPLACE, GL_MODULATE, GL_DECAL, ou GL_BLEND.

Il y a beaucoup de cas suivant le nombre de composantes de la texture.

Dans le cas de 3 ou 4 composantes (RGB ou RGBA), les modes les plus courants sont :

· GL_REPLACE, la couleur courante n’intervient pas

· GL_MODULATE, la couleur courante multiplie la texture. Il est alors possible de changer très facilement l’intensité d’une texture.

Exemple

Pour simplifier (et garantir la portabilité), le format d’image est ici le « Raw », c’est-à-dire l’image seule. Paint Shop Pro gère très bien ce format. Le problème est que la taille de l’image n’est pas inclus dans ce fichier.

void init()

{

char buffer[32*32*3];

FILE *f = fopen("../Data/Marbre.raw", "rb");

if (f)

{

fread(buffer, 3072, 1, f);

fclose(f);

glTexImage2D(GL_TEXTURE_2D,0,3, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer);

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

glEnable(GL_TEXTURE_2D);

}

cube = glGenLists(1);

glNewList(cube, GL_COMPILE);

glBegin(GL_QUADS);

glNormal3d(0.0,0.0,1.0);

glTexCoord2f(0.0, 1.0); glVertex3d(-1, 1, 1);

glTexCoord2f(1.0, 1.0); glVertex3d(-1, -1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( 1, -1, 1);

glTexCoord2f(0.0, 0.0); glVertex3d( 1, 1, 1);

glNormal3d(0.0,0.0,-1.0);

glTexCoord2f(0.0, 1.0); glVertex3d( 1, 1, -1);

glTexCoord2f(0.0, 0.0); glVertex3d( 1, -1, -1);

glTexCoord2f(1.0, 0.0); glVertex3d( -1, -1, -1);

glTexCoord2f(1.0, 1.0); glVertex3d( -1, 1, -1);

glNormal3d(1.0,0.0,0.0);

glTexCoord2f(0.0, 0.0); glVertex3d( 1, 1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( 1, -1, 1);

glTexCoord2f(1.0, 1.0); glVertex3d( 1, -1, -1);

glTexCoord2f(0.0, 1.0); glVertex3d( 1, 1, -1);

glNormal3d(-1.0,0.0,0.0);

glTexCoord2f(0.0, 0.0); glVertex3d( -1, 1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( -1, 1, -1);

glTexCoord2f(1.0, 1.0); glVertex3d( -1, -1, -1);

glTexCoord2f(0.0, 1.0); glVertex3d( -1, -1, 1);

glNormal3d(0.0,-1.0,0.0);

glTexCoord2f(0.0, 0.0); glVertex3d( -1, -1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( -1, -1, -1);

glTexCoord2f(1.0, 1.0); glVertex3d( 1, -1, -1);

glTexCoord2f(0.0, 1.0); glVertex3d( 1, -1, 1);

glNormal3d(0.0,1.0,0.0);

glTexCoord2f(0.0, 0.0); glVertex3d( -1, 1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( 1, 1, 1);

glTexCoord2f(1.0, 1.0); glVertex3d( 1, 1, -1);

glTexCoord2f(0.0, 1.0); glVertex3d( -1, 1, -1);

glEnd();

glEndList();

}

MipMapping

Le MipMapping consiste à avoir la même texture à plusieurs échelles, de façon à limiter les transferts mémoires lors du plaquage de texture (inutile de transférer une texture 256x256 si l’objet dessiné est très loin dans le décor et ne fait plus que du 5x5).

En théorie, le programmeur devrait avoir 3 ou 4 fois la même texture, mais avec une résolution différente. Il faut alors entrer toutes les textures à chaque fois, en changeant le paramètre level de glTextImage2D (sachant que 0 est l’image la plus grande, et les suivantes sont ses réductions). Lorsque la texture est appliqué, OpenGL soit choisi l’image de taille la plus proche de la surface à afficher (NEAREST_MIPMAP), soit fait une sorte de moyenne entre l’image de taille juste au dessus et celle juste en dessous (LINEAR_MIPMAP).

En pratique, il y a une fonction glu :

int gluBuild2DMipmaps(GLenum target, GLint components, GLint width, GLint height, GLenum format, GLenum type, const void *data )

Les paramètres sont les mêmes que pour glTexImage2D, sauf qu’il est cette fois possible d’utiliser des images de dimensions qui ne sont pas des puissances de 2. L’image passée est celle de meilleure qualité (level = 0).

Chargement dans la carte vidéo

L’intérêt du MipMapping est très limité si les textures ne sont pas stockées dans la carte vidéo.

Le principe est similaire à celui des listes :

void glGenTextures( GLsizei n, GLuint * textures)

permet de réserver n textures, les numéros sont alors placés dans le tableau pointé par textures.

Ensuite le numéro de la texture courante est changé par :

void glBindTexture( GLenum target, GLuint texture)

avec target = GL_TEXTURE_2D et texture le numéro de la texture à activer. Ensuite toutes les fonctions faisant intervenir une texture utilisent la texture active.

Les textures sont libérées par :

void glDeleteTextures(GLsizei n, const GLuint * textures )

Pour résumé, une texture est le plus souvent chargée par :

glGenTextures(1, &Texture_num);

glBindTexture(GL_TEXTURE_2D, Texture_num);

gluBuild2DMipmaps(GL_TEXTURE_2D, 3, width, height(), GL_RGB, GL_UNSIGNED_BYTE, data);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

Ensuite pour activer une texture, avant un glBegin :

glBindTexture(GL_TEXTURE_2D, Texture_num);

Exemple

void init()

{

char buffer[32*32*3];

FILE *f = fopen("../Data/Marbre.raw", "rb");

if (f)

{

fread(buffer, 3072, 1, f);

fclose(f);

glGenTextures(1, &Texture1);

glBindTexture(GL_TEXTURE_2D, Texture1);

glTexImage2D(GL_TEXTURE_2D,0,3, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer);

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

glEnable(GL_TEXTURE_2D);

}

f = fopen("../Data/Marbre2.raw", "rb");

if (f)

{

fread(buffer, 3072, 1, f);

fclose(f);

glGenTextures(1, &Texture2);

glBindTexture(GL_TEXTURE_2D, Texture2);

glTexImage2D(GL_TEXTURE_2D,0,3, 32, 32, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer);

}

cube = glGenLists(1);

glNewList(cube, GL_COMPILE);

glBindTexture(GL_TEXTURE_2D, Texture2);

glBegin(GL_QUADS);

glNormal3d(0.0,0.0,1.0);

glTexCoord2f(0.0, 1.0); glVertex3d(-1, 1, 1);

glTexCoord2f(1.0, 1.0); glVertex3d(-1, -1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( 1, -1, 1);

glTexCoord2f(0.0, 0.0); glVertex3d( 1, 1, 1);

glNormal3d(0.0,0.0,-1.0);

glTexCoord2f(1.0, 0.0); glVertex3d( 1, 1, -1);

glTexCoord2f(1.0, 1.0); glVertex3d( 1, -1, -1);

glTexCoord2f(0.0, 1.0); glVertex3d( -1, -1, -1);

glTexCoord2f(0.0, 0.0); glVertex3d( -1, 1, -1);

glNormal3d(1.0,0.0,0.0);

glTexCoord2f(1.0, 1.0); glVertex3d( 1, 1, 1);

glTexCoord2f(0.0, 1.0); glVertex3d( 1, -1, 1);

glTexCoord2f(0.0, 0.0); glVertex3d( 1, -1, -1);

glTexCoord2f(1.0, 0.0); glVertex3d( 1, 1, -1);

glNormal3d(-1.0,0.0,0.0);

glTexCoord2f(0.0, 0.0); glVertex3d( -1, 1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( -1, 1, -1);

glTexCoord2f(1.0, 1.0); glVertex3d( -1, -1, -1);

glTexCoord2f(0.0, 1.0); glVertex3d( -1, -1, 1);

glEnd();

glBindTexture(GL_TEXTURE_2D, Texture1);

glBegin(GL_QUADS);

glNormal3d(0.0,-1.0,0.0);

glTexCoord2f(0.0, 0.0); glVertex3d( -1, -1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( -1, -1, -1);

glTexCoord2f(1.0, 1.0); glVertex3d( 1, -1, -1);

glTexCoord2f(0.0, 1.0); glVertex3d( 1, -1, 1);

glNormal3d(0.0,1.0,0.0);

glTexCoord2f(0.0, 0.0); glVertex3d( -1, 1, 1);

glTexCoord2f(1.0, 0.0); glVertex3d( 1, 1, 1);

glTexCoord2f(1.0, 1.0); glVertex3d( 1, 1, -1);

glTexCoord2f(0.0, 1.0); glVertex3d( -1, 1, -1);

glEnd();

glEndList();

}

Priorités des textures

Comme il se peut que toutes les textures ne rentrent pas dans la carte vidéo, il est possible de donner des priorités au chargement des textures en mémoire vidéo.

void glPrioritizeTextures( GLsizei n, GLuint * textures, GLclampf * priorities)

avec priorities un tableau de n flottants appartenant à [0..1], 0 étant la priorité la plus faible et 1 la plus forte.

12. Les opérations sur les pixels

Introduction

Tout ce qu’on va voir ici concerne l’affichage de bitmaps, la copie et l’affichage de blocs de pixels et la façon dont les pixels sont stockés en mémoire.

L’utilisation la plus courante en OpenGL du transfert de pixels est l’ajout d’images qui recouvrent la scène (photo d’un cockpit, nom du joueur, score…). Il est plus facile des les afficher en utilisant les coordonnées écrans (par exemple 800x600), il faut alors repasser en mode 2D (ce qui ne fait que changer la matrice de projection, tout ce qui est 3D ne bouge pas) :

glMatrixMode(GL_PROJECTION) ;

glLoadIdentity() ;

gluOrtho2D(0.0, (Glfloat) width, 0.0, (Glfloat) height) ;

glMatrixMode(GL_MODEL_VIEW) ;

glLoadIdentity() ;

La Raster Position

La position actuelle de rastérisation est l’origine du prochain bitmap affiché. Le bitmap est affiché au dessus et à droite de cette position. Exemple :

glRasterPos2i(20, 20) ;

La façon de trouver la position est la même que celle de glVertex dans le cas ou l’environnement est en 3D. Si la position est hors du clipping (zone d’affichage), la position est dite invalide. Pour obtenir la Raster Position actuelle et sa validité , on utilise :

GLfloat vector[4] ;

glGetFloatv(GL_CURRENT_RASTER_POSITION, vector) ;

glGetBooleanv(GL_CURRENT_RASTER_POSITION_VALID) ;

Dessiner le bitmap

Après avoir choisi la Raster Position, on peut dessiner le bitmap grâce à l’instruction glBitmap dont le prototype est :

void glBitmap (GLsizei width, GLsizei height, GLfloat Xbo Ybo Xbi Ybi, const GLubyte *bitmap) ;

Si la Raster Position est invalide, rien n’est affiché. On utilise Xbo et Ybo pour définir l’origine du bitmap par rapport à la Raster Position. Xbi et Ybi sont les incréments à la Raster Position après avoir affiché le bitmap. Pour choisir la couleur d’affichage du bitmap, on utilise glColor juste avant.

Le gros problème de cette fonction est qu’elle ne gère pas le format RGB ou RGBA (seulement le mode 8 bits). Les fonctions des paragraphes suivants sont un peu plus lourdes à manipuler, mais plus générales.

Lire, écrire et copier des images

Pour lire une image, la fonction glReadPixels doit être utilisée :

void glReadPixels (Glint x, y, Glsizei width, height, Glenum format, type, Glvoid *pixels) ;

x et y représentent le coin bas-gauche de l’image à lire. Width et height sont ses dimensions. Le format et le type servent à définir le type de structure qui sera utilisé pour le stockage. Les différents types de format sont :

Format

GL_COLOR_INDEX

GL_RGB Dans l’ordre : Rouge, Vert, Bleu

GL_RGBA RGB et après Alpha

GL_RED

GL_GREEN

GL_BLUE

GL_ALPHA

GL_LUMINANCE

GL_LUMINANCE_ALPHA luminance suivi de alpha

GL_STENCIL_INDEX

GL_DEPTH_COMPONENT

Type

GL_UNSIGNED_BYTE (8bits)

GL_BYTE (8 bits)

GL_BITMAP (même format que pour glBitmap)

GL_UNSIGNED_SHORT (16 bits)

GL_SHORT (16 bits)

GL_UNSIGNED_INT (32 bits)

GL_INT (32 bits)

GL_FLOAT (flottant en simple précision)

! Il faut bien faire attention de faire correspondre le type de *pixel avec celui indiqué par le format et le type.

Pour écrire une image, on utilise la fonction glDrawPixel :

void glDrawPixels(Glsizei widht, height, Glenum format, type, const Glvoid *pixels) ;

Cela dessine un rectangle contenant l’image pointée par *pixels à la Raster Position et de la taille widht * height. Les valeurs de format et de type sont les mêmes que celles de glReadPixels.

La fonction glCopyPixels permet de copier un rectangle d’un buffer spécifié par le cinquième paramètre (GL_COLOR, GL_STENCIL ou GL_DEPTH) vers la raster position.

void glCopyPixels(Glint x, y, Glsizei width, height, Glenum buffer) ;

Agrandissement, réduction et réflexion

Normalement, chaque pixel d’une image est écrit dans un pixel sur l’écran. La fonction

void glPixelZoom(Glfloat, zoomx, zoomy) ;

permet de modifier la taille que chaque pixel prend sur l’écran. Si on donne 2 pour chaque argument, chaque pixel est représenté à l’écran par 4 pixels. Des valeurs non entières et négatives sont permises. Les facteurs négatifs de zoom font une réflexion par rapport à la raster position.

13. Tests de validation et stencil buffer

Chaîne de tests

Il est tant de voir la liste complète des tests de validation d’un point :

à Scissor Test à Alpha Test à Stencil Test à Depth Test à […] à framebuffer

Scissor test

Le scissor test est paramétré par :

void glScissor( GLint x, GLint y, GLsizei width, GLsizei height )

Cela permet de limiter l’affichage à un rectangle dont le coin haut – gauche à pour coordonnées (x, y) dans la fenêtre. Par défaut (x, y) = (0, 0) et width et height ont la même taille que la fenêtre.

Le test est activé par : glEnable(GL_SCISSOR_TEST)

Alpha test

Un point peut être éliminé par sa valeur alpha avec :

void glAlphaFunc( GLenum func, GLclampf ref )

avec func = GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, ou GL_ALWAYS

Ne pas oublier, comme toujours : glEnable(GL_ALPHA_TEST)

Stencil test

Ce test est plus complexe, mais plus utile que les deux précédents.

Tout comme le depth-buffer, le stencil-buffer est un espace mémoire de la taille du framebuffer. Mais cette fois son utilisation est libre.

Sous glut, ne pas oublier de le rajouter à l’initialisation :

glutInitDisplayMode (GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH | GLUT_STENCIL)

Effacement

D’abord, le stencil étant comme le depth un buffer, il faut (le plus souvent) l’effacer à chaque image avec :

glClear(… | GL_STENCIL_BUFFER_BIT) ;

La valeur de remplissage est paramétrable avec :

void glClearStencil( GLint s )

Initialisation

Comme pour tous les tests, il faut appeler :

glEnable(GL_STENCIL_TEST) ;

Le test est paramétré par deux fonctions :

void glStencilFunc( GLenum func, GLint ref, GLuint mask)

qui sert à indiquer le test à effectuer, les paramètres sont :

· func = GL_NEVER, GL_LESS, GL_LEQUAL, GL_GREATER, GL_GEQUAL, GL_EQUAL, GL_NOTEQUAL, ou GL_ALWAYS

· ref = valeur de référence du test

· mask = masque binaire à appliquer à la valeur du stencil et à ref pour le test

void glStencilOp( GLenum fail, GLenum zfail, GLenum zpass )

qui sert à indiquer ce qui se passe suivant le résultat du test du stencil et du depth, avec :

· fail indique l’opération à effectuer sur la valeur dans le stencil lorsque le stencil test a échoué. Peut être égal à GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, ou GL_INVERT

· zfail indique de même l’opération à effectuer si le stencil est passé mais pas le depth

· zpass indique l’opération à effectuer si le stencil et le depth est passent

14. Ombres et reflets

La principale application du stencil buffer, ce sont les ombres et les reflets.

Reflets

Principe

Les reflets sur un plan sont très simples : ce n’est qu’un double d’un objet, recouvert d’une surface légèrement transparente pour donner l’illusion qu’il s’agit d’un reflet.

En plus, ce qui simplifie bien les choses, il existe la fonction :

void glScalef( GLfloat x, GLfloat y, GLfloat z )

qui multiplie la matrice courante par une matrice multipliant toutes les coordonnées entrées par les valeurs spécifiées, c’est-à-dire une matrice diagonale (x, y, z, 1).

Dans notre cas, pour effectuer une symétrie par rapport au plan y = 0, il suffit d’entrer glScalef(1.0f, -1.0f, 1.0f).

Seul problème : le reflet peut sortir de la surface miroir ! C’est là que le stencil buffer intervient. La surface réfléchissante est d’abord « virtuellement » dessinée, en fait elle est dessinée normalement sauf que pour empêcher l’affichage on utilise :

void glColorMask( GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha )

avec tous les paramètres à GL_FALSE. Cela permet de changer la valeur du stencil là où la surface va être dessinée.

Les étapes du reflets sont finalement :

· Limitation du reflet à la surface réfléchissante :
glDisable(GL_DEPTH_TEST);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
Dessin de la surface réfléchissante
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glEnable(GL_DEPTH_TEST);

· Dessin du reflet :
glStencilFunc(GL_EQUAL, 1, 0xffffffff); /* draw if ==1 */
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glPushMatrix();
glScalef(1.0, -1.0, 1.0);
respositionnement des lumières
dessin de l’objet reflété
glPopMatrix();
respositionnement des lumières
glDisable(GL_STENCIL_TEST);

· Dessin de la surface réfléchissante :
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Dessin de la surface réfléchissante
glDisable(GL_BLEND);

· Dessin de l’objet

Exemple

Le programme est cette fois complet (même le main y est).

#include <GL/glut.h>

#include <sys/timeb.h>

#include <math.h>

GLint cube = 0;

void idle (void)

{

glutPostRedisplay ();

}

void drawFloor()

{

glColor4f(0.5f, 0.5f, 0.5f, 0.75f);

glBegin(GL_QUADS);

glVertex3f(-4.0, 4.0, 0.0);

glVertex3f( 4.0, 4.0, 0.0);

glVertex3f( 4.0, -4.0, 0.0);

glVertex3f(-4.0, -4.0, 0.0);

glEnd();

}

void SetLight()

{

GLfloat ambientProperties[] = {0.7f, 0.7f, 0.7f, 1.0f};

GLfloat diffuseProperties[] = {1.0f, 1.0f, 1.0f, 1.0f};

GLfloat positionProperties[] = {5.0f, 0.0f, 0.0f, 0.0f};

glLightfv( GL_LIGHT0, GL_AMBIENT, ambientProperties);

glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuseProperties);

glLightfv( GL_LIGHT0, GL_POSITION, positionProperties);

glEnable(GL_LIGHT0);

glEnable(GL_LIGHTING);

}

void display (void)

{

static timeb tt ,t;

static int total;

GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 1.0f};

GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 1.0f};

GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 1.0f};

glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

ftime(&t);

total += (t.time - tt.time)*1000 + (t.millitm - tt.millitm);

total %= 360*300;

tt = t;

glDisable(GL_DEPTH_TEST);

glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

glEnable(GL_STENCIL_TEST);

glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);

glStencilFunc(GL_ALWAYS, 1, 0xffffffff);

drawFloor();

glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

glEnable(GL_DEPTH_TEST);

glStencilFunc(GL_EQUAL, 1, 0xffffffff); /* draw if ==1 */

glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

glPushMatrix();

glScalef(1.0, 1.0, -1.0);

SetLight();

glTranslatef(sin(total/30.0f/180.0f*3.1415f)*5.0f, 0.0f, 5.0f);

glRotatef((total/30.0f), 0.5f, 0.3f, 0.8f);

glRotatef((total/50.0f), 0.5f, -0.3f, 0.8f);

SetLight();

glCallList(cube);

glPopMatrix();

SetLight();

glDisable(GL_STENCIL_TEST);

glEnable(GL_BLEND);

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

glDisable(GL_LIGHTING);

drawFloor();

glEnable(GL_LIGHTING);

glDisable(GL_BLEND);

glPushMatrix();

glTranslatef(sin(total/30.0f/180.0f*3.1415f)*5.0f, 0.0f, 5.0f);

glRotatef((total/30.0f), 0.5f, 0.3f, 0.8f);

glRotatef((total/50.0f), 0.5f, -0.3f, 0.8f);

glCallList(cube);

glPopMatrix();

glutSwapBuffers();

}

void init ()

{

GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 1.0f};

GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 1.0f};

GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 1.0f};

cube = glGenLists(1);

glNewList(cube, GL_COMPILE);

glBegin(GL_QUADS);

glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, Rouge);

glNormal3d(0.0,0.0,1.0);

glVertex3d(-1, 1, 1);

glVertex3d(-1, -1, 1);

glVertex3d( 1, -1, 1);

glVertex3d( 1, 1, 1);

glNormal3d(0.0,0.0,-1.0);

glVertex3d( 1, 1, -1);

glVertex3d( 1, -1, -1);

glVertex3d( -1, -1, -1);

glVertex3d( -1, 1, -1);

glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, Vert);

glNormal3d(1.0,0.0,0.0);

glVertex3d( 1, 1, 1);

glVertex3d( 1, -1, 1);

glVertex3d( 1, -1, -1);

glVertex3d( 1, 1, -1);

glNormal3d(-1.0,0.0,0.0);

glVertex3d( -1, 1, 1);

glVertex3d( -1, 1, -1);

glVertex3d( -1, -1, -1);

glVertex3d( -1, -1, 1);

glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, Bleu);

glNormal3d(0.0,-1.0,0.0);

glVertex3d( -1, -1, 1);

glVertex3d( -1, -1, -1);

glVertex3d( 1, -1, -1);

glVertex3d( 1, -1, 1);

glNormal3d(0.0,1.0,0.0);

glVertex3d( -1, 1, 1);

glVertex3d( 1, 1, 1);

glVertex3d( 1, 1, -1);

glVertex3d( -1, 1, -1);

glEnd();

glEndList();

}

void reshape (int width, int height)

{

glViewport (0, 0, width, height);

glMatrixMode (GL_PROJECTION);

glLoadIdentity ();

gluPerspective (45.0, ((GLfloat) width) / ((GLfloat) height),1.0,100.0);

glMatrixMode (GL_MODELVIEW);

glLoadIdentity ();

gluLookAt (0.0,-7.0,15.0, 0.0, 0.0, 0.0,0.0,0.0, 1.0);

glEnable(GL_DEPTH_TEST);

glEnable(GL_POLYGON_SMOOTH);

SetLight();

glutPostRedisplay();

}

int main (int argc, char *argv[])

{

glutInit (&argc, argv);

glutInitDisplayMode (GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH | GLUT_STENCIL);

glutInitWindowSize (640, 480);

glutCreateWindow ("Cours09.c");

glutDisplayFunc (display);

glutReshapeFunc (reshape);

glutIdleFunc (idle);

init();

glutMainLoop ();

return 0;

}

Matrice « miroir »

L’utilisation de glScale est beaucoup moins pratique dès que la surface n’est pas un plan parallèle à un axe. Heureusment, M.J. Kilgard (de SGI / NVidia) à mis en ligne la fonction :

Ombres

Principe

Le stencil buffer joue toujours son rôle de délimitation, pour l’ombre cette fois. Reste à dessiner l’ombre elle-même, or qu’est-ce qu’une ombre ? C’est la projection de tout un objet sur un plan suivant la direction de la lumière. Cela fait (devrait faire ?) immédiatement penser à « matrice de projection ». Effectivement il suffit de multiplier la matrice ModelView par une matrice de projection pour avoir le résultat voulu.

Quelques détails tout de même :

· l’ombre n’est pas le dessin de l’objet lui-même mais une surface noire (à moitié transparente). Bien que dans la réalité, ce n’est pas exactement cela : c’est tout le reste de la scène qui est plus éclairée. Cependant il est bien plus rapide d’assombrir là où est l’ombre que d’éclaircir tout le reste. S’il n’y a aucune lumière ambiante, l’ombre n’est pas transparente.

· avec cette méthode, il faut faire attention à ne pas assombrir deux fois le même point : ce n’est pas parce que deux surfaces cachent la lumière que l’ombre est deux fois plus sombre ! L’astuce consiste ici à incrémenter le stencil lorsque le stencil test et le depth test passent (dès qu’un point est assombri, il ne fait plus « partie » de la surface).
Cependant, il existe un cas où il faut volontairement oublier ce fait : lorsque l’objet projeté est en partie transparent. Il est alors possible d’avoir un assombrissement proportionnel au nombre de surfaces qui cachent la lumière. Sauf qu’il ne faut pas trop en abuser, le temps de calcul s’allongeant encore.

· le problème de la matrice de projection : elle n’est pas dans glu ! Alors remercions une fois encore M.J. Kilgard pour sa documentation en ligne :

/* Create a matrix that will project the desired shadow. */

enum { X = 0, Y, Z, W };

void shadowMatrix(GLfloat shadowMat[4][4], GLfloat groundplane[4], GLfloat lightpos[4])

{

GLfloat dot;

dot = groundplane[X] * lightpos[X] +

groundplane[Y] * lightpos[Y] +

groundplane[Z] * lightpos[Z] +

groundplane[W] * lightpos[W];

shadowMat[0][0] = dot - lightpos[X] * groundplane[X];

shadowMat[1][0] = 0.f - lightpos[X] * groundplane[Y];

shadowMat[2][0] = 0.f - lightpos[X] * groundplane[Z];

shadowMat[3][0] = 0.f - lightpos[X] * groundplane[W];

shadowMat[X][1] = 0.f - lightpos[Y] * groundplane[X];

shadowMat[1][1] = dot - lightpos[Y] * groundplane[Y];

shadowMat[2][1] = 0.f - lightpos[Y] * groundplane[Z];

shadowMat[3][1] = 0.f - lightpos[Y] * groundplane[W];

shadowMat[X][2] = 0.f - lightpos[Z] * groundplane[X];

shadowMat[1][2] = 0.f - lightpos[Z] * groundplane[Y];

shadowMat[2][2] = dot - lightpos[Z] * groundplane[Z];

shadowMat[3][2] = 0.f - lightpos[Z] * groundplane[W];

shadowMat[X][3] = 0.f - lightpos[W] * groundplane[X];

shadowMat[1][3] = 0.f - lightpos[W] * groundplane[Y];

shadowMat[2][3] = 0.f - lightpos[W] * groundplane[Z];

shadowMat[3][3] = dot - lightpos[W] * groundplane[W];

}

avec shadowMat la matrice à construire, groundpane l’équation du plan où projeter l’ombre (de type ax + by + cz + d = 0), et lightpos comme son nom l’indique (lightpos[3] de préférence à 0).

Et pour les coefficients du plan :

/* Find the plane equation given 3 points. */

enum { A = 0, B, C, D };

void findPlane(GLfloat plane[4], GLfloat v0[3], GLfloat v1[3], GLfloat v2[3])

{

GLfloat vec0[3], vec1[3];

/* Need 2 vectors to find cross product. */

vec0[X] = v1[X] - v0[X];

vec0[Y] = v1[Y] - v0[Y];

vec0[Z] = v1[Z] - v0[Z];

vec1[X] = v2[X] - v0[X];

vec1[Y] = v2[Y] - v0[Y];

vec1[Z] = v2[Z] - v0[Z];

/* find cross product to get A, B, and C of plane equation */

plane[A] = vec0[Y] * vec1[Z] - vec0[Z] * vec1[Y];

plane[B] = -(vec0[X] * vec1[Z] - vec0[Z] * vec1[X]);

plane[C] = vec0[X] * vec1[Y] - vec0[Y] * vec1[X];

plane[D] = -(plane[A] * v0[X] + plane[B] * v0[Y] + plane[C] * v0[Z]);

}

Les étapes de l’ombre sont alors :

· Limitation de l’ombre à la surface :
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
Dessin de la surface
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glEnable(GL_DEPTH_TEST);
glDisable(GL_STENCIL_TEST);

· Dessin de l’objet

· Calcul de la matrice de projection (avec vfloor des points de la surface) :
GLfloat floorPlane[4];
GLfloat floorShadow[4][4];
findPlane(floorPlane, vfloor[1], vfloor[2], vfloor[3]);
shadowMatrix(floorShadow, floorPlane, lightZeroPosition);

·
Changement des paramètres pour dessiner une
surface noire transparente :
glStencilFunc(GL_EQUAL, 1, 0xffffffff);
/* draw if ==1 */
glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA,
GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_LIGHTING);
glDisable(GL_TEXTURE_2D);
glDisable(GL_DEPTH_TEST);
glColor4f(0.0, 0.0, 0.0, 0.5);

· Dessin de l’ombre :
glPushMatrix();
glMultMatrixf((GLfloat *) floorShadow);
dessin de l’objet
glPopMatrix();

Exemple

GLfloat vfloor[4][3] = {-4, 4, 0, 4, 4, 0, 4,-4, 0,-4,-4, 0};

GLfloat positionProperties[] = {0.0f, -3.0f, 5.0f, 0.0f};

void drawFloor()

{

glColor4f(0.5f, 0.5f, 0.5f, 0.75f);

glBegin(GL_QUADS);

glVertex3fv(vfloor[0]);

glVertex3fv(vfloor[1]);

glVertex3fv(vfloor[2]);

glVertex3fv(vfloor[3]);

glEnd();

}

void SetLight()

{

GLfloat ambientProperties[] = {0.7f, 0.7f, 0.7f, 1.0f};

GLfloat diffuseProperties[] = {1.0f, 1.0f, 1.0f, 1.0f};

glLightfv( GL_LIGHT0, GL_AMBIENT, ambientProperties);

glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuseProperties);

glLightfv( GL_LIGHT0, GL_POSITION, positionProperties);

glEnable(GL_LIGHT0);

glEnable(GL_LIGHTING);

}

void display (void)

{

static timeb tt ,t;

static int total;

GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 1.0f};

GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 1.0f};

GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 1.0f};

glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

ftime(&t);

total += (t.time - tt.time)*1000 + (t.millitm - tt.millitm);

total %= 360*300;

tt = t;

glDisable(GL_DEPTH_TEST);

glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

glEnable(GL_STENCIL_TEST);

glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);

glStencilFunc(GL_ALWAYS, 1, 0xffffffff);

drawFloor();

glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);

glEnable(GL_DEPTH_TEST);

glDisable(GL_STENCIL_TEST);

glDisable(GL_LIGHTING);

drawFloor();

glEnable(GL_LIGHTING);

glEnable(GL_STENCIL_TEST);

glStencilFunc(GL_ALWAYS, 2, 0xffffffff);

glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

glPushMatrix();

glTranslatef(sin(total/30.0f/180.0f*3.1415f)*5.0f, 0.0f, 3.0f);

glRotatef((total/30.0f), 0.5f, 0.3f, 0.8f);

glRotatef((total/50.0f), 0.5f, -0.3f, 0.8f);

glCallList(cube);

glPopMatrix();

glStencilFunc(GL_EQUAL, 1, 0xffffffff); /* draw if ==1 */

glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);

glEnable(GL_BLEND);

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

glDisable(GL_LIGHTING);

glDisable(GL_DEPTH_TEST);

glColor4f(0.0, 0.0, 0.0, 0.5);

static GLfloat floorPlane[4];

static GLfloat floorShadow[4][4];

findPlane(floorPlane, vfloor[1], vfloor[2], vfloor[3]);

shadowMatrix(floorShadow, floorPlane, positionProperties);

glPushMatrix();

glMultMatrixf((GLfloat *) floorShadow);

glTranslatef(sin(total/30.0f/180.0f*3.1415f)*5.0f, 0.0f, 3.0f);

glRotatef((total/30.0f), 0.5f, 0.3f, 0.8f);

glRotatef((total/50.0f), 0.5f, -0.3f, 0.8f);

glCallList(cube);

glPopMatrix();

glEnable(GL_DEPTH_TEST);

glDisable(GL_BLEND);

glEnable(GL_LIGHTING);

glDisable(GL_STENCIL_TEST);

glFlush();

glutSwapBuffers();

}

15. Courbes et Surfaces calculées

Présentation

Pour certaines surfaces, comme par exemple les sphères, il se pose un problème : elles ne peuvent être qu’approchées par des triangles. Il faut faire un compromis : soit avoir une sphère très belle, ce qui peut prendre plusieurs milliers de triangles, ou bien avoir peu de triangles mais une sphère pas très sphérique. De plus, pourquoi avoir des surfaces précises si elles sont éloignées ? De toute façon elles ne font que quelques pixels.

D’où l’intérêt d’avoir des surfaces calculées : la précision de la surface est ajustée suivant les besoins (distance par rapport à l’œil), et même peut être ajustée dynamiquement suivant la puissance de la machine (si le programme tourne à 60 fps, une amélioration visuelle est raisonnable).

Quadrics

Présentation

Glu intègre déjà un certain nombre de formes : sphère, cylindre et disque. Le principe est toujours le même :

· Déclaration d’un « quadric » :
GLUquadricObj obj = gluNewQuadric();

· Appel(s) à l’une des fonctions suivantes (dessin immédiat) :
void gluCylinder( GLUquadricObj *qobj, GLdouble baseRadius, GLdouble topRadius, GLdouble height, GLint slices, GLint stacks )
void gluDisk( GLUquadricObj *qobj, GLdouble innerRadius, GLdouble outerRadius, GLint slices, GLint loops )
void gluPartialDisk( GLUquadricObj *qobj, GLdouble innerRadius, GLdouble outerRadius, GLint slices, GLint loops, GLdouble startAngle, GLdouble sweepAngle )
void gluSphere( GLUquadricObj *qobj, GLdouble radius, GLint slices, GLint stacks )

· Destruction avec :
void gluDeleteQuadric( GLUquadricObj *state )

Suivant les paramètres du rendu, il est possible de générer :

· Les normales (pour l’éclairage) :
void gluQuadricNormals( GLUquadricObj *qobj, GLenum normals )
avec normals = GLU_NONE, GLU_FLAT, ou GLU_SMOOTH

· Les coordonnées de texture :
void gluQuadricTexture( GLUquadricObj *qobj, GLboolean textureCoords )

· Le mode de dessin :
void gluQuadricDrawStyle( GLUquadricObj *qobj, GLenum drawStyle )
avec drawStyle = GLU_FILL, GLU_LINE, GLU_SILHOUETTE, ou GLU_POINT

Exemple

GLfloat positionProperties[] = {5.0f, -5.0f, 5.0f, 0.0f};

void SetLight()

{

GLfloat ambientProperties[] = {0.7f, 0.7f, 0.7f, 1.0f};

GLfloat diffuseProperties[] = {1.0f, 1.0f, 1.0f, 1.0f};

glLightfv( GL_LIGHT0, GL_AMBIENT, ambientProperties);

glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuseProperties);

glLightfv( GL_LIGHT0, GL_POSITION, positionProperties);

glEnable(GL_LIGHT0);

glEnable(GL_LIGHTING);

glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, ambientProperties);

}

void display (void)

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

GLUquadricObj *obj = gluNewQuadric();

gluQuadricNormals(obj, GL_SMOOTH);

gluQuadricDrawStyle(obj, GLU_FILL);

gluSphere(obj, 1.0, 20, 20);

gluDeleteQuadric(obj);

glFlush();

glutSwapBuffers();

}

Bézier

OpenGL intègre (ce n’est pas glu cette fois) les courbes et les surfaces de Bézier. Cependant les cartes grand public n’accélèrent pas leur calcul. Pour l’instant…

Le principe d’une courbe de Bézier est le suivant :

- Soit n point de contrôle, la courbe part du premier et arrive au dernier

- La courbe est calculée par récurrence, en rajoutant entre deux points de contrôle leur milieu jusqu’à la résolution de l’affichage. Seul les milieux du dernier niveau de récurrence sont affichés (en fait dès qu’un point, hormis les extrémités la courbe, a servi pour calculer un milieu, il est éliminé). Un petit dessin pour essayer de mieux comprendre :

Le principe est le même pour les surfaces.

Courbe

Fonctions OpenGL

La première chose à faire est de rentrer les points de contrôle :

void glMap1f( GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points )

Target

Indique quel type de coordonnées il faut générer. Les choix sont : GL_MAP1_VERTEX_3, GL_MAP1_VERTEX_4, GL_MAP1_INDEX, GL_MAP1_COLOR_4, GL_MAP1_NORMAL, GL_MAP1_TEXTURE_COORD_1, GL_MAP1_TEXTURE_COORD_2, GL_MAP1_TEXTURE_COORD_3, ou GL_MAP1_TEXTURE_COORD_4

Pour une courbe de Bézier, il faut choisir GL_MAP1_VERTEX_3. Mais il est aussi possible d’utiliser GL_MAP1_COLOR_4 pour créer un dégradé de couleurs : quelle différence entre générer des coordonnées (x, y,z) et (R, G, B) ?

u1, u2

Une courbe est ensuite repérée par un « axe courbe ». Par la suite, la courbe débutera en (u1) et se terminera en (u2)

Stride

Indique le nombre de flottants qui sépare un point de contrôle dans points du suivant. Le seul but de cette valeur est de pouvoir utiliser des tableaux de structure avec d’autres données que les points.

Order

Nombre de points dans le tableau

Points

Pointeur vers les points de contrôle

Il faut appeler glEnable avec le même target pour activer la courbe.

Ensuite la fonction :

void glEvalCoord1f( GLfloat u)

fait l’équivalent d’un glVertex(coordonnées du point repéré par u). Par exemple glEvalCoord1f(u1) à le même effet que glVertex(premier point de contrôle), et de même glEvalCoord1f(u2) à le même effet que glVertex(dernier point de contrôle). La valeur de u n’est pas limitée à [u1..u2]. Si elle sort de cette intervalle, cela prolonge simplement la courbe.

La fonction doit être appelée à l’interieur d’un glBegin / glEnd.

Exemple

void display (void)

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glPushMatrix();

static GLfloat ctrlpoints[4][3] = {

{-4.0, -4.0, -15.0}, {-2.0, 4.0, -15.0},

{ 2.0, -4.0, -15.0}, { 4.0, 4.0, -15.0}};

glColor3f(1.0f,1.0f,1.0f);

glMap1f(GL_MAP1_VERTEX_3, 0.0, 1.0, 3, 4, &ctrlpoints[0][0]);

glEnable(GL_MAP1_VERTEX_3);

glBegin(GL_LINE_STRIP);

for (GLfloat i = 0.0; i <= 1.0; i+=0.05f)

glEvalCoord1f(i);

glEnd();

glDisable(GL_MAP1_VERTEX_3);

glPointSize(5.0);

glColor3f(1.0, 1.0, 0.0);

glBegin(GL_POINTS);

for (int j = 0; j < 4; j++)

glVertex3fv(&ctrlpoints[j][0]);

glEnd();

glPopMatrix();

glFlush();

glutSwapBuffers();

}

void reshape (int width, int height)

{

glViewport (0, 0, width, height);

glMatrixMode (GL_PROJECTION);

glLoadIdentity ();

gluPerspective (45.0, ((GLfloat) width) / ((GLfloat) height),1.0,100.0);

glMatrixMode (GL_MODELVIEW);

glLoadIdentity ();

gluLookAt(0, 0, 0, 0, 0, -1, 0, 1, 0);

glEnable(GL_DEPTH_TEST);

glEnable(GL_POLYGON_SMOOTH);

}

Surface

Fonctions OpenGL

Les fonctions sont les mêmes avec des « 2 » au lieu de « 1 », et le tableau des points de contrôle est à 2 dimensions :

void glMap2f( GLenum target, Glfloat u1, Glfloat u2, GLint ustride, GLint uorder, Glfloat v1, Glfloat v2, GLint vstride, GLint vorder, const GLfloat *points )

void glEvalCoord2f( GLfloat u, GLfloat v)

Il y cependant une fonction supplémentaire bien utile :

void glEvalMesh2( GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2 )

qui est purement et simplement :

glBegin(mode);

for (j = j1; j <= j2; j += 1)

for (i = i1; i <= i2; i += 1)

glEvalCoord2(i·du + u1, j·dv + v1);

glEnd();

avec du = (u2 – u1) / i et dv = (v2 – v1) / j

(mode est au choix : GL_QUAD_STRIP, GL_LINE_STRIP, GL_POINTS).

Exemple

void display (void)

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glPushMatrix();

static GLfloat ctrlpoints[4][4][3] = {

{{-1.5, -1.5, 4.0}, {-0.5, -1.5, 2.0},

{ 0.5, -1.5, -1.0}, { 1.5, -1.5, 2.0}},

{{-1.5, -0.5, 1.0}, {-0.5, -0.5, 3.0},

{ 0.5, -0.5, 0.0}, { 1.5, -0.5, -1.0}},

{{-1.5, 0.5, 4.0}, {-0.5, 0.5, 0.0},

{ 0.5, 0.5, 3.0}, { 1.5, 0.5, 4.0}},

{{-1.5, 1.5, -2.0}, {-0.5, 1.5, -2.0},

{ 0.5, 1.5, 0.0}, { 1.5, 1.5, -1.0}}

};

glEnable(GL_LINE_SMOOTH);

glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, &ctrlpoints[0][0][0]);

glEnable(GL_MAP2_VERTEX_3);

static GLfloat tt= 0;

if ((tt += 0.05f) > 360) tt -= 360.0f;

glTranslatef(0.0f, 0.0f, -10.0f);

glRotatef(tt, 0.7f, 0.0f, 1.0f);

glColor3f(1.0, 1.0, 1.0);

glMapGrid2f(9, 0.0, 1.0, 9, 0.0, 1.0);

glEvalMesh2(GL_LINE, 0, 9, 0, 9);

glDisable(GL_MAP2_VERTEX_3);

glPopMatrix();

glFlush();

glutSwapBuffers();

}

void reshape (int width, int height)

{

glViewport (0, 0, width, height);

glMatrixMode (GL_PROJECTION);

glLoadIdentity ();

gluPerspective (45.0, ((GLfloat) width) / ((GLfloat) height),1.0,100.0);

glMatrixMode (GL_MODELVIEW);

glLoadIdentity ();

gluLookAt(0, 0, 0,

0, 0, -0.5f,

0, 1, 0);

glEnable(GL_DEPTH_TEST);

glEnable(GL_POLYGON_SMOOTH);

}

16. Ce qui n’a pas de place ailleurs

Dessiner deux fois au même endroit

Il se peut qu’il faille dessiner deux fois au même endroit, pour mettre un logo sur un rectangle par exemple. Le depth-buffer pose alors un problème : il n’est pas précis, ou en tout cas pas suffisament précis pour être sûr qu’ancun point du logo ne va être éliminé.

La solution la plus simple : désactiver le depth-test. Mais ce n’est à utiliser que lorsque l’on est sûr qu’il n’y a rien de plus proche de la caméra.

Une autre solution, parfaitement sûre mais plus compliquée, c’est d’utiliser le stencil buffer : dessiner d’abord le logo et changer la valeur du stencil là où le logo est dessiné. Dessiner ensuite le rectangle sur lequel est placé le logo, mais seulement là où le stencil n’a pas bougé.

Dernière solution, la fonction :

void glPolygonOffset(Glfloat factor, Glfloat units)

avec factor qui permet de « lisser » la profondeur (limite la dérivée du depth, sans intérêt ici), et units le nombre « d’unités » de décalage à ajouter au point. L’unité est dépendante de l’implémentation GL, et est spécifiée comme étant « le plus petit décalage tel que deux points ayant la même profonfeur soit quand même disctincts ». Dans le cas qui nous intéresse, cela donne :

glPolygonOffset(0, 1);

Il faut appeler glEnable avec GL_POLYGON_OFFSET_FILL pour activer le décalage des points.

Cette méthode fonctionne assez bien, mais la précision n’atteint pas celle du stencil : si le nombre de calculs (nombre de matrcies dans modelview) est trop différent pour le logo et la surface, il se peut très bien (pour cause de calcul en flottant et d’imprécision du depth) que cette technique ne marche pas. Elle est par contre très fiable si les matrices utilisées sont les mêmes pour le logo et la surface.

Clipping

Il est possible de rajouter des plans de clipping, pour exclure certaines parties du monde dessiné. Il en existe toujours au moins deux : ceux qui limitent l’intervalle de z (automatiquement générés lors de l’appel de gluPerspective).

Un plan quelconque peut être ajouté avec :

void glClipPlane( GLenum plane, const GLdouble *equation )

plane étant GL_CLIP_PLANEi (=GL_CLIP_PLANE0 + i), la valeur max étant GL_MAX_CLIP_PLANES. Equation est un tableau de quatre flottants donnant l’équation du plan (ax + by + cz + d = 0). Le nombre minimal de plans possible est 6 (pour définir un cube).

Pour l’activer : glEnable(GL_CLIP_PLANEi).

La règle pour savoir si un vertex est coupé ou pas est : « If the dot product of the eye coordinates of a vertex with the stored plane equation components is positive or zero, the vertex is in with respect to that clipping plane. Otherwise, it is out. »

Multitexturing

Le but de cette partie est de montrer les possibilités d’OpenGL en combinant plusieurs des fonctions vues jusqu’à présent.

Le « multitexturing » consiste à faire plusieurs rendus d’une même surface en changeant certaines options, pour obtenir des effets lumineux. L’exemple donné ici consiste à dessiner deux fois le même objet, une fois avec une texture non éclairée, puis une deuxième fois pour ajouter un rendu sans texture mais éclairé.

En OpenGL, cela donne à peu près :

glEnable(GL_BLEND);

glDepthFunc(GL_LEQUAL);

glDepthMask(GL_TRUE);

glDisable(GL_LIGHTING);

glDisable(GL_DITHER);

glShadeModel(GL_FLAT);

glEnable(GL_TEXTURE_2D);

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

Draw();

glEnable(GL_LIGHTING);

glDisable(GL_TEXTURE_2D);

glDepthFunc(GL_EQUAL);

glDepthMask(GL_FALSE);

glEnable(GL_DITHER);

glBlendFunc(GL_ONE, GL_ONE);

Draw();

²


1

Introduction à l’openGL

1. Qu’est ce que OpenGL ?

OpenGL est une interface de programmation vers le matériel graphique. Cette interface compte environ 150 commandes qui sont utilisées dans les programmes interactifs en 3D. Etant donné que cette API est à l’origine professionnelle, il y a beaucoup de fonctions qui ne sont toujours pas câblées sur les cartes accélératrices 3D pour le grand public, et qui seront donc émulées en software.

La première particularité d’OpenGL est que la programmation est indépendante du matériel graphique utilisé, et que son domaine d’utilisation n’est pas confiné aux PC/Windows ou Mac. A l’origine développé par Silicon Graphics, OpenGL est aujourd’hui disponible sur les plates-formes x86 et 68000 entre autres, que ce soit sous Windows, Linux ou Mac. La conséquence immédiate est qu’aucune commande de fenêtrage, ou d’une manière générale toute commande dépendant de l’OS, n’est incluse et que seuls des commandes graphiques de bas niveaux existent (dessiner un point, un segment mais pas de sphère par exemple). Tout objet complexe est composé à partir d’un nombre limité de primitives graphiques.

Deuxièmement, OpenGL se base toujours sur d’autres librairies graphiques qui sont en général spécifiques au système d’exploitation ou qui apportent des fonctions complexes par dessus la librairie OpenGL. L’OpenGL Utility Library (GLU) ainsi que l’OpenGL Utility Toolkit (GLUT) en sont des exemples.

Une autre particularité est que certaines implémentations d’OpenGL sont conçues pour que la station sur laquelle on affiche ne soit pas celle sur laquelle tourne le programme. Cette fonctionnalité n’est utilisé que sur les systèmes basés sur l’X, et ne sera donc pas détaillée ici (en gros, cela est possible car OpenGL utilise une architecture client (un programme) – serveur (la carte vidéo et son driver OpenGL)).

Silicon Graphics n’a pas implémenté de version pour Linux de OpenGL. Il convient donc d’utiliser Mesa, qui est une librairie compatible OpenGL (attention, la compatibilité n’est ni garantie ni totale, ....).

2. Les bonnes adresses

Voici quelques bonnes adresse à visiter pour avoir accès à des informations complémentaires et aux manuels de référence :

· OpenGL :

http://www.opengl.org | (site officiel)
http://trant.sgi.com/opengl | (exemples, astuces, trucs utiles ...)
http://reality.sgi.com/opengl | (news, documentation, liens)
http://www.opengl.org/discussion_boards/cgi_directory/Ultimate.cgi | (forum)

· GLUT :

http://reality.sgi.com/mjk/spec3/spec3.html | (description des fonctions)
http://reality.sgi.com/mjk/glut3/glut3.html | (download)

· Mesa :

http://www.mesa3d.org

3. Tout ce dont vous aurez besoin :

  • Les manuels de référence : OpenGL_1_2_1.pdf et Glu1_3.pdf
  • Les drivers OpenGL de base pour Windows (utilisez de préérence ceux de votre carte 3D, si ils fonctionnent. Au passage, si vous arrivez à utiliser les drivers 3dfx Voodoo2 avec ses propres programmes, mailez-moi immédiatement). Il y a tout dans cet éxécutable (fichiers .h, .lib, DLL ...).
  • Les librairies de GLUT et AUX pour tout ceux que la programmation Win32 n'intéresse pas, ou ceux qui ne connaissent pas le Visual Basic ou les MFC.
  • Une API référence en HTML (une page par fonction !!!).
  • Le document WORD dont ce tutoriel s'inspire : OpenGL_cours.zip.


GLUT et AUX : Pourquoi se compliquer la vie ?

Les plus extrémistes d’entre vous (n’est-ce pas Daboul ?) décideront de coder tout un programme (gestion des fenêtres, des threads etc.) sur des bases soit-disant saines, en API Win32 par exemple. Je les plains d’avance. Pour les plus réalistes (paresseux ?), il existe 2 librairies qui se chargent de tout ça : GLUT et AUX.

1. GLUT, l’OpenGL Utility Toolkit

OpenGL ne s’occupant pas des opérations relatives au système ou au fenêtrage, il laisse la place à d’autres API, dont GLUT fait parti. Il existe des implémentations de GLUT pour Windows NT, X-Window et OS/2, ce qui permet de compiler le même code source sur toutes les plates-formes.
GLUT est une interface entre le programme et l’humanoïde qui se trouve derrière l’écran, la souris, ou le joystick. Sa programmation est de type événementielle et chaque agitation de l’humain est considéré comme un événement qui sera géré par des fonctions personnalisées (callback). Pour l'utiliser, il suffit d’inclure « glut.h » à votre programme et compiler votre projet avec la librairie « glut32.lib ». Tous ces fichiers sont fournis en annexe.

1.1. Un exemple

Voici un premier exemple de programme utilisant GLUT :

#include <GL/glut.h> //fonctions GLUT

int main(int argc, char** argv)
{
 glutInit(&argc, argv);
glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
 glutInitWindowSize (800,600);
glutCreateWindow (argv[0]);
glutDisplayFunc (display);
glutReshapeFunc (reshape);
glutIgnoreKeyRepeat(1);
glutKeyboardFunc(keyboard);
glutKeyboardUpFunc(keyboardup);
glutSpecialFunc(special);
glutSpecialUpFunc(specialup) ;
glutMouseFunc(mouse);
glutMotionFunc(motion);
glutPassiveMotionFunc(motion);
glutIdleFunc (IntelligenceArtificielle);
glutMainLoop ();
return(0);
}

1.2. L’initialisation

Initialiser un programme demande, dans l'ordre :

· glutInit(int *argc, char **argv) : A être appelé avant tout autre fonction GLUT.

· glutInitDisplayMode(unsigned int mode) : différencie les mode RGBA et couleur indexé, ainsi que le double-buffering et les buffers à utiliser (depth, stencil, accumulation), que nous étudierons par la suite

· glutInitWindowPostion(int x, int y) : no comment

· glutInitWindowSize(int width, int size) : no comment

· glutCreatWindow(char *string) : crée la fenêtre (et un contexte OpenGL) avec tous les paramètres spécifiés avant.
Elle renvoie un identifiant unique pour la nouvelle fenêtre. Attention : la fenêtre ne sera pas affichée avant l’appel à glutMainLoop().

1.3. Les callbacks

Viennent ensuite les « callback ». Il s’agit de spécifier quels sont les fonctions à appeler suivant les événements qui se produisent. On peut changer les fonctions appelées à n’importe quel moment. Si on spécifie la valeur NULL comme adresse de fonction à utiliser, cela a pour effet de désactiver le « callback » correspondant. Elles sont par défaut à NULL. Les plus utilisés sont :

· glutDisplayFunc(void(*func)(void)) : indique la fonction à utiliser lors de l’affichage graphique.

· glutReshapeFunc(void(*func)(int w, int h)) : si la fenêtre est redimentionnée

· glutKeyboardFunc(void(*func)(unsigned char key, int x, int y)) et glutMouseFunc(void(*func)(int button, int state, int x, int y)) : événement clavier et souris.

Il existe d'autres évènements pour la souris, comme par exemple une fonction qui s'active dés que la souris bouge sur l'écran etc ...

1.4. Les Animations

La fonction glutIdleFunc(void (*func)(void)) est lancée lorsque le système n’a plus rien à faire (affichage terminé, pas d’évènements clavier … NULL passé en paramètre désactive cette fonction. Elle sert à faire des animations.

1.5. Les fonctions de dessin 3D évoluées

GLUT peut se charger de dessiner des objets 3D centrés sur l’origine en mode fil de fer (wire) ou solide (solid). Les objets sont : cone, icosahedron, teapot (le fameux), cube, octahedron, tetrahedron, dodecahedron, sphere, tore.
Pour dessiner un cube ou une sphère, on utilise par exemple les fonctions :

void glutWireCube(Gldouble size) ;

void glutSolidCube(Gldouble size) ;

void glutWireSphere(Gldouble radius, Glint slices, Glint stacks) ;

void glutSolidSphere(Gldouble radius, Glint slices, Glint stacks) ;

1.6. Exécuter le programme

Pour exécuter le programme et voir réellement quelque chose s’afficher après l’initialisation, il faut appeler la fonction : glutMainLoop(void). On rentre alors dans la boucle infinie d’exécution. Ca doit être à peut de choses prêt la dernière ligne du programme. On ne pourra sortir de cette boucle que par un exit().

Pour connaître la liste des fonctions utilisables de GLUT, reportez-vous à la page http://reality.sgi.com/mjk/spec3/spec3.html

2. AUX (AUXiliary Librairie), un GLUT pour Windows uniquement

Tout comme GLUT, AUX s’occupe pour vous des opérations relatives au système. Il est lui aussi de type évènementiel et adopte une syntaxe fort proche de celle de GLUT, à tel point que l’on se demande qui à copié sur qui… Il suffit de remplacer GLUT par AUX dans le nom d’une fonction !!! On ne peut pas faire plus simple. Seuls changent les fonctions de gestions des évènements clavier et souris. GLUT n’a que 2 fonctions pour le clavier tandis que AUX a besoin d’une fonction par touche. Pour la souris, GLUT propose plus de fonctions que AUX.

Il suffit, pour utiliser AUX, d’inclure « glaux.h » et surtout « windows.h » dans votre programme et de compiler avec la librairie « glaux.lib » incluse dans votre projet. Là encore, tous ces fichiers sont fournis en annexe.

Exemple d’initialisation de la session OpenGL sous AUX :

#include <windows.h> // obligatoire !!!
#include <gl\gl.h> // fonctions OpenGL
#include <gl\glaux.h> // fonctions AUX Library

void main(void)
{
 auxInitDisplayMode(AUX_DOUBLE | AUX_RGBA | AUX_DEPTH | AUX_STENCIL);
auxInitPosition(100,100,LARGEUR,HAUTEUR);
auxInitWindow("Moteur 3D");
 auxReshapeFunc(Reshape);
auxKeyFunc(AUX_UP,Key_up); //hé oui, une fonction par touche !
auxKeyFunc(AUX_DOWN,Key_down);
 auxKeyFunc(AUX_LEFT,Key_left);
auxKeyFunc(AUX_RIGHT,Key_right);
auxKeyFunc(AUX_a,Key_a);
auxKeyFunc(AUX_z,Key_z);
auxKeyFunc(AUX_q,Key_q);
auxKeyFunc(AUX_s,Key_s);
auxKeyFunc(AUX_g,Key_g);
auxKeyFunc(AUX_h,Key_h);
 auxKeyFunc(AUX_f,Key_f);
auxKeyFunc(AUX_j,Key_j);
auxKeyFunc(AUX_k,Key_k);
 auxKeyFunc(AUX_2,Key_2);
 auxKeyFunc(AUX_8,Key_8);
auxKeyFunc(AUX_4,Key_4);
auxKeyFunc(AUX_6,Key_6);
auxKeyFunc(AUX_9,Key_9);
auxKeyFunc(AUX_3,Key_3);
auxMouseFunc(AUX_LEFTBUTTON,AUX_MOUSEDOWN,Mouse_Move); //pour la …souris
auxIdleFunc(Animation); //est lance dés que le système
//n’a rien à faire.
auxMainLoop(Main_part); //Boucle principale
}

3. GLUT vs AUX

D’une manière générale, GLUT est plus complet qu’AUX et il n’est pas confiné à un seul système d’exploitation. « Alors pourquoi utiliser AUX ? » me direz-vous. Mais simplement parce que GLUT, ou plutôt son implémentation pour WINDOWS n’est pas parfaite. J’ai rencontré pas mal de problèmes avec les textures. J’ai bien sûr essayé plusieurs versions de la librairie, mais rien n’y faisait. Alors j’ai opté pour AUX, avec lequel je n’ai plus de problème en ce qui concerne les textures. Car notez tout de même que AUX bugue aussi : on ne peut pas par exemple définr une CallListe avec une fonction de dessin prédéfinie de cette librairie (ex : auxSolidSphere()).

Bref, chacun a ses petits avantages et ses inconvénients. Libre à vous de choisir. De toute façon, ils sont tellement similaires du point de vue syntaxique, que cela ne sera pas difficile de passer de l'un à l'autre. Au passage, notez que tous les exemples, que je donnerai par la suite, seront fait avec AUX.


Un petit peu de théorie

1. Dessin en trois dimension : les bases requises

Cette partie s’adresse aux débutants, qui ne connaissent rien, ou très peu du dessin en 3 dimension. Je ne parle pas de savoir utiliser un logiciel de dessin tel que 3DSMax, mais vraiment de savoir comment on représente une scène, comment on effectue les calculs, comment on translate un objet, le fait tourner…
 Si vous n’y connaissez rien, je vous conseille d’aller sur le site que j’ai réalisé l’année dernière pour mon ancien TPFH : SDIN (Secret Des Images Numériques, les bases de l'infographie). Tout y est expliqué, il y a même un super programme en pascal, qui détaille tous les calculs et fait tourner une maison en mode filiaire sur votre écran tout en éliminant les faces cachées etc.
J'ai porté ce programme en java (cliquez ici). Il dessine n’importe quel objet, il suffit de lui donner le fichier texte contenant les différents segments de l’objet dans un fichier que vous lui spécifiez dans le source HTML (voir le source de la page).

L'intéret de ce programme, c'est qu'il effectue tous les calculs lui_même à partir de la matrice de projection identique à celle utilisée dans l'OpenGL. Remarquez qu'il ne s'agit pas de Java3D.

2. Les matrices de l’OpenGL : un élément de base

En OpenGL, toutes les transformations géométriques sont entrées sous forme de matrice, la vue est définie par la position de la caméra (matrice de changement de repère) et par la matrice de projection.

La matrice de projection s’appelle GL_PROJECTION. Il n’est possible d’accéder qu’à une matrice à la fois, donc pour modifier une matrice, il faut d’abord l’activer avec glMatrixMode(mode). La matrice choisie devient la « matrice courante ». Il reste à définir la matrice de projection, mais inutile de se revoir le cours de prépa, car heureusement des fonctions de glu permettent de spécifier les matrices les plus courantes. La fonction

void gluOrtho2D( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top )

permet dans l’exemple de mettre une matrice de projection 2D de la dimension voulue. GlLoadIdentity() initialise la matrice à l’identité.

La matrice GL_MODELVIEW est en quelque sorte la matrice de positionnement de la vue. C’est le plus souvent une matrice de changement de repère. Il faut la considérer comme une matrice qui positionne les objets dans le repère du monde, et éviter de la considérer comme la position de la caméra, même si c’est aussi son rôle.

Il existe une troisième matrice, GL_TEXTURE, mais celle-ci n’est jamais utilisée directement.

Toutes sont des matrices 4x4 de float.

3. Exemple d’initialisation de l’écran pour un rendu 3D

L’exemple qui va suivre utilise la fonction AuxReshapeFunc() qui à pour but de redimensionner correctement la scène lorsqu'on agrandit la fenêtre d'éxécution. Elle est exécutée au moins une fois, au début du programme.

void CALLBACK Reshape(GLsizei w, GLsizei h) //changement de taille de la fenetre ...
{
 GLfloat aspect;
if (h == 0) h = 1;
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
 glLoadIdentity();

switch (PERSPECTIVE_ON) //si ce n’est pas une vue en perspective que l’on veut,
{ //alors c’est une vue orthogonale
case 0 : if (w<=h)
{
clipWidth = (GLfloat)LARGEUR * h/w;
clipHeight = (GLfloat)HAUTEUR;
}
else
{
clipWidth = (GLfloat)LARGEUR;
clipHeight = (GLfloat)HAUTEUR * w/h;
}
 glOrtho( -clipWidth/2, clipWidth/2, -clipHeight/2, clipHeight/2, ZMIN, ZMAX);
 break;
case 1 : aspect = (GLfloat) w / (GLfloat) h;
gluPerspective(angle_of_view, aspect, ZMIN, ZMAX);
break;
}
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

4. Les buffers : bien comprendre ce qui se passe

Il existe dans OpenGL des « buffers », qui d’une manière générale ne sont que des espaces mémoires contenant des données propres à l’OpenGL.
Le principal est le « color_buffer », qui correspond à ce que l’on voit sur l’écran. La fonction glClear permet de remplir tout un buffer avec la même valeur. La valeur par défaut est 0. Un appel à glClear sur le color_buffer rempli l’écran de noir. GlClearColor permet de changer la couleur de remplissage.

Un autre buffer, le back-buffer, fonctionne différemment : pour ne pas voir se dessiner la scène à l’écran, on utilise la technique du « double-buffering ». Le COLOR_BUFFER est en fait dédoublé : l’un est affiché (front-buffer), l’autre est celui sur lequel on travaille (back-buffer). A chaque fois qu’une image est calculée, on inverse (swap) les buffers avec la commande auxSwapBuffers(). Pour le programmeur, tout marche comme s’il dessinait toujours sur le même buffer. L’avantage est que l’utilisateur ne vois pas se dessiner l’image.

Les autres buffers standards sont le Depth-buffer, le Stencil-buffer, l’Accumulation-buffer. Chaque buffer joue un rôle précis, mais ils ne seront présentés que lorsque nous en aurons besoin. D’autres sont des extensions propriétaires (T-Buffer, W-Buffer, …).

On initialise un ou plusieurs buffers au début du programme avec la commande auxInitDisplayMode(buffer1 | buffer2 | buffer3 | …). Les noms des buffers sont les suivants :

- Double buffering : AUX_DOUBLE
 - Color Buffer (image de l’écran) : AUX_RGBA
 - Depth Buffer : AUX_DEPTH
 - Stencil buffer : AUX_STENCIL

De même, pour effacer ces buffers, on untilise la commande glClear( buffer1 | buffer2 | buffer 3 | …) avec, cette fois-ci, comme nom pour les buffers :

- Color Buffer (image de l’écran) : GL_COLOR_BUFFER_BIT
- Depth Buffer : GL_DEPTH_BUFFER_BIT
- Stencil buffer : GL_STENCIL_BUFFER_BIT


Enfin de la 3D

1. Les primitives graphiques

Le début du dessin d’une primitive se fait avec glBegin : ici un polygone. Les possibilités sont tirées de la man page de glBegin :

  • GL_POINTS
    Treats each vertex as a single point. Vertex n defines point n.N points are drawn.
  • GL_LINES
    Treates each pair of vertices as an independent line segment.Vertices 2n-1 and 2n define line n. N/2 lines are drawn.
  • GL_LINE_STRIP
    Draws a connected group of line segments from the first vertex to the last. Vertices n and n+1 define line n. N-1 lines are drawn.
  • GL_LINE_LOOP
    Draws a connected group of line segments from the first vertex to the last, then back to the first. Vertices n and n+1 define line n. The last line, however, is defined by vertices N and N lines are drawn.
  • GL_TRIANGLES
    Treates each triplet of vertices as an independent triangle. Vertices 3n-2, 3n-1, and 3n define triangle n. N/3 triangles are drawn.
  • GL_TRIANGLE_STRIP
    Draws a connected group of triangles. One triangle is defined for each vertex presented after the first two vertices. For odd n, vertices n, n+1, and n+2 define triangle n. For even n, vertices n+1, n, and n+2 define triangle n. N-2 triangles are drawn.
  • GL_TRIANGLE_FAN
    Draws a connected group of triangles. One triangle is defined for each vertex presented after the first two vertices. Vertices 1, n+1, and n+2 define triangle n. N-2 triangles are drawn.
  • GL_QUADS
    Treats each group of four vertices as an independent quadrilateral. Vertices 4n-3, 4n-2, 4n-1, and 4n define quadrilateral n. N/4 quadrilaterals are drawn.
  • GL_QUAD_STRIP
    Draws a connected group of quadrilaterals. One quadrilateral is defined for each pair of vertices presented after the first pair. Vertices 2n-1, 2n, 2n+2, and 2n+1 define quadrilateral n. N/2-1 quadrilaterals are drawn. Note that the order in which vertices are used to construct a quadrilateral from strip data is different from that used with independent data.
  • GL_POLYGON
    Draws a single, convex polygon. Vertices 1 through N define this polygon.

Le format général des fonctions d’OpenGL est : {nom}{1234}{b s i d ub us ui}{v}, respectivement le nom de la fonction, le nombre de paramètres, leur type, passé directement ou par pointeur (vecteur). Tous les caractéristiques de la fonction sont optionnelles sauf le nom.

Le début du dessin d'une primitive commence par glBegin(). Elle est suivie de la définitition des différents points composant l'objet. Chaque point est défini grâce à la fonction glVertex, qui existe sous plusieurs formes : glVertex2d, glVertex2f, glVertex2i, glVertex2s, glVertex3d, glVertex3f, glVertex3i, glVertex3s, glVertex4d, glVertex4f, glVertex4i, glVertex4s, glVertex2dv, glVertex2fv, glVertex2iv, glVertex2sv, glVertex3dv, glVertex3fv, glVertex3iv…
glColor() permet de fixer la couleur courante. De même, elle existe sous de nombreuses formes.
glEnd(), quant à elle, indique la fin de l’entrée de coordonnées.

Cependant, jusqu’ici, rien n’est affiché : OpenGL a mis tout ce qu’il y a à faire dans un buffer, mais ne l’affiche que lorsqu’on lui demande, avec glFlush(), qui rend la main immédiatement, ou avec glFinish(), qui attend que tout soit affiché (peu d’intêret).

2. Exemple simple : un triangle

Les fonctions suivantes permettent de dessiner un triangle à l’écran :

glBegin(GL_TRIANGLES);
                                     //commence
                le dessin d'un triangle
glColor3f(0.0f,1.0f,0.0f);

                 //couleur du premier sommet
glVertex3f(-65.0f,65.0f,0.0f);
                        //coordonnées du premier sommet
glColor3f(1.0f,0.0f,0.0f);
                //etc ...
glVertex3f(65.0f,-65.0f,0.0f);
glColor3f(0.0f,0.0f,1.0f);
glVertex3f(-65.0f,-65.0f,0.0f);
glEnd();
                         //fin du tracé du triangle

Sources et éxécutable

Pour obtenir le dégradé des couleurs, il a simplement fallu déclarer les 3 sommets avec des couleurs différentes et l’OpenGL l’a fait, tout seul, comme un grand. On peut bien sûr désactiver cette fonctionnalité avec glShadeModel(GL_FLAT), qui demande à l’OpenGL de remplir le polygone avec la couleur du dernier point specifié. Pour la réaciver, il faut taper : glShadeModel(GL_SMOOTH).

3. Bouger ce triangle : rotations, translations, symétries

1. Rotation et translation

Pour bouger la « caméra » ou un objet (c’est la même chose de bouger tout les objets ou la caméra), il faut utiliser la matrice de vue (GL_MODELVIEW). Comme calculer directement une matrice de changement de repère n’est pas vraiment évidente, il existe des fonctions prédéfinies : glRotate et glTranslate.

void glRotated( GLdouble angle, GLdouble x, GLdouble y, GLdouble z )

void glRotatef( GLfloat angle, GLfloat x, GLfloat y, GLfloat z )

void glTranslated( GLdouble x, GLdouble y, GLdouble z )

void glTranslatef( GLfloat x, GLfloat y, GLfloat z )

Ces fonctions multiplient à droite la matrice de vue. Il faut donc faire attention à l’ordre dans lequel les transformations sont entrées : c’est la dernière transformation entrée qui sera appliquée en premier.

Rappels :
- soient f de matrice L, g de matrice M et h de matrice N. h°g°f a pour matrice N.M.L donc il faut entrer les matrices dans l'ordre N,M,L
- les rotations et les translations ne sont pas commutatives. Par exemple, pour faire tourner un objet sur lui-même, il faut entrer en dernier la matrice de rotation. Pour simplifier, l’origine du repère local d’un objet est par convention son centre de rotation (d’inertie) pour permettre de le faire tourner sur lui-même sans calcul complexe.

Tout cela peut paraître compliqué, mais un ordinateur ne sait comprendre que les matrices, et un énorme avantage est le temps de calcul : même si de nombreuses transformations sont entrées, il ne reste qu’une seule matrice à appliquer à tout les points entrés.

2.Pile de matrice

Mais comment inverser les transformations entrées ? Calculer les inverses est une perte de temps. Une pile est en effet associée à chaque matrice. Les fonctions sont :

void glPushMatrix( void )

void glPopMatrix( void )

qui respectivement empile et dépile la matrice courante. Même s’il n’y a que deux fonctions, il existe bien trois piles différentes (une par matrice). Comme pour les transformations, seule la matrice active peut-être empilée / dépilée (ce qui est logique, puisque que une matrice est empilée pour la sauvegarder avant une transformation).

Le plus souvent, un objet est caractérisé par ses angles de rotation (ax, ay, az) et sa position (x, y, z). Son affichage est alors du type :

glPushMatrix() ;
glTranslate3f(x,y,z) ;
glRotate3f(ax,ay,az) ;
 glBegin(…)
…
glEnd() ;
glPopMatrix() ;

Exemple d’utilisation : un soleil avec ses satellites, et les lunes des satellites. Le principe est le même :

- Dessin du soleil
- PushMatrix

- Translation dans le repère local d’une planète
- Dessin de la planète
- PushMatrix

- Translation dans le repère local d’une lune
- Dessin de la lune

- PopMatrix
- PushMatrix

- Translation dans le repère local d’une autre lune
- Dessin de la lune

- PopMatrix

- PopMatrix
- PushMatrix

- Translation dans le repère local d’une autre planète
- Dessin de la planète
- PushMatrix

- Translation dans le repère local d’une lune
- Dessin de la lune

- PopMatrix

- PopMatrix
- etc.

ATTENTION : la moindre erreur, le moindre oubli d’un glPopMatrix() ou glPushMatrix() peut se révéler désastreux et être en plus difficile à détecter si vous faites se déplacer la caméra.

4. Depth-Buffer

En 3D, tout n’est pas affiché : il y a des faces cachées. Comment déterminer ce qui est affiché ? En affichant ce qui est la plus proche de la caméra. C’est le rôle du depth-buffer : il sauvegarde la distance du point actuellement affiché. Si un point plus proche arrive ensuite, il remplace le point et la valeur dans la depth-buffer est changée.

Le depth-buffer à les même dimensions que le color-buffer. Il s’active grâce à glEnable(GL_DEPTH_TEST) et se désactive par glDisable(GL_DEPTH_TEST). Il ne faut pas, bien sûr, oublier de l’initialiser au début du programme avec la commande : auxInitDisplayMode(… | AUX_DEPTH | …). Il doit être effacé avec glClear(GL_DEPTH_BUFFER_BIT) à chaque fois que la scène est redessinée.

5. Anti-aliasing

L’OpenGL est une « state machine » : les paramètres de dessin consistent en une série de variables (très nombreuses, voir les annexes de la spécification OpenGL pour une liste complète).

La plupart sont modifiables par :

void glEnable( GLenum cap )

void glDisable( GLenum cap )

Pour l’antialiasing, les paramètres sont : GL_POINT_SMOOTH, GL_LINE_SMOOTH, et GL_POLYGON_SMOOTH. Le but de l’antialiasing est de rendre apparemment plus lisse les lignes en faisant un dégradé sur les contours des points.

6. Culling

Le culling consiste à ne dessiner que les triangles dont les points sont dans le sens des aiguilles d’une montre (« clockwise ») ou dans l’autre sens (« counter-clockwise »). L’intérêt est l’optimisation : l’objectif est de toujours dessiner les faces extérieures d’un objet dans le sens contraire des aiguilles d’une montre, et de filtrer les faces cachées avec le culling.

Prenons l’exemple du cube : les faces sont toujours affichées dans le même ordre, peut importe si elles sont visibles ou pas. C’est le depth-buffer qui permet d’afficher les bonnes faces. Cependant le test du depth-buffer est long : il faut calculer la distance de chaque point de chaque face par rapport à la caméra, puis tester le depth pour chaque point (et mettre à jour le depth si le point est plus près). C’est un test beaucoup trop important pour les faces dont on est sûr qu’elles ne seront pas affichées. Il est plus rapide d’éliminer une face entière d’un coup avec le culling.

L’astuce utilisée par le culling pour savoir de quel coté est vue une face consiste à regarder dans quel sens les points des triangles sont dessinés (en OpenGL tout est décomposé en triangles) : dans le sens des aiguilles d’une montre ou dans l’autre sens.

Cette technique est très rapide : en gros il suffit de regarder le signe du produit scalaire de deux cotés d’un triangle. La seule contrainte pour le programmeur est de dessiner dans le bon sens les points.
Le standard est le CCW (counter-clockwise), que ce soit pour les primitives de glu ou glut, ou encore de 3DS ou Lightwave.
En OpenGL, les fonctions correspondantes sont :

void glCullFace( GLenum mode ) avec mode à GL_FRONT, GL_BACK ou GL_FRONT_AND_BACK, pour indiquer quel coté doit être éliminé au culling.

void glFrontFace( GLenum mode ) avec mode à GL_CW ou GL_CCW pour indiquer quelles sont les faces de devant

Un appel à glEnable / glDisable avec en paramètre GL_CULL_FACE active / désactive le culling.

7. Exemple complet d’utilisation du Depth-Buffer

Il s'agit d'un simple triangle, comme dans l'exemple précédent, mais coupé par une ligne. J'ai également déssiné un point, mais il ne se voit pas beaucoup.

#include <windows.h> // Standard Window header required for all programs
 #include <gl\gl.h> // fonctions OpenGL
#include <gl\glaux.h> // Librarie AUX
#define largeur 250
#define hauteur 250
GLfloat clipHeight;
GLfloat clipWidth;

void setupRC()
{
 glClearColor(0.0f, 0.0f, 0.0f, 1.0f );
 glShadeModel(GL_SMOOTH);
 //glShadeModel(GL_FLAT);
 glFrontFace(GL_CW);
 glEnable(GL_DEPTH_TEST);
}

void CALLBACK Main_part(void)
{
 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 glBegin(GL_POINTS);
 glVertex2f(-10.0f,-10.0f);
 glEnd();
 glColor3f(1.0f,0.0f,1.0f);
 glBegin(GL_LINES);
 glVertex3f(-10.0f,-10.0f,-50.0f);
 glVertex3f(100.0f,100.0f,50.0f);
 glEnd();
 glBegin(GL_TRIANGLES); //la ligne coupe le triangle et est partiellement cachée
 glColor3f(0.0f,1.0f,0.0f);
 glVertex3f(0.0f,130.0f,0.0f);

 glColor3f(0.0f,0.0f,1.0f);
 glVertex3f(0.0f,0.0f,0.0f);
 glColor3f(1.0f,0.0f,0.0f);
 glVertex3f(130.0f,0.0f,0.0f);
 glEnd();
 glFlush();
}

void CALLBACK Reshape(GLsizei w, GLsizei h) //si il y a un changement de taille de la fenetre ...
{
 GLfloat Near = -100.0f;
 GLfloat Far = 100.0f;
 if (h == 0) h = 1;
 glViewport(0,0,w,h);
 glMatrixMode(GL_PROJECTION);
 glLoadIdentity();
 if (w<=h)
 {
 clipWidth = (GLfloat)largeur * h/w;
 clipHeight = (GLfloat)hauteur;
 }
 else
{
 clipWidth = (GLfloat)largeur;
 clipHeight = (GLfloat)hauteur * w/h;
 }
 glOrtho( -clipWidth/2, clipWidth/2, -clipHeight/2, clipHeight/2, Near, Far);
 glMatrixMode(GL_MODELVIEW);
 glLoadIdentity();
}

void main(void)
{
 auxInitDisplayMode(AUX_SINGLE | AUX_RGBA | AUX_DEPTH);
 auxInitPosition(100,100,largeur,hauteur);
 auxInitWindow("Test");
 setupRC();
 auxReshapeFunc(Reshape);
 auxMainLoop(Main_part);
}

la ligne coupe le triangle et est partiellement cachée

sources et éxécutable


Les CallListes

1. Commandes OpenGL

Certains objets sont toujours les mêmes. Comme OpenGL traduit toutes les séquences entrées, il est intéressant de créer des listes pour regrouper les instructions. Cela accélère légèrement l’affichage et réduit le code. Une liste est alors codée par un entier (un seul entier est alors envoyé pour un objet quelconque).
En pratique, il faut :

  • d’abord réserver des numéros de listes avec : GLuint glGenLists( GLsizei range ), où "range" est le nombre de numéros de listes à réserver. Les numéros réservés vont de la valeur renvoyée à (cette valeur + range - 1).

  • ensuite appeler : void glNewList( GLuint list, GLenum mode ) avec pour list le numéro de la liste à créer, et mode = GL_COMPILE ou GL_COMPILE_AND_EXECUTE. Les deux modes créés la liste, mais le second en plus l’éxécute (l’affiche).

  • mettre toutes les routines souhaitées (glBegin, glColor, glVertex…), elles sont enregistrées dans la liste. Certaines commandes ne peuvent pas être enregistrées dans une liste, mais il y en a peu et c’est plutôt logique (par exemple glGenList, glFlush, glNewlist…).

  • terminer par : void glEndList( void )

Pour afficher une liste, il faut appeler : void glCallList( GLuint list ) ou void glCallLists( GLsizei n, GLenum type, const GLvoid *lists). Pour afficher plusieurs listes d’un coup (type indique le type du tableau passé en paramètre : GL_BYTE, GL_INT…).

Reste à effacer une liste lorsqu’elle n’est plus utile : void glDeleteLists( GLuint list, GLsizei range ), dont les paramètres sont les même que glGenList.

2. Exemple

Il s'agit du même exemple qu'à la page précédente, mais cette fois un peu plus ordonnée avec des CallListes :
#define POINT 1
#define TRIANGLE 2
#define DROITE 3

void Make_CallListes()
{
glNewList(POINT,GL_COMPILE);
glBegin(GL_POINTS);
glVertex2f(-10.0f,-10.0f);
glEnd();
glEndList();

glNewList(TRIANGLE,GL_COMPILE);
glBegin(GL_TRIANGLES);
glColor3f(0.0f,1.0f,0.0f);
glVertex3f(0.0f,130.0f,0.0f);
glColor3f(0.0f,0.0f,1.0f);
glVertex3f(0.0f,0.0f,0.0f);
glColor3f(1.0f,0.0f,0.0f);
glVertex3f(130.0f,0.0f,0.0f);
glEnd();
glEndList();

glNewList(DROITE,GL_COMPILE);
glColor3f(1.0f,0.0f,1.0f);
glBegin(GL_LINES);
glVertex3f(-10.0f,-10.0f,-50.0f);
glVertex3f(100.0f,100.0f,50.0f);
glEnd();
glEndList();
}

void CALLBACK Main_part(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glCallList(POINT);
glCallList(TRIANGLE);
glCallList(DROITE);
glFlush();
}

sources et éxécutable

3. Remarque à propos d' AUX

Comme je l'ai déjà dit à lors de la présentation de GLUT et d'AUX, cette dernière bugue méchamment lorsqu'on utilise les CallListes et les fonctions de dessin prédéfinies tel que auxSolidSphere, auxSolidBox, auxSolidCone etc... En fait, dés que l'OpenGl rencontre une de ces fonctions dans la déclaration d'une liste, il sort de cette liste et la considère finie. Vous aurez certes votre sphère ou votre cône, mais ce qui se trouvait après n'aura même pas été lu !!! Evitez donc de les utiliser (elles ne sont là qu'a titre d'exemple, comme le dit si bien la documentation, et ne devrai pas servir dans des programmes plus consistants).


Eclairage

1. Activation

C’est pour ce genre de choses que l’on apprécie la simplicité d’OpenGL, car activer l’éclairage se fait par :

glEnable(GL_LIGHTING) ; // Active la gestion des lumières
glEnable(GL_LIGHT0) ; // allume la lampe 0

C’est tout.
Pour allumer une autre lampe, mettre GL_LIGHTi au lieu de GL_LIGHT0. Il y a au minimum 8 lampes disponibles. On a toujours GL_LIGHTi = GL_LIGHT0 + i. Pour éteindre, on utilise glDisable.
Le nombre maximal de lumières est obtenu en appelant : glGetIntegerv(GL_MAX_LIGHTS, &nb_lights)

2. Paramètres

Il faut quand même donner un minimum d’information sur la lampe, comme la couleur et la position de la lampe.

i. Un peu de théorie

Dans la réalité, les sources de lumières sont des volumes (par exemple un filament), et la lumière se réfléchit à l’infini sur tous les objets.
Il est impossible de simuler un tel comportement, surtout quand il faut calculer une image en quelques millisecondes. Des simplifications ont été apportées :

- les sources de lumière sont des points.

- la lumière ne se réfléchit pas sur les objets.

Pour plus de réalisme, les sources disposent de plusieurs paramètres qui n’ont pas forcément d’équivalent réel (voir le paragraphe suivant). Une chose par contre ne change pas : la couleur visible d’un objet dépend à la fois de sa couleur propre et celle de la lampe, et l’intensité de la lumière qu’il reçoit dépend de l’angle de la surface avec la direction vers la source lumineuse.

ii. Tout sur la lampe

Tiré tout droit de la spécification OpenGL :

Paramètre | Type | Par défaut | Description
acm | Couleur | (0,0,0,0) | Intensité ambiante : la lumière ambiante est présente partout dans la scène, elle n’a pas de direction
dcli | Couleur | (0,0,0,0) sauf lampe 0 : (1,1,1,1) | Intensité de diffusion : c’est l’intensité « réelle » de la source, elle part dans tous les sens depuis la source
scli | Couleur | Idem | Intensité du « spot » : comme la lumière diffuse, sauf qu’elle n’est émise qu’à l’intérieur d’un cône
Ppli | Position | (0,0,1,0) | Position
sdli | Direction | (0,0,-1) | Direction du spot (vecteur)
srli | Réel | 0 | Exposant du spot (entre 0 et 128)
crli | Réel | 180 | Angle du cône du spot (180 ou entre 0 et 90)
k0i | Réel | 1.0 | Atténuation constante (entre 0 et ¥)
k1i | Réel | 0 | Atténuation linéaire
k2i | Réel | 0 | Atténuation quadrique

iii. Et sur les matériaux

Les mêmes paramètres existent pour spécifier la « réponse » de la surface d’un objet :

Paramètre | Type | Par défaut | Description
acm | Couleur | (0.2,0.2,0.2,1.0) | Couleur ambiante
dcm | Couleur | (0.8,0.8,0.8,1.0) | Couleur de diffusion
scm | Couleur | (0,0,0,1) | Couleur au « spot »
ecm | Couleur | (0,0,0,1) | Couleur émise
srm | Réel | 0 | Exposant au « spot »

2. Fonctions OpenGL

Les fonctions suivantes permettent de spécifier tous les paramètres précédents :

· Pour une source lumineuse :

void glLightf( Glenum light, Glenum pname, GLfloat param )
void glLighti( Glenum light, Glenum pname, GLint param )

light = GL_LIGHTi
pname = GL_SPOT_EXPONENT, GL_SPOT_CUTOFF, GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION, ou GL_QUADRATIC_ATTENUATION
et param = la nouvelle valeur

void glLightfv( GLenum light, GLenum pname, const Glfloat *params)
void glLightiv( GLenum light, GLenum pname, const GLint *params)

light = GL_LIGHTi
pname = GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, GL_POSITION, GL_SPOT_DIRECTION, GL_SPOT_EXPONENT, GL_SPOT_CUTOFF,
L_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION, ou GL_QUADRATIC_ATTENUATION
param = la nouvelle valeur

Attention à la position de la lampe : comme toutes les coordonnées entrées, elles dont immédiatement multipliées par la matrice ModelView. Si celle-ci change, il faut repositionner les lampes.

void glLightModelf( GLenum pname, GLfloat param )
void glLightModeli( GLenum pname, GLint param )
void glLightModelfv( GLenum pname, const GLfloat *params )
void glLightModeliv( GLenum pname, const GLint *params )

Pname | param
GL_LIGHT_MODEL_AMBIENT (fv ou iv seulement) | Intensité ambiante de la scène, par défaut (0.2, 0.2, 0.2, 1.0)
GL_LIGHT_MODEL_LOCAL_VIEWER | 0 : les reflets des spots sont calculés sur l’axe –z ¹ 0 : par rapport à la position de l’œil
GL_LIGHT_MODEL_TWO_SIDE | 0 : seule la face « front » est calculée ¹ 0 : les deux faces d’une surface sont calculées

· Pour un matériau :

Les propriétés de surface d’un polygone peuvent être différentes suivant la face que la face est « front » ou « back » (voir glFrontFace, ou le paragraphe sur le culling).

void glMaterialfv( GLenum face, GLenum pname, const GLfloat *params )
 void glMaterialiv( GLenum face, GLenum pname, const GLint *params )

La page man est tellement bien faite que je ne vois pas l’intérêt de la refaire :

Face | Specifies which face or faces are being updated. Must be one of GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK.
Pname | Specifies the material parameter of the face or faces that is being updated. Must be one of GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, GL_EMISSION, GL_SHININESS, GL_AMBIENT_AND_DIFFUSE, or GL_COLOR_INDEXES.
Params | Specifies a pointer to the value or values that pname will be set to.

glMaterial assigns values to material parameters. There are two matched sets of material parameters. One, the front-facing set, is used to shade points, lines, bitmaps, and all polygons (when two-sided lighting is disabled), or just front-facing polygons (when two-sided lighting is enabled). The other set, back-facing, is used to shade back-facing polygons only when two-sided lighting is enabled. Refer to the glLightModel reference page for details concerning one- and two-sided lighting calculations.

[…]

GL_AMBIENT | params contains four integer or floating-point values that specify the ambient RGBA reflectance of the material. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to -1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default ambient reflectance for both front- and back-facing materials is (0.2, 0.2, 0.2, 1.0).
GL_DIFFUSE | params contains four integer or floating-point values that specify the diffuse RGBA reflectance of the material. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to -1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default diffuse reflectance for both front- and back-facing materials is (0.8, 0.8, 0.8, 1.0).
GL_SPECULAR | params contains four integer or floating-point values that specify the specular RGBA reflectance of the material. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to -1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default specular reflectance for both front- and back-facing materials is (0.0, 0.0, 0.0, 1.0).
GL_EMISSION | params contains four integer or floating-point values that specify the RGBA emitted light intensity of the material. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to -1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default emission intensity for both front- and back-facing materials is (0.0, 0.0, 0.0, 1.0).
GL_SHININESS | params is a single integer or floating-point value that specifies the RGBA specular exponent of the material. Integer and floating-point values are mapped directly. Only values in the range [0,128] are accepted. The default specular exponent for both front- and back-facing materials is 0.
GL_AMBIENT_AND_DIFFUSE | Equivalent to calling glMaterial twice with the same parameter values, once with GL_AMBIENT and once with GL_DIFFUSE.

3. Les normales

Une fois que la source lumineuse est définie, ainsi que les caractéristiques du matériau, il reste encore une chose : l’angle d’incidence de la lumière. Cela ne change pas la couleur d’une surface, mais l’intensité de l’éclairage qu’elle reçoit.
L’angle d’incidence est l’angle entre la normale à une surface et la droite partant de la source lumineuse et passant par le point d’incidence. Il faut impérativement préciser pour chaque point la « normale » de ce point (plus rigoureusement, la normale à la surface dont ce point fait partie). Le principe est le même que pour glColor, la normale est une des variables d’état d’OpenGL, qui est modifiée par l’une des fonctions suivantes :

void glNormal3b( GLbyte nx, GLbyte ny, GLbyte nz )
void glNormal3d( GLdouble nx, GLdouble ny, GLdouble nz )
void glNormal3f( GLfloat nx, GLfloat ny, GLfloat nz )
void glNormal3i( GLint nx, GLint ny, GLint nz )
void glNormal3s( GLshort nx, GLshort ny, GLshort nz )
void glNormal3bv( const GLbyte *v )

Les paramètres correspondent au coordonnées du vecteur normal. ATTENTION : il doit être UNITAIRE (de norme 1). Il est possible de rendre unitaire automatiquement les vecteurs entrés, mais cela entraîne un calcul supplémentaire : glEnable(GL_NORMALIZE). Pour éviter cette perte de temps, qui, pour les scènes assez complexes, peut devenir importante, on peut calculer soit_même la normale avec la fonction ci-dessous :

void Vecteur_Unite(float vector[3])
{
 float length;

// Calcul de la norme du vecteur
length = (float)sqrt((vector[0]*vector[0]) + (vector[1]*vector[1]) + (vector[2]*vector[2]));
if(length == 0.0f) length = 1.0f; //évite une violente erreur !!!
vector[0] /= length;
vector[1] /= length;
vector[2] /= length;
}

//
                                Points p1, p2, & p3 spécifiés
                                dans le sens trigonométrique
void Normale(float
                                v[3][3], float out[3])
{
float
                                v1[3],v2[3];
static const
                                int x = 0;
static const
                                int y = 1;
static const
                                int z = 2;

// Calcul
                                de 2 vecteurs à partir des 3 points
v1[x] = v[0][x] - v[1][x];
v1[y]
                                = v[0][y] - v[1][y];
v1[z] = v[0][z] - v[1][z];
v2[x]
                                = v[1][x] - v[2][x];
v2[y] = v[1][y]
                                - v[2][y];
v2[z] = v[1][z] - v[2][z];

//
                                calcul du produit vectoriel
out[x] = (v1[y]*v2[z] -
                                v1[z]*v2[y]);
out[y] = (v1[z]*v2[x] - v1[x]*v2[z]);
out[z]
                                = (v1[x]*v2[y] - v1[y]*v2[x]);

// on le réduit à un vecteur
                                unité
Vecteur_Unite(out);
}

4. Exemple de scène avec lumière

Voici l’exemple le plus simple :

void Init_light()
{
//différents paramètres
GLfloat ambient[] = {0.15f,0.15f,0.15f,1.0f};
GLfloat diffuse[] = {0.5f,0.5f,0.5f,1.0f};
GLfloat light0_position [] = {0.0f, -10.0f, 0.0f, 0.0f};
GLfloat specular_reflexion[] = {0.8f,0.8f,0.8f,1.0f};
GLubyte shiny_obj = 128;

//positionnement de la lumière avec les différents paramètres
glEnable(GL_LIGHTING);
glLightfv(GL_LIGHT0,GL_AMBIENT,ambient);
glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuse);
glLightfv(GL_LIGHT0,GL_POSITION,light0_position);
glEnable(GL_LIGHT0);

//spécification de la réflexion sur les matériaux
glEnable(GL_COLOR_MATERIAL);
glColorMaterial(GL_FRONT,GL_AMBIENT_AND_DIFFUSE);
glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT,ambient);
glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,diffuse);
glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,specular_reflexion);
glMateriali(GL_FRONT_AND_BACK,GL_SHININESS,shiny_obj);
}

Appliquez à un cube, cela donne :

Sans lumière particulière Avec une lumière moyennement forte

Sources et éxécutable

Notez qu'ici, j'ai utilisé 2 fois les tableaux "ambient" et "diffuse". Ceci n'est pas du tout obligatoire. je ne l'ai fait que pour simplifier les choses. Il y a des tonnes d'effets différents, donc, testez vous_même. Notez également qu'en ce qui concerne la réfléxion sur les matériaux, vous pouvez changer ces proporiétés avant de dessiner chaque objet, qui aura alors des caractéristiques lumineuses propres. (le bois ne reflète pas la lumière comme le métal...)


Fog (brouillard)

C’est ce qui simule les effets atmosphériques (je ne vous apprends rien là) comme la fumée, la pollution etc. On s’en sert aussi pour rendre les effets de vision limitée (en même temps, cela permet de retirer de la scène une partie des objets 3D trop loin et d’accélérer le rendu).

1. Le fonctionnement du brouillard

Lorsque le brouillard est activé, les objets qui sont de plus en plus loin commencent à se fondre dans la couleur du brouillard. On peut contrôler aussi la densité du brouillard pour déterminer le rythme avec lequel les objets sont estompé lorsque la distance augmente.
Le brouillard est appliqué à la scène après les transformations matricielles, la lumière, et les textures, donc il affecte les objets transformés (ie avec texture…).

2. L’utilisation du brouillard

C’est très simple. On l’active en passant GL_FOG comme attribut de glEnable() et on la couleur et l’équation de contrôle de la densité du brouillard grâce à :

void glFogf( GLenum pname, GLfloat param )
void glFogi( GLenum pname, GLint param )
void glFogfv( GLenum pname, const GLfloat *params )
void glFogiv( GLenum pname, const GLint *params )
avec pname = GL_FOG_MODE, GL_FOG_DENSITY, GL_FOG_START, GL_FOG_END, ou GL_FOG_COLOR, et param la nouvelle valeur.

Exemple :

GlEnable(GL_FOG) ;
GLfloat
                fogcolor[4] = {0.5, 0.5, 0.5, 1} ;
GLint fogmode = GL_EXP ;
glFogi (GL_FOG_MODE, fogmode) ;
glFogfv(GL_FOG_COLOR, fogcolor) ;
glFogf(GL_FOG_DENSITY, 0.35) ;
glFogf(GL_FOG_START, 1.0) ;
glFogf(GL_FOG_END, 5.0) ;

3. Les équations

Le brouillard se sert d’une variable f variant de 0 (brouillard complet) à 1 (transparent comme de l’eau de roche). Les 3 équations sont :

· GL_LINEAR : f = (end – z) / (end – start)

· GL_EXP : f = e-density * z

· GL_EXP2 : f = e-(density * z)²

Remarque :
Penser à mettre le background de la couleur du brouillard : ainsi, l’effet sera plus frappant. La commande correspondante est : GlearColor(couleur_brouillard[0],couleur_brouillard[1],couleur_brouillard[2],couleur_brouillard[3]);

Exemple :
Voici un screen-shot de mon petit moteur de rendu 3D avec du brouillard. C'est tout de même formidable l'OpenGL : tout ça avec seulement 4 lignes de codes !!!

Pour les sources, je vous laisse le soin de regarder dans le programme général qui se trouve dans les annexes. Je n'ai pas trouvé utile de vous mettre un exemple, car vous savez déja tout. C'est extrémement simple, tout du moins, en ce qui concerne le brouillard global (appliqué à toute la scène). Je cherche, en ce moment, à créer de la fumée dans une zone précise de l'écran. C'est moins évident que le brouillard global, car si on se contente de procéder de la même façon, la fumée disparaît lorqu'on s'approche !!! Pour toutes idées, mailez-moi, svp.


Effet de transparence

Plusieurs fois déjà des couleurs ont été présentée sous forme RGBA : rouge, vert, bleu et alpha. Si les trois premières coordonnées sont évidentes, la quatrième, quant à elle, sert à indiquer la « transparence ».

1. Activation

Si pour l’instant rien n’a été dit sur les valeurs de l’alpha, c’est simplement parce que par défaut OpenGL s’en fiche. Pour activer le canal alpha, il faut appeler comme d’habitude glEnable :

glEnable(GL_BLEND) ;
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) ;
                // expliqué après

Ensuite il suffit de changer la valeur de A suivant la transparence.

2. Exemple

Il s'agit, toujours du même cube, mais dont les côtés sont maintenant transparents.

void CALLBACK Main_part(void)
{
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
drawFloor();
glPushMatrix();
//initialisation de la transparence
glEnable(GL_BLEND);
//la couleur de l'objet va être (1-alpha_de_l_objet) * couleur du fond et (le_reste * couleur originale)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glTranslatef(0.0f, -2.0f, 5.0f);
glRotatef((80.0f), -0.5f, 0.3f, -0.8f);
glRotatef((10.0f), 0.5f, -0.3f, 0.8f);
glCallList(cube);
//on désactive la transparence
glDisable(GL_BLEND);
glPopMatrix();
auxSwapBuffers();
}

void init()
{
//C'est la partie la plus importante
//Les couleurs sont définies avec un alpha de 0.25
//qui va servir dans le calcul de la transparence
//Rappel : 0 = transparent, 1 = opaque
GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 0.25f};
GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 0.25f};
GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 0.25f};
GLfloat Jaune[] = {0.8f, 0.8f, 0.0f, 0.25f};
GLfloat Rose[] = {0.8f, 0.0f, 0.8f, 0.25f};
GLfloat Cyan[] = {0.0f, 0.8f, 0.8f, 0.25f};

//on se contente de dessiner le cube,
//en utilisant cependant la fonction
//glColor4f() au lieu de glColor3f()
//car la composante alpha compte cette fois_ci
cube = glGenLists(1);
glNewList(cube, GL_COMPILE);
glBegin(GL_QUADS);
glColor4fv(Jaune);
glNormal3d(-1.0,0.0,0.0);
glVertex3d( -1, 1, 1);
glVertex3d( -1, 1, -1);
glVertex3d( -1, -1, -1);
glVertex3d( -1, -1, 1);
glColor4fv(Rose);
glNormal3d(0.0,1.0,0.0);
glVertex3d( -1, 1, 1);
glVertex3d( 1, 1, 1);
glVertex3d( 1, 1, -1);
glVertex3d( -1, 1, -1);
glColor4fv(Bleu);
glNormal3d(0.0,-1.0,0.0);
glVertex3d( -1, -1, 1);
glVertex3d( -1, -1, -1);
glVertex3d( 1, -1, -1);
glVertex3d( 1, -1, 1);
glColor4fv(Cyan);
glNormal3d(0.0,0.0,-1.0);
glVertex3d( 1, 1, -1);
glVertex3d( 1, -1, -1);
glVertex3d( -1, -1, -1);
glVertex3d( -1, 1, -1);
glColor4fv(Vert);
glNormal3d(1.0,0.0,0.0);
glVertex3d( 1, 1, 1);
glVertex3d( 1, -1, 1);
glVertex3d( 1, -1, -1);
glVertex3d( 1, 1, -1);
glColor4fv(Rouge);
glNormal3d(0.0,0.0,1.0);
glVertex3d(-1, 1, 1);
glVertex3d(-1, -1, 1);
glVertex3d( 1, -1, 1);
glVertex3d( 1, 1, 1);
glEnd();
glEndList();
}

Sources et éxécutables

3. Paramètres

La paramétrage principal est effectué par la fonction :

void glBlendFunc( GLenum sfactor, GLenum dfactor )

Si la fonction s’appelle « blend » (« mélanger » en anglais, ici en parlant des couleurs) et pas « transparency », c’est que la transparence n’est qu’une application de l’alpha-blending. C’est de loin la principale. Néanmoins, ce que fait réellement l’alpha-blending, c’est mélanger la couleur déjà affichée avec une nouvelle.

Le mélange est précisé par un appel à glBlendFunc, avec le premier paramètre indiquant par quelle valeur multiplier (canal par canal) la nouvelle couleur, le second la couleur déjà affichée. La nouvelle couleur affichée est la somme de ces deux multiplications. L’indice « s » indique que la nouvelle couleur est concernée, « d » qu’il s’agit de la couleur déjà affichée. La couleur affichée est : C = CsS + CdD. S et D étant paramétrés respectivement par sfactor et dfactor.

Principales valeurs de S en fonction de sfactor :

Valeur de sfactor | Valeur de S
ZERO | (0,0,0,0)
ONE | (1,1,1,1)
DST_COLOR | (Rd,Gd,Bd,Ad)
ONE_MINUS_DST_COLOR | (1,1,1,1) - (Rd,Gd,Bd,Ad)
SRC_ALPHA | (As, As, As, As)
ONE_MINUS_SRC_ALPHA | (1,1,1,1) - (As,As,As,As)
DST_ALPHA | (Ad,Ad,Ad,Ad)
ONE_MINUS_DST_ALPHA | (1,1,1,1) - (Ad,Ad,Ad,Ad)
SRC_ALPHA_SATURATE | (f,f,f,1) avec f = min (As, 1 - Ad)

Principales valeurs de D en fonction de dfactor :

Valeur de sfactor | Valeur de D
ZERO | (0,0,0,0)
ONE | (1,1,1,1)
SRC_COLOR | (Rs,Gs,Bs,As)
ONE_MINUS_SRC_COLOR | (1,1,1,1) - (Rs,Gs,Bs,As)
SRC_ALPHA | (As, As, As, As)
ONE_MINUS_SRC_ALPHA | (1,1,1,1) - (As,As,As,As)
DST_ALPHA | (Ad, Ad, Ad, Ad)
ONE_MINUS_DST_ALPHA | (1,1,1,1) - (Ad,Ad,Ad,Ad)
SRC_ALPHA_SATURATE | (f,f,f,1) avec f = min (As, 1 - Ad)

Les paramètres les plus courants sont (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA).
Cela représente bien la transparence codée dans le canal Alpha : la couleur affichée est multipliée par la valeur de transparence (si 0, complètement transparent, si 1 opaque), et la couleur en dessous est multipliée par le complément.

4. Ordre des primitives

Le problème avec cette manière de rendre les objets transparents, c’est qu’il faut dessiner les surfaces dans l’ordre d’apparition pour avoir un meilleur rendu. en fait, ce qu'il faut faire, c'est dessiner la surface transparente juste après les surfaces que l'on doit voir au travers, et dessiner après les surfaces qui ne doivent pas apparaître. Par exemple, dans le programme final, j'ai placé une fenètre teintée en bleu. Comme je voulais que toute ma scène soit visible au travers, je la dessine presque en dernier (ce qu'il y a après est seulement ce qui n'apparaîtra jamais dedans).


Les Textures

Textures

Jusqu’ici, les surfaces étaient simplement coloriées, ce qui ne permet pas d’avoir des rendus très réalistes. Les textures améliore considérablement le rendu. Le principe est de remplir une surface avec une image bitmap correspondant à une photo de la surface.

1. Principe général

Un texture est appliquée à une surface de la manière suivante :

· d’abord il faut indiquer quelle image il va falloir appliquer (ce qui revient à donner un pointeur vers l’image).
· ensuite, avant d’entrer chaque point (vertex) de la surface, il faut préciser où se situe ce point dans la texture, c’est le même principe qu’avant avec glColor. Cela revient en quelque sorte à mettre des épingles sur la texture

OpenGL se charge ensuite d’appliquer la texture, en faisant correspondre les vertex avec les « épingles ».

2. En OpenGL

Dans ce cours, les routines de chargement d’images en mémoire ne seront pas expliquées. Ce dont à besoin OpenGL, c’est d’une image non compressée chargée sous la forme d’un tableau en mémoire. Il existe de nombreuses routines toutes faites pour charger les principaux formats d’images (BMP, PCX, GIF, PNG…).

2.1. Spécifier l’image

Pour spécifier la texture à utiliser, appeler :

void glTexImage2D( GLenum target, GLint level, GLint components, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels )
Avec :
Target | GL_TEXTUTE_2D
Level | Indique le niveau de mipmapping, expliqué plus loin dans ce cours
Components | Nombre de composantes dans la texture (1, 2, 3 ou 4). Généralement 3 (pour RGB) ou 4 (pour RGBA).
Width | Largeur de l’image. Doit être 2n + 2 (dépend de bordure).
Height | Hauteur de l’image. Doit être 2m + 2 (dépend de bordure).
Bordure | Indique la largeur de la bordure : 0 ou 1
Format | Codage des couleurs : GL_COLOR_INDEX, GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA, GL_LUMINANCE, ou GL_LUMINANCE_ALPHA
Type | Type du tableau : GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, ou GL_FLOAT
Pixel | Pointeur vers l’image.

2.2. Les « épingles »

La première chose à faire, c’est d’activer les textures :

glEnable(GL_TEXTURE_2D) ;

Pour poser une épingle, avant un vertex il faut appeler :

void glTexCoord2f( GLfloat s, GLfloat t )

ou l’une de ses variantes.
Les coordonnées (s, t) indiquent la correspondance entre le prochain vertex entré et la texture. En OpenGL, le repère d’une texture fait toujours [0..1] sur [0..1], mais s et t peuvent sortir des ces intervalles, l’effet dépend alors du paramètrage choisi.

  • Lorsque s et t sont compris entre 0 et 1, OpenGL va positionner les épingles directement sur la texture. Vous pourrez ainsi utiliser seulement une petite partie de celle-ci. Si la surface sur laquelle vous posez la texture est trés grande, il va étirer la texture pour la placer sur votre surface.
  • A l'inverse, si t et s (ou seulement un seul) sont supérieurs à 1, la texture va être répétée.

La texture appliquée sur la base du temple est répétée plusieurs fois.
La base a une longueur de 40 unités sur 4. Les valeurs de s et t sont respectivement 2 et 20
(les valeurs suivantes sont généralement intéressantes : s = 1/2 * largeur et t = 1/2 * hauteur)

2.3. Paramétrage

2.3.1. Application de la texture

Les paramètres d’application d’une texture sont :

Nom | Type | Valeur possibles | Signification
GL_TEXTURE_WRAP_S | Entier | GL_CLAMP, GL_CLAMP_TO_EDGE, GL_REPEAT | Indique ce qui se passe si une coordonnée s sort de l’intervalle [0..1]. Soit la texture est répètée (mosaïque), soit seulement le bord est répété.
GL_TEXTURE_WRAP_T | Entier | Idem | Idem pout t
GL_TEXTURE_MIN_FILTER | Entier | NEAREST, LINEAR, NEAREST_MIPMAP_NEAREST, NEAREST_MIPMAP_LINEAR, LINEAR_MIPMAP_NEAREST, LINEAR_MIPMAP_LINEAR | Indique la qualité de l’agrandissement d’une texture. LINEAR est meilleur que NEAREST, mais plus lent.
GL_TEXTURE_MAG_FILTER | Entier | NEAREST, LINEAR | Idem en réduction

Il existe d’autres paramètres, mais ceux-ci sont de loin les plus importants. Pour changer un paramètre, il faut appeler :

void glTexParameteri(GLenum target, GLenum pname, GLint param )

avec target = GL_TEXTURE_2D, pname l’un des noms de paramètres, et param la nouvelle valeur.

2.3.2. glColor

La couleur courante (spécifiée par glColor), joue un rôle différent suivant les paramètres passés à :

void glTexEnvi( GLenum target, GLenum pname, Glint param )
avec target = GL_TEXTURE_ENV, pname = GL_TEXTURE_ENV_MODE, et param = GL_REPLACE, GL_MODULATE, GL_DECAL, ou GL_BLEND.

Il y a beaucoup de cas suivant le nombre de composantes de la texture. Dans le cas de 3 ou 4 composantes (RGB ou RGBA), les modes les plus courants sont :

· GL_REPLACE, la couleur courante n’intervient pas

· GL_MODULATE, la couleur courante multiplie la texture. Il est alors possible de changer très facilement l’intensité d’une texture, en dessinant d'abord la surface en blanc, par exemple, lequel va être atténué selon la position de la lumière, puis en plaquant la texture. L'intensité de cette dernière va varier selon l'éclairage...

Ici, c'est plutôt sombre
Mais, en changeant la position de la lumière,
cela devient beaucoup plus clair

3. Exemple

Voici un cube texturé. Pour simplifier (et garantir la portabilité), le format d’image est ici le « Raw », c’est-à-dire l’image seule. Paint Shop Pro gère très bien ce format. Le problème est que la taille de l’image n’est pas inclus dans ce fichier. Mon image à une taille de 128*128 et est au format RGB (24 bits = 3 octets).

void init()
{
GLfloat Blanc[] = {1.0f, 1.0f, 1.0f, 1.0f};
//Taille = 128 * 128. RGB 3 octets
char buffer[128*128*3];
FILE *f = fopen("herbe.raw", "rb");

if (f)
{
fread(buffer, 128*128*3, 1, f);
fclose(f);
printf("texture chargée");
//On spécifie qu'elle texture, sa taille, son type ...
glTexImage2D(GL_TEXTURE_2D,0,3, 128, 128, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer);
//cela définit la façon dont la texture est appliquée. LINEAR est le meilleur, mais le + lent.
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
//On répète la texture si s et t sortent des bornes [0,1]
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
//ouf, ca y est !!!
glEnable(GL_TEXTURE_2D);
}else printf("Il y a un problème de fichier!!!!\n");

cube = glGenLists(1);
glNewList(cube, GL_COMPILE);
glBegin(GL_QUADS);
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, Blanc);
glNormal3d(0.0,0.0,1.0);
//on spécifie la position des épingles (on tourne : une seule coordonnée change à la fois)
glTexCoord2d(0,0);
glVertex3d(-1, 1, 1);
glTexCoord2d(0,1);
glVertex3d(-1, -1, 1);
glTexCoord2d(1,1);
glVertex3d( 1, -1, 1);
glTexCoord2d(1,0);
glVertex3d( 1, 1, 1);
glNormal3d(-1.0,0.0,0.0);
//idem glTexCoord2d(0,0);
glVertex3d( -1, 1, 1);
glTexCoord2d(0,1);
glVertex3d( -1, 1, -1);
glTexCoord2d(1,1);
glVertex3d( -1, -1, -1);
glTexCoord2d(1,0);
glVertex3d( -1, -1, 1);
//idem
glNormal3d(1.0,0.0,0.0);
glTexCoord2d(0,0);
glVertex3d( 1, 1, 1);
glTexCoord2d(0,1);
glVertex3d( 1, -1, 1);
glTexCoord2d(1,1);
glVertex3d( 1, -1, -1);
glTexCoord2d(1,0);
glVertex3d( 1, 1, -1);
glNormal3d(0.0,1.0,0.0);
//idem glTexCoord2d(0,0);
glVertex3d( -1, 1, 1);
glTexCoord2d(0,1);
glVertex3d( 1, 1, 1);
glTexCoord2d(1,1);
glVertex3d( 1, 1, -1);
glTexCoord2d(1,0);
glVertex3d( -1, 1, -1);
glNormal3d(0.0,-1.0,0.0);
//idem
glTexCoord2d(0,0);
glVertex3d( -1, -1, 1);
glTexCoord2d(0,1);
glVertex3d( -1, -1, -1);
glTexCoord2d(1,1);
glVertex3d( 1, -1, -1);
glTexCoord2d(1,0);
glVertex3d( 1, -1, 1);
glNormal3d(0.0,0.0,-1.0);
//idem
glTexCoord2d(0,0);
glVertex3d( 1, 1, -1);
glTexCoord2d(1,0);
glVertex3d( 1, -1, -1);
glTexCoord2d(1,1);
glVertex3d( -1, -1, -1);
glTexCoord2d(0,1);
glVertex3d( -1, 1, -1);
glEnd();
glEndList();
}
}

Sources et éxécutable

Autres Formats :
Voici 2 fichiers ZIP contenant les sources de petites librairies qui permettent de charger des fichiers TGA et BMP.
Download TGA.zip
Download BMP.zip

4. MipMapping

Le MipMapping consiste à avoir la même texture à plusieurs échelles, de façon à limiter les transferts mémoires lors du plaquage de texture (inutile de transférer une texture 256x256 si l’objet dessiné est très loin dans le décor et ne fait plus que du 5x5).

En théorie, le programmeur devrait avoir 3 ou 4 fois la même texture, mais avec une résolution différente. Il faut alors entrer toutes les textures à chaque fois, en changeant le paramètre level de glTextImage2D (sachant que 0 est l’image la plus grande, et les suivantes sont ses réductions). Lorsque la texture est appliqué, OpenGL soit choisi l’image de taille la plus proche de la surface à afficher (NEAREST_MIPMAP), soit fait une sorte de moyenne entre l’image de taille juste au dessus et celle juste en dessous (LINEAR_MIPMAP).

En pratique, il y a une fonction glu :

int gluBuild2DMipmaps(GLenum target, GLint components, GLint width, GLint height, GLenum format, GLenum type, const void *data )

Les paramètres sont les mêmes que pour glTexImage2D, sauf qu’il est cette fois possible d’utiliser des images de dimensions qui ne sont pas des puissances de 2. L’image passée est celle de meilleure qualité (level = 0).

5. Chargement dans la carte vidéo

L’intérêt du MipMapping est très limité si les textures ne sont pas stockées dans la carte vidéo.
Le principe est similaire à celui des listes :

void glGenTextures( GLsizei n, GLuint * textures)

permet de réserver n textures, les numéros sont alors placés dans le tableau pointé par textures. Ensuite le numéro de la texture courante est changé par :

void glBindTexture( GLenum target, GLuint texture)

avec target = GL_TEXTURE_2D et texture le numéro de la texture à activer. Ensuite toutes les fonctions faisant intervenir une texture utilisent la texture active.

Les textures sont libérées par :
void glDeleteTextures(GLsizei n, const GLuint * textures )

Pour résumer, une texture est le plus souvent chargée par :

glGenTextures(1,
            &Texture_num);
glBindTexture(GL_TEXTURE_2D, Texture_num);
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, width, height(),
            GL_RGB, GL_UNSIGNED_BYTE, data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

Ensuite pour activer une texture, avant un glBegin :

glBindTexture(GL_TEXTURE_2D, Texture_num);

6. Priorités des textures

Comme il se peut que toutes les textures ne rentrent pas dans la carte vidéo, il est possible de donner des priorités au chargement des textures en mémoire vidéo.

void glPrioritizeTextures( GLsizei n, GLuint * textures, GLclampf * priorities)

avec priorities un tableau de n flottants appartenant à [0..1], 0 étant la priorité la plus faible et 1 la plus forte.


Les différents tests

Tests de validation et stencil buffer

1. Chaîne de tests

Voici la liste complète des tests de validation d’un point :

à Scissor Test à Alpha Test à Stencil Test à Depth Test à […] à framebuffer

2. Scissor test

Le scissor test est paramétré par :

void glScissor( GLint x, GLint y, GLsizei width, GLsizei height )

Cela permet de limiter l’affichage à un rectangle dont le coin haut – gauche à pour coordonnées (x, y) dans la fenêtre. Par défaut (x, y) = (0, 0) et width et height ont la même taille que la fenêtre.
Le test est activé par :

glEnable(GL_SCISSOR_TEST)

3. Alpha test

Un point peut être éliminé par sa valeur alpha avec :

void glAlphaFunc( GLenum func, GLclampf ref )

avec func = GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, ou GL_ALWAYS
Ne pas oublier, comme toujours : glEnable(GL_ALPHA_TEST)

4. Stencil test

Ce test est plus complexe, mais plus utile que les deux précédents. Tout comme le depth-buffer, le stencil-buffer est un espace mémoire de la taille du framebuffer. Mais cette fois son utilisation est libre. Sous AUX, ne pas oublier de le rajouter à l’initialisation :

auxInitDisplayMode(AUX_DOUBLE | AUX_RGBA | AUX_DEPTH | AUX_STENCIL);

4.1. Effacement

D’abord, le stencil étant comme le depth un buffer, il faut (le plus souvent) l’effacer à chaque image avec :

glClear(… | GL_STENCIL_BUFFER_BIT) ;

La valeur de remplissage est paramétrable avec :

void glClearStencil( GLint s )

4.2. Initialisation

Comme pour tous les tests, il faut appeler :

glEnable(GL_STENCIL_TEST) ;

Le test est paramétré par deux fonctions :

void glStencilFunc( GLenum func, GLint ref, GLuint mask)

qui sert à indiquer le test à effectuer, les paramètres sont :

· func = GL_NEVER, GL_LESS, GL_LEQUAL, GL_GREATER, GL_GEQUAL, GL_EQUAL, GL_NOTEQUAL, ou GL_ALWAYS

· ref = valeur de référence du test

· mask = masque binaire à appliquer à la valeur du stencil et à ref pour le test

La fonction :

void glStencilOp( GLenum fail, GLenum zfail, GLenum zpass )

sert à indiquer ce qui se passe suivant le résultat du test du stencil et du depth, avec :

· "fail" indique l’opération à effectuer sur la valeur dans le stencil lorsque le stencil test a échoué. Peut être égal à GL_KEEP, GL_ZERO, GL_REPLACE, GL_INCR, GL_DECR, ou GL_INVERT.

· " zfail" indique de même l’opération à effectuer si le stencil est passé mais pas le depth.

· "zpass" indique l’opération à effectuer si le stencil et le depth sont validés.

4.3 Utilité

Pour le moment, l'utilité du "Stencil_buffer" ne doit pas vous sauter aux yeux. Et pourtant, il est extrémement pratique. Nous verrons dans le chapître suivant 2 grandes utilisation de ce "buffer" : les ombres simples et le reflet dans un miroir. Mais pour l'instant, nous allons voir en gros, à quoi il sert.

Le stencil buffer permet de dessiner une scène quelconque, non pas dans la fenètre toute entière, mais dans une forme quelconque. Imaginez que vous voulliez dessiner un cube à l'écran, mais au travers d'un hublot, rond et non pas la fenètre toute entière, carrée. Vous allez activer le "Stencil-Test", dessiner votre hublot dans le Stencil buffer (et non pas à l'écran, nous verrons comment au prochain chapître), puis dessiner votre cube, comme à l'accoutumé, à l'écran cette fois-ci. Seule la portion de votre cube se trouvant sur le hublot sera déssinée.

La scène n'est affichée que dans la pseudo-ellipse

Autre exemple : le "stencil buffer" sert aussi lorsque l'on veut afficher un tableau de score, un décor statique comme dans les jeux tels que Tétris (une partie de l'écran est fixe, ou presque)


Ombres et reflets

La principale application du stencil buffer, ce sont les ombres et les reflets. Ce sont 2 trucs assez sympas, pas trop durs à réaliser si on accepte certaines limitations.

1. Reflets

1.1. Principe

Les reflets sur un plan sont très simples : ce n’est qu’un double d’un objet, recouvert d’une surface légèrement transparente pour donner l’illusion qu’il s’agit d’un reflet. En plus, ce qui simplifie bien les choses, il existe la fonction :

void glScalef( GLfloat x, GLfloat y, GLfloat z )

qui multiplie la matrice courante par une matrice multipliant toutes les coordonnées entrées par les valeurs spécifiées, c’est-à-dire une matrice diagonale (x, y, z, 1). Dans notre cas, pour effectuer une symétrie par rapport au plan y = 0, il suffit d’entrer glScalef(1.0f, -1.0f, 1.0f).
Seul problème : le reflet peut sortir de la surface miroir ! C’est là que le stencil buffer intervient. La surface réfléchissante est d’abord « virtuellement » dessinée, en fait elle est dessinée normalement sauf que pour empêcher l’affichage on utilise :

void glColorMask( GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha )

avec tous les paramètres à GL_FALSE. Cela permet de changer la valeur du stencil là où la surface va être dessinée.

Les étapes du reflets sont finalement :

· Limitation du reflet à la surface réfléchissante :

glDisable(GL_DEPTH_TEST);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
Dessin de la surface réfléchissante
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glEnable(GL_DEPTH_TEST);

· Dessin du reflet :

  glStencilFunc(GL_EQUAL, 1, 0xffffffff);
                    /* draw if ==1 */
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glPushMatrix();
glScalef(1.0, -1.0, 1.0);
respositionnement des lumières
dessin de l’objet reflété
glPopMatrix();
respositionnement des lumières
glDisable(GL_STENCIL_TEST);

· Dessin de la surface réfléchissante :

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Dessin de la surface réfléchissante
glDisable(GL_BLEND);
· Dessin de l’objet

1.2. Exemple

Voici le reflet d'un cube sur le sol :

void CALLBACK Main_part(void)
{
GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 1.0f};
GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 1.0f};
GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 1.0f};

glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glDisable(GL_DEPTH_TEST);
//on ne va pas dessiner dans le buffer-écran, mais dans les autres buffers
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
//Dessin de la surface réfléchissante dans le stencil-buffer
drawFloor();
//on reactive le dessin à l'ecran
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glEnable(GL_DEPTH_TEST);
//cela va dessiner uniquement dans les endroits du stencil-buffer qui sont à 1
//c.a.d. dans le miroir
glStencilFunc(GL_EQUAL, 1, 0xffffffff);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glCullFace(GL_BACK);
glPushMatrix();
//on fait la symétrie par rapport à l'axe des Z
glScalef(1.0, 1.0, -1.0);
//les lumières aussi subissent la symétrie
SetLight();
//on dessine la scène qui va être reflétée
glTranslatef(0.0f, 0.0f, 5.0f);
glRotatef((80.0f), -0.5f, 0.3f, -0.8f);
glRotatef((10.0f), 0.5f, -0.3f, 0.8f);
SetLight();
glCallList(cube);
glPopMatrix();
//fin de la symétrie
//on remet les lumières et tout le reste
SetLight();
glCullFace(GL_FRONT);
glDisable(GL_STENCIL_TEST);
//on dessine le sol transparent
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_LIGHTING);
drawFloor();
glEnable(GL_LIGHTING);
glDisable(GL_BLEND);
//puis le reste de la scène
glPushMatrix();
glTranslatef(0.0f, 0.0f, 5.0f);
glRotatef((80.0f), -0.5f, 0.3f, -0.8f);
glRotatef((10.0f), 0.5f, -0.3f, 0.8f);
glCallList(cube);
glPopMatrix();
auxSwapBuffers();
}

Sources et éxécutable

1.3 Tout n'est pas parfait !!!

Ben, oui, il faut le reconnaître, cet algorithme est pas mal, mais il ne gère pas un truc. Quand un objet se trouve juste derrière le sol sur lequel la scène se reflète, il apparaît dans le miroir alors qu'il devrait être caché. ceci est dû à la symétrie : lors de cette dernière, l'objet passe devant le miroir. vient alors le "Stencil-Test". L'image est alors découpée autour du miroir. Mais commel l'objet EST devant le miroir, il apparait !!!!

"Mais, il suffit de tenir compte du "Depth_Test" lors du "Stencil-test" ", me direz-vous. Ben, j'ai essayé et ca n'a pas marché. Ou plutot si, ca marchait, mais j'avais alors d'autre problèmes : objets cachés qui apparaissaient dans le miroir, miroir ne faisant apparaitre QUE l'objet en question .Bref, je n'ai pas réussi. Vous avez une idée??? Mailez-moi !!!

2. Ombres

2.1. Principe

Le stencil buffer joue toujours son rôle de délimitation, pour l’ombre cette fois. Reste à dessiner l’ombre elle-même, or qu’est-ce qu’une ombre ? C’est la projection de tout un objet sur un plan suivant la direction de la lumière. Cela fait (devrait faire ?) immédiatement penser à « matrice de projection ». Effectivement il suffit de multiplier la matrice ModelView par une matrice de projection pour avoir le résultat voulu.

Quelques détails tout de même :

- l’ombre n’est pas le dessin de l’objet lui-même mais une surface noire (à moitié transparente). Bien que dans la réalité, ce n’est pas exactement cela : c’est tout le reste de la scène qui est plus éclairée. Cependant il est bien plus rapide d’assombrir là où est l’ombre que d’éclaircir tout le reste. S’il n’y a aucune lumière ambiante, l’ombre n’est pas transparente.
- avec cette méthode, il faut faire attention à ne pas assombrir deux fois le même point : ce n’est pas parce que deux surfaces cachent la lumière que l’ombre est deux fois plus sombre ! L’astuce consiste ici à incrémenter le stencil lorsque le stencil test et le depth test passent (dès qu’un point est assombri, il ne fait plus « partie » de la surface).
Cependant, il existe un cas où il faut volontairement oublier ce fait : lorsque l’objet projeté est en partie transparent. Il est alors possible d’avoir un assombrissement proportionnel au nombre de surfaces qui cachent la lumière. Sauf qu’il ne faut pas trop en abuser, le temps de calcul s’allongeant encore.
- le problème de la matrice de projection : elle n’est pas dans glu ! Voici un moyen de la calculer :
void MakeShadowMatrix(GLfloat points_plan[3][3], GLfloat lightPos[4], GLfloat destMat[4][4])
{
GLfloat planeCoeff[4];
GLfloat dot;

//on calcule un vecteur normal à ce plan
Normale(points_plan,planeCoeff);

// le dernier coefficient se calcule par substitution
planeCoeff[3] = - ( (planeCoeff[0]*points_plan[2][0]) + (planeCoeff[1]*points_plan[2][1]) + (planeCoeff[2]*points_plan[2][2]));
dot = planeCoeff[0] * lightPos[0] + planeCoeff[1] * lightPos[1] + planeCoeff[2] * lightPos[2] + planeCoeff[3] * lightPos[3];

//
                                Now do the projection
// 1ère colonne
destMat[0][0] = dot - lightPos[0]
                                * planeCoeff[0];
destMat[1][0] = 0.0f - lightPos[0]
                                * planeCoeff[1];
destMat[2][0] = 0.0f - lightPos[0]
                                * planeCoeff[2];
destMat[3][0] = 0.0f - lightPos[0]
                                * planeCoeff[3];
//
                                2ème colonne
destMat[0][1] = 0.0f - lightPos[1]
                                * planeCoeff[0];
destMat[1][1] = dot - lightPos[1]
                                * planeCoeff[1];
destMat[2][1] = 0.0f - lightPos[1]
                                * planeCoeff[2];
destMat[3][1] = 0.0f - lightPos[1]
                                * planeCoeff[3];
//
                                3ème colonne destMat[0][2] = 0.0f
                                - lightPos[2] * planeCoeff[0];
destMat[1][2] = 0.0f - lightPos[2]
                                * planeCoeff[1];
destMat[2][2] = dot - lightPos[2]
                                * planeCoeff[2];
destMat[3][2] = 0.0f - lightPos[2]
                                * planeCoeff[3];
//
                                4ème colonne destMat[0][3] = 0.0f
                                - lightPos[3] * planeCoeff[0];
destMat[1][3] = 0.0f - lightPos[3]
                                * planeCoeff[1];
destMat[2][3] = 0.0f - lightPos[3]
                                * planeCoeff[2];
destMat[3][3] = dot - lightPos[3]
                                * planeCoeff[3];
}

Les étapes de la création de l’ombre sont alors :

· Limitation de l’ombre à la surface :
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
Dessin de la surface
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glEnable(GL_DEPTH_TEST);
glDisable(GL_STENCIL_TEST);

· Dessin de l’objet

· Calcul de la matrice de projection (avec vfloor des points de la surface) :
GLfloat floorPlane[4];
GLfloat floorShadow[4][4];
findPlane(floorPlane, vfloor[1], vfloor[2], vfloor[3]);
shadowMatrix(floorShadow, floorPlane, lightZeroPosition);

· Changement des paramètres pour dessiner une surface noire transparente :
glStencilFunc(GL_EQUAL, 1, 0xffffffff); /* draw if ==1 */
glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_LIGHTING);
glDisable(GL_TEXTURE_2D);
glDisable(GL_DEPTH_TEST);
glColor4f(0.0, 0.0, 0.0, 0.5);

· Dessin de l’ombre :
glPushMatrix();
glMultMatrixf((GLfloat *) floorShadow);
dessin de l’objet
glPopMatrix();

2.2. Exemple

Voici le même cube, mais avec son ombre sur le sol. J'ai copié ci-dessous tout ce qui a changé par rapport à la dernière fois.
//On a besoin de 3 points sur le sol
//Remarquez que j'ai un peu triché : les points ont une valeur de 0.05f pour leur hauteur au sol.
//Ceci a pour but d'éviter ce que l'on appelle le "Z-figthing",
// c'est à dire les problèmes d'approximation qui, par endroit, amène l'ombre a passer sous le sol !!!
//Elle est alors invisible. Cela peut se régler avec le Stencil buffer, mais ce n'est pas le but ici.

GLfloat sol[3][3] = {{0.0f,0.0f,0.05f}, {4.0f,0.0f,0.05f}, {0.0f,4.0f,0.05f}};
GLfloat ombre[4][4];

void cube(int ombre)
{
GLfloat Rouge[] = {0.8f, 0.0f, 0.0f, 1.0f};
GLfloat Vert[] = {0.0f, 0.8f, 0.0f, 1.0f};
GLfloat Bleu[] = {0.0f, 0.0f, 0.8f, 1.0f};
GLfloat Jaune[] = {0.8f, 0.8f, 0.0f, 1.0f};
GLfloat Rose[] = {0.8f, 0.0f, 0.8f, 1.0f};
GLfloat Cyan[] = {0.0f, 0.8f, 0.8f, 1.0f};

glBegin(GL_QUADS);
//Si il s'agit de l'ombre,
//on dessine le cube noir légèrement transparent
if (!ombre) glColor4fv(Rouge); else glColor4f(0.0f, 0.0f, 0.0f, 0.25f);
glNormal3d(0.0,0.0,1.0);
glVertex3d(-1, 1, 1);
glVertex3d(-1, -1, 1);
glVertex3d( 1, -1, 1);
glVertex3d( 1, 1, 1);
if (!ombre) glColor4fv(Jaune);
glNormal3d(-1.0,0.0,0.0);
glVertex3d( -1, 1, 1);
glVertex3d( -1, 1, -1);
glVertex3d( -1, -1, -1);
glVertex3d( -1, -1, 1);
if (!ombre) glColor4fv( Vert);
glNormal3d(1.0,0.0,0.0);
glVertex3d( 1, 1, 1);
glVertex3d( 1, -1, 1);
glVertex3d( 1, -1, -1);
glVertex3d( 1, 1, -1);
if (!ombre) glColor4fv( Rose);
glNormal3d(0.0,1.0,0.0);
glVertex3d( -1, 1, 1);
glVertex3d( 1, 1, 1);
glVertex3d( 1, 1, -1);
glVertex3d( -1, 1, -1);
if (!ombre) glColor4fv( Bleu);
glNormal3d(0.0,-1.0,0.0);
glVertex3d( -1, -1, 1);
glVertex3d( -1, -1, -1);
glVertex3d( 1, -1, -1);
glVertex3d( 1, -1, 1);
if (!ombre) glColor4fv( Cyan);
glNormal3d(0.0,0.0,-1.0);
glVertex3d( 1, 1, -1);
glVertex3d( 1, -1, -1);
glVertex3d( -1, -1, -1);
glVertex3d( -1, 1, -1);
glEnd();
}

void drawFloor()
{
GLfloat Gris[] = {0.25f, 0.25f, 0.25f, 1.0f};

glColor4fv(Gris);
glBegin(GL_QUADS);
glVertex3f(-4.0, 4.0, 0.0);
glVertex3f( 4.0, 4.0, 0.0);
glVertex3f( 4.0, -4.0, 0.0);
glVertex3f(-4.0, -4.0, 0.0);
glEnd();
}

void SetLight()
{
GLfloat ambientProperties[] = {0.7f, 0.7f, 0.7f, 1.0f};
GLfloat diffuseProperties[] = {1.0f, 1.0f, 1.0f, 1.0f};

glLightfv( GL_LIGHT0, GL_AMBIENT, ambientProperties);
glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuseProperties);
glLightfv( GL_LIGHT0, GL_POSITION, light_pos);
glEnable(GL_LIGHT0);
glEnable(GL_LIGHTING);
glEnable(GL_COLOR_MATERIAL);
}

void CALLBACK Main_part(void)
{
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
SetLight();
//on dessine la scène normalement
drawFloor();
glPushMatrix();
glTranslatef(0.0f, 0.0f, 5.0f);
glRotatef((80.0f), -0.5f, 0.3f, -0.8f);
glRotatef((10.0f), 0.5f, -0.3f, 0.8f);
//dessin du cube en couleur normale
cube(0);
glPopMatrix();
//on initialise la transparence
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//Calcul de la matrice de projection sur le sol
MakeShadowMatrix(sol,light_pos,ombre);
glPushMatrix();
glMultMatrixf((GLfloat *) ombre);
//dessin de l'objet
glTranslatef(0.0f, 0.0f, 5.0f);
glRotatef((80.0f), -0.5f, 0.3f, -0.8f);
glRotatef((10.0f), 0.5f, -0.3f, 0.8f);
//on dessine le cube en noir transparent
cube(1);
glPopMatrix();
glDisable(GL_BLEND);
auxSwapBuffers();
}

Sources et éxécutable


Moteur3D

Un p'tit Moteur de rendu 3D

(Release 1.0)

J'ai récapitulé tout ce que nous avons vu dans un unique programme, une sorte de petit moteur 3D. Il gère et utilise donc :

  • les "CallListes",
  • les textures,
  • les ombres,
  • le reflet,
  • le brouillard,
  • les collisions.

J'ai essayé au maximum de commenter le code, mais comme toujours il y a des choses qui sont claires pour moi et que je n'ai peut-être pas bien expliquées. Si jamais vous ne comprenez pas quelque-chose, ou si vous trouvez un bug (cherchez pas, il n'y en pas -:::), faites le moi savoir.

Voici un petit bassin !

Une petite maison toute simple. Remarquez le placage bizarre des textures sur le mur :
c'est ma pauvre ATI Rage Pro 8 Mo qui a du mal à les placer...
(avec une TNT1, il n'y a plus de problème)

Le petit reflet, toujours bien marrant !
(la prochaine version gèrera les miroirs sous tous les angles !!!)

Sources et éxécutable
(Les sources sont enfin documentés !!!)

Page précédente


<img src="ogl_sm.jpg"><br/>Tutoriaux OpenGL

Accueil
Recherche
Publications
Enseignement
ENSIMAG
ESISAR
» Tutoriaux OpenGL
FAQ
Introduction
Initialiser OpenGL avec Windows
Initialiser OpenGL avec GLUT
Les bases
Les couleurs
Les lumières
Les spots
Les textures
L'input avec GLUT
L'input avec Windows
Matrices 3DSMAX
Format BMP
Tutorials DOS
CV
Coin perso
Avant-Propos
Accéder aux tutoriaux
Liens
Menu

Tutoriaux OpenGL
Contenu

Avant-Propos

⇑
Voici une série de tutoriaux (anciennement nommés "Les tutorials OpenGL d'Antoche") que j'avais écrit quand j'ai appris OpenGL, en 2000. Comme il semble qu'ils soient bien appréciés par pas mal de gens, je les conserve ici.

ATTENTION : ces tutoriaux ont été mirrorés sur beaucoup de sites, qui n'ont pas répercuté les corrections que j'y ai faites. Par conséquent :
  • Lisez ceux qui sont sur cette page, pas les autres
  • Si vous trouvez une erreur, si vous avez un problème, REGARDEZ DANS LA FAQ EN PREMIER si il/elle n'y est pas déjà référencée ! Je ne répondrai pas aux questions qui trouvent leur réponse danq la FAQ.

Accéder aux tutoriaux

⇑
  1. FAQ
  2. Introduction à OpenGL
  3. Initialiser OpenGL avec Windows
  4. Initialiser OpenGL avec GLUT
  5. Les bases
  6. Les couleurs
  7. Les lumières
  8. Les spots
  9. Les textures
  10. L'input avec GLUT
  11. L'input avec Windows
  12. Voir aussi mon cours sur GLSL a l'ENSIMAG

Liens

⇑
OpenGL:
  • en OpenGL official website:
    • en The OpenGL FAQ
    • en GLUT official website
  • en OpenGL extensions
  • en The redbook (The OpenGL Programming Guide) on-line
  • en The bluebook (The OpenGL Reference) on-line
  • en libQGLViewer
  • en Nate Robins
  • en CodeSampler.com
  • en opengl-gamedev list
  • fr GlInFrench
  • fr EraQuila
  • fr Le tutoriel OpenGL de Fabrice Neyret
  • fr Le manuel OpenGL d'Edmond Boyer
Voir mon coin perso pour plus de liens, notamment sur la programmation en général.

← ESISAR ↑ Enseignement ↑ Matrices 3DSMAX →
Last modified on Wednesday December 13th, 2006 E-mail any comment


OpenGL : Présentation

Accueil
Recherche
Publications
Enseignement
ENSIMAG
ESISAR
Tutoriaux OpenGL
FAQ
» Introduction
Initialiser OpenGL avec Windows
Initialiser OpenGL avec GLUT
Les bases
Les couleurs
Les lumières
Les spots
Les textures
L'input avec GLUT
L'input avec Windows
Matrices 3DSMAX
Format BMP
Tutorials DOS
CV
Coin perso
1. OpenGL : Quoi qu'est-ce ?
1.1 Qu'est-ce qu'OpenGL ?
1.2 OpenGL vs Direct 3D
2. OpenGL : Principe
2.1 OpenGL : Le pourquoi du comment de la portabilité
2.2 OpenGL : les bases
3. Où télécharger OpenGL ?
4. OpenGL : Récapitulation
Menu

OpenGL : Présentation
Contenu

1. OpenGL : Quoi qu'est-ce ?

⇑

1.1 Qu'est-ce qu'OpenGL ?

⇑
Si vous êtes le propriétaire d'un certain système d'exploitation nommé Windows, vous aurez probablement vu quelques écrans de veille pompeusement appelés 'écrans de veille OpenGL', qui affichent de magnifiques animations 3D, comme une boite changeant de forme, ou des canalisations parcourant l'écran dans tous les sens (mais pourquoi des canalistations, bordel ?). Hé bien OpenGL, c'est la librairie qui permet d'aboutir à ces screensavers (pardon, écrans de veille) hyper évolués. Mais sachez qu'OpenGL n'est pas un petit machin pourri servant juste à faire des anims pourries. Non, en fait OpenGL est une librairie graphique 3D très évoluée et totalement portable offrant de nombreuses ressources aux programmeurs cherchant à faire de la synthèse d'images (pour les jeux par exemples).

1.2 OpenGL vs Direct 3D

⇑
Direct3D est grosso-modo la même chose qu'OpenGL, sauf que c'est une librairie spécifique à Windows, gérée uniquement par Microsoft (OpenGL est gérée par un consortium d'entreprises du secteur), et que la manière de la programmer est relativement différente.
A tous les idiots qui vous disent que l'une est plus performante que l'autre, rétorquez que ce qui fait la performance des API 3D aujourd'hui c'est la carte graphique utilisée et, dans une certaine mesure, les drivers.

2. OpenGL : Principe

⇑
Bon, maitenant que vous savez ce qu'est réellement OpenGL, voyons comment ca fonctionne à l'intérieur, et tout d'abord, comment il peut être si totalement portable.

2.1 OpenGL : Le pourquoi du comment de la portabilité

⇑
En fait, si OpenGL est 100% portable avec tous les systèmes d'exploitation, c'est tout simplement parce que ce n'est pas lui qui se charge de l'affichage. Hé oui ! Ca peut peut-être sembler bizarre pour une librairie graphique, mais en fait non. Et c'est aussi la raison pour laquelle OpenGL est obligé de s'appuyer sur un système d'exploitation ayant une interface graphique. Je m'explique : en fait, OpenGL sert d'intermédiaire entre le programme et le système d'exploitation, en triturant et traduisant les informations données par le programme et en les envoyant au système d'exploitation. Plus précisément, expliquons le boulot à faire par le programmeur pour initialiser OpenGL : tout d'abord, il doit initialiser un environnement graphique dans le système d'exploitation (c'est-à-dire une fenêtre, dans le cas de Windows). Cette fenêtre sera définie de manière unique par un Device Context, une sorte de handle qui permet au programme d'accéder à cette fenêtre et de lui faire subir toutes les opérations d'affichage possible (affichage de pixels, etc...). Ensuite, le travail sera d'associer à ce Device Context un Rendering Context, qui va permettre à OpenGL de s'adresser à la fenêtre pour lui envoyer ses ordres de dessin. Avec un shéma ca ira mieux :

Programme » | OpenGL » | Rendering Context » | Device Context » | Affichage
Géré par le programme |  | Géré par l'OS

Voila pourquoi OpenGL est si portable : il se contente de mixer les infos et de les balancer à l'OS. A lui enuite de les afiicher. La seule contrainte pour l'OS est donc de pouvoir dialoguer avec OpenGL. Ceci est par exemple possible sous Windows grâce au driver opengl32.dll disponible dès installation et qui s'occupe de la traduction des données OpengGL en données compréhensible pour la carte vidéo. Et c'est là qu'intervient l'accélération matérielle. Car OpenGL est désormais accéléré matériellement par toutes les cartes graphiques actuelles. Pour obtenir donc que vos programmes OpenGL soient accélérés par votre carte 3D, il suffit de remplacer l'opengl32.dll de base, qui fait du rendu software, par un opengl32.dll traduisant les données OpenGL en données directement compréhensible par la carte 3D, c'est-à-dire en clair installer les drivers de votre carte 3D.
Mais ce mode de fonctionnement impose intrinsèquement une contrainte pour le programmeur : il doit d'abord initialiser l'affichage à travers l'OS (c'est-à-dire créer une fenêtre pour récupérer un Device Context) pour initialiser OpenGL. Et ca c'est encore pas trop lourd sous Windows, mais c'est du masochisme sous X. Heureusement, il existe une librairie, nommée glut, qui perment de faire tout ca sans se préoccuper de l'OS, ce qui rend le code 100% portable. Pour ma part, je trouve cette librairie pas très pratique, surtout lorsqu'on est en train de développer. De plus, comme nous allons voir uniquement la programmation sous Windows, la création de la fenêtre ne devrait pas être trop dure. Mais vous pourrez trouver beaucoup d'infos sur glut sur internet et vous en servir par la suite si ca vous chante. Sachez cependant qu'il est préférable de débuter sans glut car cela permet d'avoir une meilleure appréhension du fonctionnement d'OpenGL (enfin c'est mon avis).

2.2 OpenGL : les bases

⇑
OpenGL est axé selon un principe de primitives : chaque objet est composé d'éléments basiques comme les sommets, les faces, etc..., appelés primitives. Pour créer un objet, il suffit donc de lui indiquer toutes ses primitives. Par exemple, une face carrée sera générée de cette façon :

glBegin(GL_QUADS);
glVertex3f(1.0,1.0,0.0);
glVertex3f(0.0,1.0,0.0);
glVertex3f(0.0,0.0,0.0);
glVertex3f(1.0,0.0,0.0);
glEnd();

Et voilà c'est tout ! Avouez quand même que c'est simple, clair, lisible, basique... bref : c'est génial !

3. Où télécharger OpenGL ?

⇑
Pour programmer en OpenGL, il faut posséder les headers (fichiers .h) et les librairies qui les accompagnent (.dll et .lib pour windows, .sl ou .a pour unix...). Je ne peux pas vous fournir les librairies Unix ou Mac, tout simplement parce que je ne les ai pas, mais par contre je peux vous aider à trouver les librairies Windows. Tout d'abord, il faut savoir qu'OpenGL sans glut est directement inclus dedans à partir de la version 95 OSR2. glut n'est inclus dans aucune des versions de windows. Bref, de toute façon, toutes les librairies OpenGL pour tous les systèmes sont disponibles sur le site officiel d'OpenGL : www.opengl.org. J'en profite au passage pour balancer quelques autres liens sympas, bien qu'anglophones : NeHe (bons tutorials en anglais), www.xmission.com/~nate/opengl.html (autres tutorials par un créateur d'OpenGL), reality.sgi.com/mjk/ (plein plein de liens - anglais, évidemment - par un des créateurs d'OpenGL et de GLUT), reality.sgi.com/mjk/tips (les trucs pour faire de la modélisation avancée - ombrages, transparence... - par le même auteur). Pour ceux qui n'auraient pas compris, les fichiers .h sont à copier dans le répertoire include/ du compilateur, les fichiers .lib dans le répertoire lib/, et les .dll dans le répertoire système de windows. Attention cependant à opengl32.dll : ce driver dépend de votre carte 3D (si vous n'en avez pas, c'est un driver software), et si c'est un mauvais driver (genre MiniGL qui ne fonctionne qu'avec Quake III), mieux ne vaut pas écraser l'ancien. Le truc : le copier dans le répertoire de votre programme OpenGL, comme ça votre programme utilisera ce driver, et les autres programmes tourneront avec le driver système. Pour avoir les derniers drivers de votre carte 3D, il y a un très bon site : www.glsetup.com (tous les drivers windows complets pour toutes les cartes). GLSetup est également présent sur certains jeux utilisant OpenGL (HomeWorld par exemple).

4. OpenGL : Récapitulation

⇑
Qu'est-ce qu'on a appris ici ? Hé bien qu'OpenGL c'est bien, que c'est carrément portable, que l'on crée des objets à partir de leurs primitives, qu'il faut savoir un petit peu parler le langage de son OS, et qu'en gros un programme dessinant en OpenGL se structure à peu près comme ça :

  • Création d'une fenêtre
  • Récupération d'un Device Context pour cette fenêtre
  • Récupération d'un Rendering Context pour ce Device Context
  • Initialisation d'OpenGL à l'aide de ce Rendering Context
  • ...
  • Création d'objets à partir de primitives simples
  • Affichage des objets
  • ...
  • Libération du Rendering Context
  • Libération du Device Context
  • Fermeture du programme
Antoche

← FAQ ↑ Tutoriaux OpenGL ↑ Initialiser OpenGL avec Windows →
Last modified on Friday December 23rd, 2005 E-mail any comment


Initialiser OpenGL avec Windows

Accueil
Recherche
Publications
Enseignement
ENSIMAG
ESISAR
Tutoriaux OpenGL
FAQ
Introduction
» Initialiser OpenGL avec Windows
Initialiser OpenGL avec GLUT
Les bases
Les couleurs
Les lumières
Les spots
Les textures
L'input avec GLUT
L'input avec Windows
Matrices 3DSMAX
Format BMP
Tutorials DOS
CV
Coin perso
1. Créer une fenêtre Windows
2. Attacher OpenGL à cette fenêtre
Menu

Initialiser OpenGL avec Windows
Contenu

1. Créer une fenêtre Windows

⇑

Comme je l'ai dit dans le premier chapitre, il faut obtenir un Device Context (DC), puis un Rendering Context (RC) afin d'initialiser OpenGL. Et pour cela, nous avons besoin de maîtriser quelque peu la gestion de fenêtres dans Windows. Nous allons donc voir comment créer une fenêtre et récupérer les infos nécessaires à l'initialisation.
Il y a en fait deux moyens d'obtenir un DC : soit en se créant sa petite fenêtre en Win32, soit en utilisant les MFC. Ici, je ne vais parler que de Win32, car c'est beaucoup plus simple et pratique que les MFC, qui sont très lourdes et chiantes à utiliser. Mais si vous savez gérer les MFC et compris comment récupérer un DC et un RC, alors vous ne devriez pas avoir de problème à intégrer OpenGL dans vos programmes utilisant les MFC.
Autre chose : j'utilise Ms Visual C++, que je trouve plus pratique pour ce genre de choses. Les procédures spécifiques au compilateur, du genre inclure les .lib, seront donc expliquées pour ce logiciel, et pas pour le Borland C++ Builder. Mais le code restera bien sûr le même quelque soit le compilateur.
Enfin, sachez que la touche F1 est votre amie : à chaque fois que je vous présente une nouvelle fonction, étudiez-la plus en détail avec l'aide de votre compliateur, car je ne peux pas tout vous dire sur tout, ou je n'aurais jamais fini. Servez-vous en donc le plus possible, et si jamais vous avez encore des doutes, envoyez-moi vos questions.

Pour les newbies de VC++, créez d'abord un nouveau projet par File>New>Project>Win32 Application, puis un nouveau fichier C++ par File>New>File>C/C++ Source File.

Bon, commencont par le commencement, à savoir les #include. Il y en a 3 obligatoires, sans lesquels votre programme ne pourra pas marcher. Les voici :

#include <windows.h>
#include <gl/gl.h>
#include <gl/glu.h>

gl.h et glu.h servent bien évidemment à utiliser les fonctions OpenGL. Quand à windows.h, il est utilisé pour créer les fenêtres, et vous n'en avez normalement pas besoin si vous utilisez glut.h. Les header OpenGL sont généralement dans le répertoire \include\gl de votre compilateur.
Mais ce n'est pas tout : les headers se rapportent à des fonctions qui sont définies dans les opengl32.dll et glu32.dll : il faut donc les lier à votre projet. Pour ce faire, avec VC++, allez dans Project>Settings>Links, et incluez opengl32.lib et glu32.lib dans la liste Object/library modules.

Ensuite, il faut savoir que dans un programme Win32, la fonction de départ n'est plus main mais WinMain. Créez donc la première fonction de votre programme :

int WINAPI WinMain( | HINSTANCE hInstance,
 | HINSTANCE hPrevInstance,
 | LPSTR lpCmdLine,
 | int nCmdShow)
{ |

La valeur de retour est la même que pour la fonction main() classique. Voyons maintenant ce que veulent bien dire ces paramètres bizarres :

  • hInstance est une sorte de handle pour votre programme : comme Windows est multitâche, il a besoin de savoir quel programme lui dit quoi. Et c'est à travers cette variable que Windows sait à qui il a affaire : c'est en quelque sorte la carte d'identité du programme. C'est pourquoi il est conseillé de la sauvegarder dans une variable globale, ce que nous allons faire :
HInst = hInstance; //HInst est un variable globale de type HINSTANCE
  • hPervInstance ne sert plus à rien, il est conservé pour des raisons de compaptibilité avec les programmes 16 bits
  • lpCmdLine est un pointeur sur la ligne de commande, pour récupérer les paramètres éventuels
  • nCmdShow est le style initial de la fenêtre : normale, réduite...etc. Il ne nous sert pas à grand chose pour le moment

Hé bien puisque nous avons notre fonction principale, pourquoi ne pas créer tout de suite notre fenêtre ? Allons-y :

HWND OpenGLWindow = CreateWindow
( "BUTTON", //Classe de la fenêtre
"Fenêtre OpenGL", | //Nom de la fenêtre
WS_VISIBLE | WS_BORDER, | //Caractéristiques
0, | //Position x
0, | //Position y
640, | //Largeur
480, | //Hauteur
0, | //Handle de la fenêtre même
0, | //Identifiant de la fenêtre fille
HInst, | //HINSTANCE du programme
NULL /*Chaine de caractères envoyée
en paramètre lors de la création
de la fenêtre*/
);
if (!OpenGLWindow) exit(1);

Je sais que les puristes vont m'incendier, mais je trouve que c'est plus simple de commencer comme ca. Etudions maintenant cette fonction CreateWindow :

  • Elle renvoie une valeur de type HWND, c'est-à-dire un handle pour la fenêtre (tiens, on se rapproche du DC). Il s'apparente au HINSTANCE pour le programme, sauf que là c'est pour une fenêtre. Vous pouvez aussi le sauvegarder en tant que variable globale. S'il est nul, il y a eu erreur.
  • Le premier paramètre correspond à la classe de la fenêtre : c'est elle qui définit presque toutes ses propriétés. Ici, nous avons pris une classe préexistante, pour simplifer l'explication, mais plus tard nous allons devoir en créer une nous-mêmes. Je disais que les puristes allaient m'incendier car en fait on n'utilise jamais ce genre de classe prédéfinie, parce que ca sert à rien, puisqu'on ne peut même pas définir la fonction WinProc. En fait leur seul utilité est d'apprendre à utiliser CreateWindow.
  • Les caractéristiques de la fenêtre peuvent être multiples. Faites F1 pour obtenir toutes les options possibles
  • Les 4 paramètres suivant parlent d'eux-mêmes
  • Les 2 d'après ne servent que dans le cas d'applications à plusieurs fenêtres, donc on s'en fout
  • Et enfin le dernier paramètre permet de passer des arguments à la fonction WinProc avec le message WM_CREATE. Je sais, vous ne comprenez pas, mais bient vous saurez.

Ca y est, la fenêtre est créée. Le programme est fini ? Pas tout à fait. Car il faut gérer les interactions avec Windows. Et pour cela il faut savoir comment le programme communique avec Windows : en fait, ce n'est pas Windows qui transmet au programme les messages le concernant, mais plutt le programme qui doit demander à Windows s'il doit faire quelque chose, grâce à la fonction PeekMessage. En quelque sorte, le programme va voir dans son casier s'il n'y a pas des messages pour lui via PeekMessage, et s'il y en a, il les récupère avec GetMessage, puis les distribue aux membres de son équipe (les fenêtres), via DispatchMessage. C'est très imagé mais au moins c'est clair et je suis sûr de m'être fait comprendre. Voilà donc le code à écrire après la création de la fenêtre :

MSG msg;
do
{
while (PeekMessage(&msg,OpenGLWindow,0,0,PM_NOREMOVE))
{

if(!GetMessage(&msg,OpenGLWindow,0,0))
                                exit(0);
DispatchMessage(&msg);
}
}
while(1);

Lorsque le message WM_QUIT est envoyé au programme, cela signifie qu'il doit fermer et que tout est fini. Si c'est ce message que récupère GetMessage, il renvoie 0 : cela explique le if(!GetMessage(&msg,OpenGLWindow,0,0)) exit(0); C'est la seule condition pour qu'un programme quitte. Lorsque le message est récupéré par GetMessage, il est supprimé de la pile des messages (le 'casier' de tout-à-l'heure), à une exception près : le message WM_PAINT n'est supprimé que lorsqu'il a été traité. Pour l'instant nous n'avons pas besoin de savoir ce que msg contient, puisque le seul message qui nous importe est WM_QUIT, et il est géré par GetMessage.
Peut-être ces histoires de messages vous semblent-elles un peu obscures, mais ne vous inquiétez pas, les rares taches d'ombres vont bientôt s'éclaircir.

Mais finissons d'abord notre programme par les lignes qui suivent :

return 0; //(pour la forme)
}

Et voilà ! Le premier programme d'exemple est terminé et vous pouvez le lancer : vous verrez un zoli bouton avec "Fenêtre OpenGL" marqué dessus, et vous pourrez même cliquer dessus, et alors là : RIEN ! Hé oui ! Il ne se passe rien quand on clique sur le bouton ! Ben oui, c'est là le problème : ca sert à rien d'utiliser la classe prédéfinie, puisqu'on peut même pas lui dire ce qu'on veut qu'il se passe quand on clique sur le bouton.

Il va donc falloir qu'on définisse notre propre classe de fenêtre. C'est d'ailleurs toujours comme ce qu'on fait. Stoppez le programme et recopiez donc le code suivant juste avant l'appel à CreateWindow :

WNDCLASS WindowClass =
{ | 0, | //Style
 | WinProc, | //Procédure pour la gestion des messages
 | 0, | //octets supplémentaires à allouer à la classe
 | 0, | //octets supplémentaires à allouer à la fenêtre
 | HInst, | //Handle du programme
 | 0, | //Icne
 | 0, | //Curseur
 | 0, | //Couleur d'arrière-plan
 | NULL, | //Pointeur sur le menu associé à la classe
 | "La classe!" | //Nom de la classe fenêtre
}; |
if (!RegisterClass(&WindowClass)) exit(1);

Ca y est : nous avons défini notre classe de fenêtre, qui s'appelle "La classe!". On peut donc remplacer "BUTTON" par "La classe!" dans la procédure CreateWindow. Mais si vous essayez de lancer le programme tel quel, le compilateur va vous renvoyer une erreur : en effet, dans la déclaration de la classe, nous lui passons comme fonction de gestion des messages une fonction que nous avons nommé WinProc (nous aurions aussi bien pu l'appeler Salutcavaouimoicavaettoicavaouicavaettoiohmoitusaiscava, mais je trouve que WinProc est plus approprié), mais cette fonction n'existe pas. Il faut donc la créer :

LRESULT CALLBACK WinProc(
HWND hwnd, | //Handle de la fenêtre
UINT uMsg, | //Message
WPARAM wParam, | //Paramètre word
LPARAM lParam ) | //Paramètre long
{
return DefWindowProc(
hwnd,
uMsg,
wParam,
lParam
); 		//Lance la procédure par défaut
}

Voici donc la procédure si mystérieuse : un truc tout con en fait, qui ne fait que passer le relais à une autre procédure déjà définie. Et puis si vous êtes perspicace, vous auriez pu me dire "Mais alors, on aurait pu tout connement mettre DefWindowProc à la place de WinProc !". Hé ben en fait...oui. Mais maintenant, on va personnaliser un peu cette fonction, histoire de s'amuser.

D'abord, vous aurez srement remarqué que le fait d'appuyer sur Alt+F4 ferme la fenêtre, mais pas le programme : il faut l'arrêter manuellement avec le compilateur ou Ctrl+Alt+Del. En effet, le fait d'envoyer le message "Ferme la fenêtre" ne provoque pas chez DefWindowProc l'envoi du message "Quitte le programme" : nous allons donc remplacer le contenu de WinProc par :

if (uMsg==WM_CLOSE) {PostQuitMessage(0);return
                        0;}
else return DefWindowProc(hwnd,uMsg,wParam,lParam);

Ca y est ! nous avons enfin notre propre fonction de gestion de messages à nous, qui sait fermer le programme. Et vous remarquerez que les fameux messages évoqués plus haut (WM_PAINT,WM_QUIT,etc...) sont contenus dans la variable uMsg, transmise à WinProc, et que PostQuitMessage dit au programme de se fermer sans autre forme de procès.

Bon et bien maintenant, si nous intégrions OpenGL à notre fenêtre, pour voir ? C'est vrai, depuis le début du tutorial, on a toujours pas vu de DC ou de RC, juste un HWND, qui ma foi faisait penser à un DC de par sa définition. Je ne vous mentirais pas plus longtemps : le DC est en effet directement tiré du HWND. En fait, il suffit de le récupérer grâce à la fonction GetDC. "Hé bien on n'a qu'à le faire alors !" Ok, ok on va le faire...mais o ? après la création de la fenêtre, dans le WinMain ? Oui, pourquoi pas, mais ca ferait un petit peu bidouille, non ? Alors que lorsque l'on appelle CreateWindow, celle-ci exécute justement WinProc une seule fois, avec comme message WM_CREATE... C'est trop beau : tant qu'à faire, on va en profiter pour récupérer notre DC ! Etoffons donc notre chère WinProc :

switch(uMsg)
{
case WM_CLOSE:

ReleaseDC(hwnd,DC);
PostQuitMessage(0);
break;

//Libère le DC et ferme le programme
case WM_CREATE:

DC=GetDC(hwnd);
break;

//Récupère le DC
default:

return DefWindowProc(
hwnd,
uMsg,
wParam,
lParam);
break;

 //Sinon, fait le truc habituel
}
return 0;

Sans oublier de rajouter

HDC DC;

dans les déclaration de variables globales. Et voilà, nous avons notre DC ! Lancez le programme et vous verrez ce que ca change : ... rien. Car l'initialisation d'OpenGL n'est même pas commencée. Il nous faut encore récupérer le Rendering Context, et le définir comme sortie OpenGL courante, en faisant

RC = wglCreateContext(DC); //RC est une variable globale de type HGLRC
if (!RC) SendMessage(hwnd,WM_CLOSE,0,0);
wglMakeCurrent(DC, RC);

après le GetDC du WM_CREATE, et en rajoutant

wglMakeCurrent(NULL, NULL);
if (RC) wglDeleteContext(RC);

avant le ReleaseDC du WM_CLOSE.
Voilà, normalement ca devrait marcher. Mais ca marche pas. Pourquoi ? Et bien tout simplement parce qu'on a pas paramétré le Device Context. En effet, il faudrait d'abord lui dire en quelle résolution on veut être, et puis si on utilise le double buffering, et puis qu'on veut lui associer un RC. Bref, il faut paramétrer ce qu'on appelle le PixelFormat (cad en gros le format d'affichage). Créons donc une petite fonction qui va nous faire tout ca :

void SetupPixelFormat(HDC hDC)
{
PIXELFORMATDESCRIPTOR pfd =
{
sizeof(PIXELFORMATDESCRIPTOR), | //taille du descripteur de format
1, | //version
PFD_SUPPORT_OPENGL |
PFD_DRAW_TO_WINDOW |
PFD_DOUBLEBUFFER, 		//Propriété
PFD_TYPE_RGBA, | //Mode de couleurs
16, | //Bits de couleur
0, 0, 0, 0, 0, 0, | //Paramètres des couleurs
0,0, | //Paramètres alpha
0,0, 0, 0, 0, | //Paramètres du buffer d'accumulation
32, | //Bits de profondeur
0, | //Bits du buffer stencil
0, | //Nombre de buffers auxiliaires
0, | //ignoré (obsolète)
0, | //réservé/code>
0, | //ignoré (obsolète)
0, | //Couleur de transparence
0 | //Ignoré (obsolète)
};
int pixelFormat;
pixelFormat = ChoosePixelFormat(hDC, &pfd);
if (!pixelFormat)
{

MessageBox
(
WindowFromDC(hDC),
"Mode graphique non supporté",
"Problème",
MB_ICONERROR | MB_OK
);
exit(1);

 /*Vérifie si un PixelFormat du type demandé existe*/
}
if (!SetPixelFormat(hDC, pixelFormat, &pfd))
{

MessageBox
(
WindowFromDC(hDC),
"Mode graphique non supporté",
"Problème",
MB_ICONERROR | MB_OK
);
exit(1);

 /*Applique le PixelFormat. Arrête si erreur*/
}
}

Je me fais chier à décrire cette fonction, mais en fait il n'y a pas à en dire beaucoup plus. Les paramètres sont toujours les mêmes, sauf peut-être le nombre de bits de couleur et de profondeur. A part ca, le reste ne sert pas à grand chose, mais c'est nécessaire. Contentez-vous de recopier la fonction, et interrogez votre compilateur ou moi si quelque chose vous gêne. Mais je vous rassure, vous n'avez pas grand chose à savoir sur ces paramètres.

Tout ce qu'il reste à faire, c'est d'inclure un appel à cette fonction après la récupération du DC et avant celle du RC (sans cela wglCreateContext vous renverra une erreur). Et voilà ! Là c'est réellement fini, et votre fonction WinProc doit ressembler à ceci :

LRESULT CALLBACK WinProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam )
{
switch(uMsg)
{
case WM_CLOSE:

wglMakeCurrent(NULL, NULL);
if (RC) wglDeleteContext(RC);
ReleaseDC(hwnd,DC);
PostQuitMessage(0);
break;

//Libère RC et DC et ferme le programme
case WM_CREATE:

DC=GetDC(hwnd);
SetupPixelFormat(DC);
RC = wglCreateContext(DC);
if (!RC) SendMessage(hwnd,WM_CLOSE,0,0);
wglMakeCurrent(DC, RC);
break;

 //Récupère le DC et le RC
default:

return DefWindowProc(hwnd,
uMsg,
wParam,
lParam);
break;

 //Sinon, fait le truc habituel
}
return 0;
}

Si vous lancez le programme maintenant, vous ne verrez cependant pas de changement par rapport à avant, tout simplement parce que le message WM_PAINT est encore géré par DefWindowProc. Mais sachez qu'OpenGL est bien lié à cette fenêtre, et que le pas entre la fenêtre vide et un rendu OpenGL est très mince. Pour le découvrir, passez à la deuxiême partie de ce tutorial !

2. Attacher OpenGL à cette fenêtre

⇑

Dans la premiè partie, je vous ai appris à créer votre fenêtre par CreateWindow(), à associer la sortie OpenGL à cette fenêtre par GetDC(), SetupPixelFormat(), wglCreateContext() et wglMakeCurrent(), et à gérer les messages envoyés à cette fenêtre par une fonction que nous avons appelé WinProc(). Je vais maintenant finir de vous expliquer comment initialiser OpenGL lui-même, maintenant que la fenêtre est bien créée.
Reprenez donc votre programme de la dernière fois, et modifiez votre fonction WinProc() de façon à ce qu'elle ressemble à ça :

LRESULT CALLBACK WinProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam )
{
switch(uMsg)
{
case WM_CLOSE:

wglMakeCurrent(NULL, NULL);
if (RC) wglDeleteContext(RC);
ReleaseDC(hwnd,DC);
PostQuitMessage(0);
break;

//Libère RC et DC et ferme le programme
case WM_CREATE:

DC=GetDC(hwnd);
SetupPixelFormat(DC);
RC = wglCreateContext(DC);
if (!RC) SendMessage(hwnd,WM_CLOSE,0,0);
wglMakeCurrent(DC, RC);
InitGL();
break;

 //Récupère le DC et le RC
case WM_SIZE:

Reshape(
LOWORD(lParam),
HIWORD(lParam)
);
break;

//Met à jour les paramètres OGL
case WM_PAINT:

Draw();
break;

//Exécute le rendu
default:

return DefWindowProc(
hwnd,
uMsg,
wParam,
lParam
);
break;

 //Sinon, fait le truc habituel
}
return 0;
}

Nous avons rajouté un appel à 3 fonctions que nous allons écrire un peu plus loin : Draw(), Reshape() et InitGL(). Nous reparlerons beaucoup de Draw() et InitGL() dans les prochains tutoriaux. Mais parlons d'abord de Reshape() : voici ce qu'il faut écrire à l'intérieur :

void Reshape(int width, int height)
{ |
 | glViewport(0,0,width,height);
 | glMatrixMode(GL_PROJECTION);
 | glLoadIdentity();
 | gluPerspective(45,float(width)/float(height),0.1,100);
} |
  • glViewport() informe d'abord OpenGL sur la taille de la zone de sortie
  • Ensuite glMatrixMode() charge la matrice de projection. Une matrice ? quésako ? En fait, c'est un tableau sur lequel on peut effectuer des opérations mathématiques spécifiques. La matrice de projection sert à définir comment les coordonnées 3D sont transformées en coordonnées 2D sur l'écran. La matrice modèle et vue (GL_MODELVIEW) définit la position du repère de coordonnées dans le monde. C'est sur cette matrice que l'on effectuent des opérations qui se traduisent par des rotations, des translations ou des homotéties. La matrice texture permet de définir l'aspect des textures plaquées sur les faces. On peut aussi lui faire subir des rotations, etc, qui se répercutent sur l'affichage. Les matrices sont donc des outils très puissant de modélisation.
  • glLoadIdentity() fait un reset de la matrice (il la réinitialise) pour pouvoir effectuer des modifications sur celle-ci
  • Et enfin gluPerspective modifie la matrice courante (c'est-à-dire ici la matrice de projection) pour qu'OpenGL transforme les coordonnées 3D au moment du rendu en coordonnées 2D par rapport à l'écran, de façon à ce qu'on ait 'impression de regarder à travers un objectif de 45 de focale, de largeur width et de hauteur height, et dont le clipping va de 0.1 à 100 (attention la valeur du clipping near - ici 0.1 - doit être strictement supérieure à 0).

Cette fonction Reshape() est appelée à chaque fois que la fenêtre est redimensionnée, et notemment à sa création. Grâce à cette fonction, OpenGL est totalement initialisé et est parfaitement près à dessiner. Mais alors qu'est-ce que vient faire InitGL() ? Et bien en fait on va mettre dans cette fonction toutes les fonctions optionnelles destinées à paramétrer le rendu : activation du test de profondeur, définition des lumières, textures, transparence... Donc pour l'instant, comme on n'a rien à y mettre, on va quand même la créer, mais en la laissant vide :

void InitGL()
{
}

Et maintenant, la fontion Draw() : c'est la fonction qui sera appelée à chaque fois que la fenêtre sera rafraîchie, et donc c'est là-dedans qu'on va placer toute la sauce qui va afficher à l'écran de magnifiques images. La fonction Draw() commencera toujours par glClear() (sauf effet visuel particulier) et finira toujours par SwapBuffers(DC). Mais trêve de bavardages, la voici :

void Draw()
{
} glClear
(
GL_COLOR_BUFFER_BIT |
GL_DEPTH_BUFFER_BIT
); 		//Efface le frame buffer et le Z-buffer
glMatrixMode(GL_MODELVIEW); | //Choisit la matrice MODELVIEW
glLoadIdentity(); | //Réinitialise la matrice
//Placer ici tout le code de transformation et de dessin

SwapBuffers(DC);

 //Echange les 2 frame buffers
  • glClear() efface les buffers passés en paramètre. Ici, nous n'utilisons que le frame buffer (c'est-à-dire le buffer correspondant à l'image affiché à l'écran) et le z-buffer (utilisé pour les tests de profondeur). Pour l'instant, nous n'utiliseront pas le z-buffer, mais comme plus tard on l'utilisera toujours, autant vous montrer tout de suite comment on s'en sert.
  • Ensuite on charge la matrice modèle/vue et on la réinitialise pour pouvoir par la suite effectuer toutes les transformations 3D nécessaires (rotations, translations...).
  • Après vient tout le code de dessin et de transformation, que l'on remplira au fur et à mesure des tutorials
  • Et enfin la fonction SwapBuffers(DC) échange les deux frame buffers : celui sur lequel on travaillait est affiché à l'écran tandis que celui qui était à l'écran est caché et servira pour le prochain rendu.

Vous pouvez dès maintenant essayer ce programme : vous y verrez un magnifique écran noir, car pour l'instant rien n'est dessiné dans notre écran
Mais pour ne pas vous laisser sur votre fin, voici quelques lignes à insérer dans Draw() entre glLoadIdentity() et SwapBuffers() pour vous montrer que ca marche vraiment :

gluLookAt(0,0,-10,0,0,0,0,1,0);
glBegin(GL_TRIANGLES);

glVertex2i(0,1);
glVertex2i(-1,0);
glVertex2i(1,0);
glEnd();

Je vous laisse découvrir ce que ca donne (pour les impatients, allez voir dans le tut glut).

Et voila ! Vous avez réellement dessiné en OpenGL, rien qu'avec un petit tutorial de rien du tout ! C'est-y pas génial ? Avouez que c'est pas compliqué. Tout ce que vous avez besoin de savoir pour programmer en OpenGL sous Windows, c'est créer une fenêtre, savoir récupérer et gérer les messages qui lui sont transmis, et récupérer un Device Context et un Rendering Context pour initialiser le PixelFormat. Et en plus, vous n'avez même pas besoin de connaître par coeur comment le faire ! Et si vous trouvez ca déjà trop dur, alors je ne vous laisse même pas imaginer ce que ce serait si vous vous mettiez à Direct3D.

Antoche

← Introduction ↑ Tutoriaux OpenGL ↑ Initialiser OpenGL avec GLUT →
Last modified on Tuesday October 18th, 2005 E-mail any comment


Initialiser OpenGL avec GLUT

Accueil
Recherche
Publications
Enseignement
ENSIMAG
ESISAR
Tutoriaux OpenGL
FAQ
Introduction
Initialiser OpenGL avec Windows
» Initialiser OpenGL avec GLUT
Les bases
Les couleurs
Les lumières
Les spots
Les textures
L'input avec GLUT
L'input avec Windows
Matrices 3DSMAX
Format BMP
Tutorials DOS
CV
Coin perso
Menu

Initialiser OpenGL avec GLUT
Contenu

GLUT, quoi qu'est-ce ? Et bien c'est une librairie pour OpenGL (GL utility toolkit) destinée à faciliter la vie du programmeur. Non, elle ne vous fait pas la bouffe ni ne vous torche à votre place, mais elle sait quand même créer une fenêtre dans n'importe quel environnement de n'importe quel OS et bien plus, comme vous le verrez bientôt. Elle a été en fait désignée pour permettre une portabilité à 100% du code. Hé oui ! Vous avez bien lu ! 100% ! Ca veut dire quel que soit l'OS, tous vos programmes OpenGL seront parfaitement compilables sans aucune modifications à apporter ! C'est-y pas génial, ca ?
Mais alors pourquoi j'ai pas commencé par faire un tutorial glut ? Ben d'abord par ce que j'ai appris sans, et qu'ensuite je trouve plus pratique de développer sans glut pour ce qui est du débugage et des test, parce qu'on a beaucoup plus d'options et de trucs comme ça. Mine de rien, ca permet quand même d'appréhender les réactions de chaque machine à OpenGL, de voir les différences entres les cartes 3D etc... Mais glut a lui aussi plein d'avantages : d'abord on peut faire de tout petits programmes tous cons qui tiennent en quelques lignes et peuvent faire des effets excellent, et puis glut ne se contente pas de gérer le graphisme : on peut aussi grâce à glut gérer la souris, le clavier... C'est vraiment excellent !

Après cette brève introduction, passons à la partie code. Je vais moins commenter le code que dans le tutorial sur OpenGL pour win, parce que les remarque sont les mêmes. Donc je vous conseille de lire rapidement le tutorial sus-cité (surtout la 2e partie) afin de saisir les 2 ou 3 concepts de base avant d'aborder celui-ci. Et si vous avez déjà réussi à utiliser OpenGL avec windows, alors ce tutorial-ci sera une partie de plaisir. Dans les #include, on uniquement glut.h. Remarquez qu'ici il n'y a pas de windows.h : c'est un code tout à fait portable. Il n'y a pas non plus gl.h ou glu.h mais ceux-ci sont déjà inclus par glut.h normalement. Mais tout dépend des compilateurs et des systèmes : certains permettent d'inclure les 3, d'autres se contentent de glut.h. Mais pas de panique : toutes les fonctions gl et glu sonnt accessibles dès lors que vous incluez glut.h.

#include <gl/glut.h>

La différence entre les différentes plateformes se fera en fait au moment de la compilation : sous windows, vous incluerez glut32.lib, opengl32.lib et glu32.lib dans vos liens, tandis que sous Unix vous ferez "cc prog.c -o Prog -lX11 -lglut -lGL etc..."
Sous Visual C++, vous n'aurez donc plus à créer un Win32 Apllication Project, avec WinMain et tout le toutim, mais juste un Win32Console Application Project, c'est-à-dire un prog 32 bits qui sait utiliser les dll de win.

Et bien passons maintenant au main() (ahhh, enfin un main normal !), qui va d'ailleurs être plutôt dans le genre

int main( int argc, char *argv[ ], char *envp[ ] )

Si vous n'avez jamais vu cette syntaxe, sachez qu'elle est tout à fait conforme au C ANSI, et qu'elle permet juste de récupérer les arguments de ligne de commande et les variables d'environnement.
Commencons par remplir ce main() avec les fonctions d'initialistation de glut :

int WindowName;
int main( int argc, char *argv[ ], char *envp[ ] )
{ |
 | glutInit(&argc, argv);
 | glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
 | glutInitWindowSize(640,480); | //Optionnel
 | WindowName = glutCreateWindow("Ma première fenêtre OpenGL !");
 | glutFullScreen(); | //Optionnel
 | return 0;
} |
  • glutInit() a besoin des arguments de ligne de commande, donc on lui donne.
  • On transmet à glutInitDisplayMode() les options qu'on veut : ici, j'ai choisi un mode RGBA (32 bits), un double-buffering, et un Z-buffer à titre d'exemple.
  • glutInitWindowSize() et glutInitWindowPos() sont des fonctions qui permettent de spécifier la taille et la postition des fenêtres qui vont être créées.
  • Enfin, on crée notre fenêtre, en transmettant juste le titre, en on récupère un nom unique pour cette fenêtre, qui servira à la redimensionner, la fermer... Donc mieux vaut l'enregistrer en tant que variable globale.

Et là, vous pouvez d'ores et déjà lancer votre programme : ça crée une fenêtre OpenGL en plein écran et tout et tout... et ca la ferme aussitôt ! Hé oui, on a pas encore vu comment gérer les évènements comme on le faisait avec WM_PAINT et toutes ces autres conneries. Et ben on va le faire maintenant : vous vous rapellez qu'à réception du message WM_SIZE, on appelait notre fonction Reshape(), qui remettait les paramètres OpenGL aux goûts de la fenêtre. Et ben là, c'est tout pareil : y'a juste à écrire dans le main() après les quelques initialisations

glutReshapeFunc(Reshape);

Avec notre fonction Reshape() définie de la sorte :

void Reshape(int width, int height)
{
glViewport(0,0,width,height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(
45,
float(width)/float(height),
0.1,
100
); 		//Pour les explications, lire le tutorial sur OGL et win
glMatrixMode(GL_MODELVIEW); //Optionnel
}

Et de même, on appelait Draw() à réception du message WM_PAINT. Ici, c'est la même chose que pour Reshape() : on crée notre fonction Draw() qui va contenir toutes les fonctions de dessin, et on initialise glut avec

glutDisplayFunc(Draw);

A titre d'exemple, la fonction Draw() peut être quelque chose dans ce genre (référez-vous au tutorial OGL+Windows 2e partie pour toutes les explications sur InitGL(), Draw() et Reshape() ) :

void Draw()
{
} glClear
(
GL_COLOR_BUFFER_BIT |
GL_DEPTH_BUFFER_BIT
); 		//Efface le frame buffer et le Z-buffer
glMatrixMode(GL_MODELVIEW); | //Choisit la matrice MODELVIEW
glLoadIdentity(); | //Réinitialise la matrice
gluLookAt(0,0,-10,0,0,0,0,1,0);
glBegin(GL_TRIANGLES);

glVertex2i(0,1);
glVertex2i(-1,0);
glVertex2i(1,0);
glEnd(); | //Pour les explications, lire le tutorial sur OGL et win

glutSwapBuffers();

 //Attention : pas SwapBuffers(DC) !

glutPostRedisplay();

 //Demande de recalculer la scène

Remarquez que cette fonction diffère de la version "sans glut" par glutSwapBuffers(). Donc si vous voyez SwapBuffers(DC) dans les prochains tuts, ne vous étonnez pas et remplacez-le par glutSwapBuffers().
Attention également à glutPostRedisplay() : en effet, Draw() n'est appelée que lorsque glut pense que la fenêtre doit être rafraîchie (donc lorqu'on change de fenêtre, lorsqu'elle est réduite...). Pour demander à glut de refaire le rendu, il faut donc appeler glutPostRedisplay(), sinon votre fenêtre ne sera pas rafraîchie régulièrement.

Ensuite, après avoir initialisé glut, vous pouvez initialiser OpenGL lui-même en vous faisant une petite fonction InitGL contenant tous les trucs nécessaires : glEnable(GL_DEPTH_TEST), glEnable(GL_LIGHTING) etc... Cela ne vous dit peut-être rien pour l'instant, mais vous en aurez besoin plus tard.
Enfin, lancez le gestionnaire glut, qui se chargera de lancer les fonctions Draw() et Reshape() aux moments voulus, grâce à glutMainLoop().

Pour résumer, voici la fonction main() complète :

int main( int argc, char *argv[ ], char *envp[ ] )
{ |
 | glutInit(&argc, argv);
 | glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
 | glutInitWindowSize(640,480); | //Optionnel
 | WindowName = glutCreateWindow("Ma première fenêtre OpenGL !");
 | glutFullScreen(); | //Optionnel
 | glutReshapeFunc(Reshape);
 | glutDisplayFunc(Draw);
 | InitGL();
 | glutMainLoop();
 | return 0;
} |

Ici notre fonction InitGL() ne contient absolument rien, mais nous nous en servirons plus tard. Notez que glutMainLoop() ne s'arrête jamais : on est obligé de sortir du programme par un exit(), ou de fermer la fenêtre manuellement.

Le programme présenté ici donnera quelque chose de ce genre (là c'est sans fullscreen) :

Et voilà, c'est fini ! Vous pouvez maintenant garder cet exemple comme un canevas sur lequel vous n'aurez plus qu'à tisser votre code (c'est beau, non ?). Dans les prochains tutorials, je ferais toujours référence à Draw(), InitGL() et Reshape(), donc vous devrez être censés savoir ce que c'est. En plus, ces fonctions se retrouvent également dans la partie "sans glut" : comme ça je n'aurais pas à expliquer les choses de 2 façons différentes.

Antoche

← Initialiser OpenGL avec Windows ↑ Tutoriaux OpenGL ↑ Les bases →
Last modified on Monday December 26th, 2005 E-mail any comment


OpenGL : les bases

Accueil
Recherche
Publications
Enseignement
ENSIMAG
ESISAR
Tutoriaux OpenGL
FAQ
Introduction
Initialiser OpenGL avec Windows
Initialiser OpenGL avec GLUT
» Les bases
Les couleurs
Les lumières
Les spots
Les textures
L'input avec GLUT
L'input avec Windows
Matrices 3DSMAX
Format BMP
Tutorials DOS
CV
Coin perso
Menu

OpenGL : les bases
Contenu

Ce troisième volet s'inscrit directement dans la continuité du second, et il sera le même que vous utilisiez glut ou non. Nous allons étudier les primitives et les matrices en nous appuyant sur le tutorial précédent. Reprenez donc le projet que nous avions commencé, et qui affichait un magnifique triangle blanc.

Bon, on va commencer par le dessin en OpenGL. Pour dessiner, c'est hyper simple, et on peut faire n'importe quoi. Comme vous le savez sûrement si vous avez légèrement touché à la 3D, TOUS les objet sont en fait une association d'élements de base : ce sont des faces orgranisées dans un certain ordre dans l'espace, qui représentent l'objet. Chaque face peut être déconposée en une série de facettes triangulaires.

Ainsi, un rectangle est en fait constitué de deux triangles consécutifs :

Ces triangles sont eux-même composés de 3 cotés, ou 3 lignes, et de 3 points (ou vertex) qui définissent les extrémités de ces segments. Ces éléments (points, lignes, triangles) sont aussi appelés primitives. OpenGL est basé sur le principe des primitives, c'est-à-dire que pour dessiner un objet, il faut dessiner toutes ses primitives (ca parait logique). Et pour cela, on utilise des fonctions très simples d'utilistation et permettant des dessins très poussés, puisque grâce à ces fonctions, on peut dessiner quasiment tout.
Passons maintenant à la pratique : comment dessiner un élément primitif ? Hé bien vous l'aurez sûrement deviné en lisant le code d'exemple du triangle : on appelle glBegin() avec comme paramètre l'élément, on lui envoie les coordonnées des vecteurs-clés via glVertex(), et on tremine avec glEnd(). Ultra simple, non ?
Notez que vous ne verrez rien si vous n'applez pas SwapBuffers(DC). Pourquoi ? Parce qu'on utilise une technique appelée double-buffering, qui consiste à afficher une image pendant que l'on calcule l'autre, et lorsque celle-ci est terminée, on échange les deux. Donc si on enlève SwapBuffers(DC), on verra toujours un écran vide, tandis qu'on dessinera dans un écran non visible. Si vous travaillez en mode simple-buffering (changez l'option dans SetupPixelFormat() pour les windows-lover, et dans glutInit() pour les glut-lover), il faut remplacer SwapBuffers() par glFlush(). Mais c'est stupide de n'utiliser qu'un seul buffer, car la construction de l'image est alors visible, ce qui provoque un effet de scintillement.
Voyons maintenant ce que l'on peut dessiner à part des triangles :

Paramètre transmis à glBegin | Description | Exemple
GL_POINTS | Dessine un point pour chaque vertex transmis. |
GL_LINES | Dessine des lignes. Le point n défini le début d'une ligne et le point n+1 la fin de la ligne. |
GL_LINE_STRIP | Dessine un groupe de lignes connectées, formant une chaine partant du premier vertex et s'arrêtant au deriner. |
GL_LINE_LOOP | Même chose que GL_LINE_STRIP, mais en revenant au premier vertex à la fin. |
GL_TRIANGLES | Chaque triplet de vertex constitue un triangle |
GL_TRIANGLE_STRIP | Un triangle est défini pour chaque vertex présenté après les deux premiers |
GL_TRIANGLE_FAN | Même chose que GL_TRIANGLE_STRIP, sauf que le premier vertex de chaque triangle est toujours le n°1 |
GL_QUADS | Chaque quadruplet de vertex définit un quadrilatère (composé de 2 triangles) |
GL_QUAD_STRIP | Chaque couple de vertex présenté après la première paire définit un quadrilatère (attention à l'ordre des points) |
GL_POLYGON | Dessine un polygone convexe |

Remarque : les lignes rouges et les numéros sur les exemples servent seulement à montrer comment sont dessinés les primitives, mais ne sont pas visibles en réalité.

Tout-à-l'heure, j'ai parlé de glVertex(), et pourtant dans mon exemple il y a écrit glVertex2d(). Pourquoi ? Hé bien parce qu'en fait il y a une multitude de fonctions pour dessiner des sommets. En fait, on appelle toujours la fonction glVertexxy[v](), où :

  • x définit le nombre de dimension, de 2 à 4. Lorsqu'il y a 2 dimensions, on définit x et y, et z est par défaut à 0. Pourquoi aller jusqu'à 4 dimensions alors qu'on fait de la 3D ? C'est à cause des coordonnées homogènes. C'est un concept mathématique qui permet d'effectuer toutes les transformations 3D nécessaires simplement avec des matrices 4x4 et des vecteurs 4D. En pratique, vous n'avez pas à toucher à cette 4e dimension, elle prend normalement toujours la valeur de 1. Si vous voulez en savoir plus, allez faire un tour dans le redbook ou sur des sites de maths.
  • y définit le type des paramètres : 'i' pour int, 's' pour short, 'f' pour float, et 'd' pour double.
  • Lorsque l'on rajoute 'v', on passe comme paramètre un pointeur sur les coordonnées plutôt que les coordonnées elles-même

Vous vous servirez en fait principalement de GL_TRIANGLES et GL_QUADS. Mais là nous allons nous servir de GL_LINES. En effet, maintenant que vous savez dessiner les primitives (donc que vous savez tout dessiner), nous allons étudier les transformations de base en OpenGL. Et pour cela, le plus simple est de dessiner un repère tridimensionnel. Votre fonction Draw() doit donc être :

void Draw()
{ |
 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 | glMatrixMode(GL_MODELVIEW);
 | glBegin(GL_LINES);
 | glVertex2i(0,0);glVertex2i(0,1);
 | glVertex2i(0,0);glVertex2i(1,0);
 | glVertex2i(0,0);glVertex3i(0,0,1);
 | glEnd();
 | SwapBuffers(DC); | // glutSwapBuffers(); pour glut
 | glutPostRedisplay(); | // Uniquement pour GLUT
} |

Mais si vous lancez le programme tel quel, vous ne verrez rien. Pourquoi ? Parce qu'au départ, nous sommes placé en (0,0,0) et nous regardons vers le point (0,0,-1). Or notre objet est situé en (0,0,0), donc nous ne verrons pas grand chose. Pour changer cela, il suffit d'insérer avant glBegin() :

gluLookAt(3,2,3,0,0,0,0,1,0);

gluLookAt() est une fonction très simple et puissante permettant de définir un point de vue. Les 3 premiers paramètres définissent les coordonnées du point de vue, les 3 suivant l'endroit où il regarde, et les 3 derniers un vecteur qui dit où est le haut de la caméra. gluLookAt() effectue les opérations nécessaires sur la matrice active afin que le repère soit bien orienté. Nous devons donc activer nous-même la matrice ModelView grâce à glMatrixMode().

Vous pouvez maintenant lancer votre programme, et vous verrez une fenêtre de ce genre (sans les textes, bien sûr) :

Nous allons maintenant utiliser ce repère pour étudier les transformations, au nombre de 3. Déclarez d'abord une variable globale double a=0; et commencons.

Les prinicpes fondamentaux :

  • Lorsque vous avez effectué une transformation, tous les objets dessinés après cette transformation sont affectés, tant que glLoadIdentity() n'est pas appelé (auquel cas les objets ne seront plus transformés) ;
  • Les modifications affectent directement le repère de coordonnées et se basent sur le repère de coordonnées. Le axes utilisés sont donc toujours les axes locaux. C'est-à-dire que si vous avez fait une rotation autour de l'axe X, et que vous voulez faire une rotation autour de l'axe Y, cette rotation s'effectuera autour de l'axe modifié par la première rotation (vous verrez un exemple concret tout-à-l'heure) ;
  • Un objet dessiné ne peut plus être modifié.

L'homotétie ( glScalef / glScaled ) :
Il s'agit d'un agrandissement ou une réduction par rapport au centre du repère de coordonnées. Si vous êtes un habitué de 3DS, cette fonction revient à faire un Non-Uniform Scaling sur tous les objets dessinés après. Exemple : insérez avant le glBegin() de Draw() les lignes suivantes (sans oublier d'inclure math.h pour les fonctions trigo) :

glScaled(1.-cos(a)/3.,1.+cos(a)/3.,1.+cos(a)/3.);
a+=.1;

Et vous verrez une belle animation dans ce genre :

Le repère est agrandi ou réduit selon certains axes. Comme je l'ai dit précédemment, toutes les transformations qui suivent seront basées sur le nouveau repère, tant que celui-ci n'aura pas été réinitialisé par glLoadIdentity(). Donc vous translatez un objet après avoir effectué une réduction par glScale(), le déplacement sera plus court, puisque le repère aura diminué.

la translation ( glTranslated / glTranslatef ) :
C'est en fait un déplacement selon le vecteur passé en paramètre. Le meilleur moyen de l'expliquer est de fournir un exemple. à la place du glScaled, écrivez :

glTranslated(cos(a),0,sin(a));

Et ca donne :

Je ne le répèterais jamais assez : les transformations affectent aussi le système de coordonnées. Donc si vous avez effectué une translation, et qu'ensuite vous vouliez faire une homotétie, le centre de cette homotétie aura lui aussi été déplacé.

la rotation ( glRotated / glRotatef ) :
Elle permet d'effectuer une rotation autour de n'importe quel axe. On lui passe comme paramètre l'angle en degré et les coordonnées x,y et z du vecteur de rotation. La rotation fait tout tourner, y compri le repère lui-même (comme les autres transformations d'ailleurs). Donc si vous faites une première rotation selon un vecteur v1, et que vous voulez faire une deuxième rotation selon un autre vecteur v2, n'oubliez pas que v2 a lui aussi subi la première rotation ! Un petit exemple ?

glRotated(a*180/3.14,1,1,1);

donne :

Vous pouvez bien sûr combiner les transformations. Essayez par exemple les 3 en même temps, c'est assez sympa.

Et voilà ! Vous savez tout sur le dessin et les transformation et OpenGL. Vous pouvez donc maintenant tout dessiner ! Bon, OK, c'est lourd de dessiner une mobylette à la mano, mais en fait glu vous permet d'y arriver plus facilement : elle fournit des primitives moins primitives, avec gluSphere, gluDisk, gluCylinder, gluPartialDisk... Etudiez ces fonctions avec F1, et amusez-vous à dessiner de petits objets et à tourner autour. Je vous rapelle que le meilleur moyen d'apprendre est encore d'apprendre par soi-même. Donc entraînez-vous à manier les transformations et les primitives jusqu'à ce que vous vous soyez bien familiarisés avec.

La prochaine fois, nous verrons le coloriage, alors préparez vos crayons de couleurs ;-)

Antoche

← Initialiser OpenGL avec GLUT ↑ Tutoriaux OpenGL ↑ Les couleurs →
Last modified on Friday May 12th, 2006 E-mail any comment


OpenGL : les couleurs

Accueil
Recherche
Publications
Enseignement
ENSIMAG
ESISAR
Tutoriaux OpenGL
FAQ
Introduction
Initialiser OpenGL avec Windows
Initialiser OpenGL avec GLUT
Les bases
» Les couleurs
Les lumières
Les spots
Les textures
L'input avec GLUT
L'input avec Windows
Matrices 3DSMAX
Format BMP
Tutorials DOS
CV
Coin perso
Menu

OpenGL : les couleurs
Contenu

Au cas où vous ne l'aviez pas encore remarqué, tout ce qu'on a fait jusqu'à présent, c'était tout blanc. Mais tout blanc alors. Tellement qu'en fait on ne pouvait pas distinguer 2 faces entre elles, ce qui m'a poussé jusqu'ici à dessiner des lignes plutôt que des faces, et ca faisait un petit peu limité pour ce que peu faire réellement la bête. Donc maintenant on va pouvoir grâce à ce tutorial dessiner de beaux objets "solides" et bien visibles, et pas des petits cubes à la con en mode filaire.

Reperenez donc (comme par hasard) le pogramme du tutorial précédent, et modifiez Draw() de façon à n'avoir que le strict minimum :

void Draw()
{ |
 | glClear(GL_COLOR_BUFFER_BIT);
 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 | glMatrixMode(GL_MODELVIEW);
 | SwapBuffers(DC) | // glutSwapBuffers(); pour glut
 | glutPostRedisplay(); | //Uniquement pour GLUT
} |

Bon là on n'a pas grand chose, mais on va dessiner un cube. Qui dit cube dit 6 faces rectangulaires. Donc hop c'est parti :

void Draw()
{

glClear(GL_COLOR_BUFFER_BIT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
gluLookAt(5,5,5,0,0,0,0,1,0);
glBegin(GL_QUADS);

glVertex3i(1,1,1);
glVertex3i(1,-1,1);
glVertex3i(-1,-1,1);
glVertex3i(-1,1,1);

 //1 face

glVertex3i(1,1,-1);
glVertex3i(1,-1,-1);
glVertex3i(-1,-1,-1);
glVertex3i(-1,1,-1);

 //2 faces

glVertex3i(1,1,1);
glVertex3i(1,-1,1);
glVertex3i(1,-1,-1);
glVertex3i(1,1,-1);

 //3 faces

glVertex3i(-1,1,1);
glVertex3i(-1,-1,1);
glVertex3i(-1,-1,-1);
glVertex3i(-1,1,-1);

 //4 faces

glVertex3i(-1,1,-1);
glVertex3i(-1,1,1);
glVertex3i(1,1,1);
glVertex3i(1,1,-1);

 //5 faces

glVertex3i(-1,-1,-1);
glVertex3i(-1,-1,1);
glVertex3i(1,-1,1);
glVertex3i(1,-1,-1);

 //6 faces
glEnd();

SwapBuffers(DC)

 //glutSwapBuffers(); pour glut
glutPostRedisplay(); //Uniquement pour GLUT
}

Bon ok c'est lourd mais rien ne vous interdit de tout recopier à la main, copier/coller ca existe. Et puis de toute façon ça fait un bon entrainement.
Sinon pour l'instant rien de nouveau, on utilise ce qu'on a vu dans les tuts précédents. Et qu'est-ce que ça donne, ça ? Et ben on obtient un truc dans ce genre :

C'est vrai qu'on a un peu de mal à faire la distinction entre l'une ou l'autre face. C'est pour ça que j'ai choisi de mettre des couleurs aux faces. Nous en venons donc à : "comment qu'on va peinturlurer ce truc sans s'en foutre plein partout ?" Hé bien grâce à cette merveilleuse fonction glColor() !

Comme pour glVertex(), glColor() va de glColor3b() (R,G,B sur un octet signé) à glColor4usv() (R,G,B,Alpha sur un pointeur sur short non signé) en passant par glColor3d() (R,G,B sur un double, de 0.0 à 1.0). C'est justement cette dernière fonction que nous allons utiliser car pouvoir sélectionner l'intensité de la couleur entre 0 et 1 est bien pratique. Pour les codages sur des valeurs entières, tout l'intervalle disponible est utilisé (par exemple en ub - unsigned byte -, les couleurs iront de 0 à 255).
Bien. Comment se sert-on de glColor() maintenant ? En fait, quand on appelle cette fonction, elle va changer la couleur des vertices (sommets) que l'on dessinera après l'appel de la fonction, selon le principe de la machine d'état. Donc puisqu'on veut une couleur unie par face, on va appeler glColor() 6 fois, une fois avant chaque face :

void Draw()
{

glClear(GL_COLOR_BUFFER_BIT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
gluLookAt(5,5,5,0,0,0,0,1,0);
glBegin(GL_QUADS);

glColor3d(1,0,0);
glVertex3i(1,1,1);
glVertex3i(1,-1,1);
glVertex3i(-1,-1,1);
glVertex3i(-1,1,1);

 //1 face

glColor3d(0,1,0);
glVertex3i(1,1,-1);
glVertex3i(1,-1,-1);
glVertex3i(-1,-1,-1);
glVertex3i(-1,1,-1);

 //2 faces

glColor3d(0,0,1);
glVertex3i(1,1,1);
glVertex3i(1,-1,1);
glVertex3i(1,-1,-1);
glVertex3i(1,1,-1);

 //3 faces

glColor3d(0,1,1);
glVertex3i(-1,1,1);
glVertex3i(-1,-1,1);
glVertex3i(-1,-1,-1);
glVertex3i(-1,1,-1);

 //4 faces

glColor3d(1,1,0);
glVertex3i(-1,1,-1);
glVertex3i(-1,1,1);
glVertex3i(1,1,1);
glVertex3i(1,1,-1);

 //5 faces

glColor3d(1,0,1);
glVertex3i(-1,-1,-1);
glVertex3i(-1,-1,1);
glVertex3i(1,-1,1);
glVertex3i(1,-1,-1);

 //6 faces
glEnd();

SwapBuffers(DC)

 //glutSwapBuffers(); pour glut
glutPostRedisplay(); //Uniquement pour GLUT
}

Et hop c'est fini et voilà le résultat :
Avouez que c'est tout de même plus joli. Bon ok mes couleurs sont à chier, mais là au moins on voit la différence entre les faces; et puis de toute façon si vous êtes pas content vous n'avez qu'à changer les couleurs, c'est vous qui choisissez après tout.
Remarquez que l'on a appelé glColor() entre glBegin() et glEnd(), et cela n'a posé aucun problème. Certaines fonctions le peuvent et d'autres non, selon leur action.

Ah non ! J'oubliais ! Si vous lancez comme ça, ça marchera, mais vous verrez vraiment un cube bizarre. En effet, nous n'avons pas dit à OpenGL de ne pas afficher les faces cachées. Il va donc tout dessiner dans l'ordre où on lui a donné les faces, sans se préoccuper s'il y avait quelque chose devant ou pas. Il faut donc activer le test de profondeur. Pour cela, tapez
glEnable(GL_DEPTH_TEST);
dans InitGL(). Et rajoutez aussi
glEnable(GL_COLOR_MATERIAL);
tant que vous y êtes, pour être bien sûr que le coloriage est activé.

Mais au fait, quand on regarde un cube tout blanc (ou un lapin ou un kangourou si vous préférez) dans la réalité, on voit quand même bien les arêtes, même si les faces sont immaculées. Ben oui, parce qu'en général, les faces ne sont pas éclairées de la même façon. Or ici on utilise l'éclairage par défaut tout à fait pourri je le conçoit, mais qui au moins nous permet d'y voir quelque chose. C'est un éclairage dit "ambient", c'est-à-dire que la lumière est exactement la même en tout point (un peu genre lumière "ambiente" dans 3DS). C'est pour ça que toutes les faces ont exactement la même couleur.
Mais on ne va pas aborder les lumières maintenant, plutôt dans le prochain tutorial. Pourquoi je vous parle de ça alors ? D'abord pour que vous compreniez mieux (si qq chose vous tracasse encore, n'hésitez pas à me le dire), et ensuite parce que glColor() n'est pas sans liens avec l'éclairage. En effet, comme dans 3DS, chaque objet a plusieurs couleurs : couleur ambiente (celle qu'a l'objet lorsqu'aucune lumière ne le frappe), couleur diffuse (couleur que renvoie l'objet lorsqu'il est éclairé), couleur spéculaire (couleur que renvoie l'objet lorsqu'il est frappé directement par la lumière) et couleur d'émission (couleur qu'émet l'objet, comme un trux fluorescent par exemple). Il faut bien faire la différence entre diffuse et spéculaire : une sphère métallique aura par exemple une couleur diffuse grise, et une couleur spéculaire très blanche, qui correspondra au point très lumineux à l'endroit où la lumière vient se réfléchir. Une image illustre mieux qu'un long discours :

Ici la couleur ambiente est bleutée. Elle ne se voit pas beaucoup, mais elle est tout de même présente. La couleur diffuse est grise, mais on voit que l'ambiente influe aussi dessus, ce qui la rend gris-bleu. Quand à la spéculaire, elle est carrément blanche et l'ambiente n'a quasiment aucune influence dessus. Cette image a été réalisée avec 3DS, mais vous verrez qu'on peut en faire d'aussi belles avec OpenGL dans le prochain tutorial.

Revenons à nos moutons. Ici nous modifions la couleur par défaut, qui est GL_AMBIENT_AND_DIFFUSE. Nous ne changeons donc pas la couleur spéculaire. Pour choisir la couleur modifiée par glColor(), il faut appeler d'abord glColorMaterial(face,mode). face désigne les faces qui seront affectées : GL_FRONT pour les face avant, GL_BACK pour les faces arrières et GL_FRONT_AND_BACK pour les 2. Comme nous n'avons pas encore vu l'orientation des faces, choisissez GL_FRONT_AND_BACK (défaut). mode définit lui la couleur à modifier : GL_EMISSION, GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, et GL_AMBIENT_AND_DIFFUSE (défaut). Notez que cette fonction ne peut pas être appelée entre glBegin() et glEnd() (c'est une source fréquente d'erreurs).
Essayez avant le glBegin() de sélectionner une couleur différente et vous verrez qu'en fait le résultat est le même quel que soit la couleur choisie. C'est parce que nous sommes toujours avec l'éclairage par défaut, qui permet surtout de découvrir et d'apprendre les fonctions basiques de dessin en étant sûr de voir qq chose. Nous devrons donc étudier en détail l'éclairage dans un prochain tutorial.

Mais il a autre chose que je n'ai pas complètement éclairci. En effet, si vous êtes attentif, vous avez peut-être remarqué que j'ai dit plus haut que glColor() définissait la couleur des sommets qui allaient être dessinés après. Donc a priori, on peut essayer de définir des couleurs différentes pour chaque sommet d'une face. Qu'est-ce donc que ça va donner, ça ? Eh ben le meilleur moyen de le savoir, c'est tout simplement d'essayer...

void Draw()
{

glClear(GL_COLOR_BUFFER_BIT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
gluLookAt(5,5,5,0,0,0,0,1,0);
glBegin(GL_QUADS);

glColor3d(1,0,0);glVertex3i(1,1,1);
glColor3d(0,1,0);glVertex3i(1,-1,1);
glColor3d(0,0,1);glVertex3i(-1,-1,1);
glColor3d(1,0,1);glVertex3i(-1,1,1);

//1 face

glColor3d(1,1,0);glVertex3i(1,1,-1);
glColor3d(0,1,1);glVertex3i(1,-1,-1);
glColor3d(1,0,0);glVertex3i(-1,-1,-1);
glColor3d(1,1,1);glVertex3i(-1,1,-1);

//2 faces

glColor3d(1,0,0);glVertex3i(1,1,1);
glColor3d(0,1,0);glVertex3i(1,-1,1);
glColor3d(0,1,1);glVertex3i(1,-1,-1);
glColor3d(1,1,0);glVertex3i(1,1,-1);

//3 faces

glColor3d(1,0,1);glVertex3i(-1,1,1);
glColor3d(0,0,1);glVertex3i(-1,-1,1);
glColor3d(1,0,0);glVertex3i(-1,-1,-1);
glColor3d(1,1,1);glVertex3i(-1,1,-1);

//4 faces

glColor3d(1,1,1);glVertex3i(-1,1,-1);
glColor3d(1,0,1);glVertex3i(-1,1,1);
glColor3d(1,0,0);glVertex3i(1,1,1);
glColor3d(1,1,0);glVertex3i(1,1,-1);

//5 faces

glColor3d(1,0,0);glVertex3i(-1,-1,-1);
glColor3d(0,0,1);glVertex3i(-1,-1,1);
glColor3d(0,1,0);glVertex3i(1,-1,1);
glColor3d(0,1,1);glVertex3i(1,-1,-1);

//6 faces
glEnd();

SwapBuffers(DC)

 //glutSwapBuffers(); pour glut
glutPostRedisplay(); //Uniquement pour GLUT
}

Ok c'est peut-être un peu lourd, mais au moins c'est clair : chaque sommet a une couleur différente (j'ai mis les sommets (1,1,1) et (-1,-1,-1) tous les deux en rouge pour ne pas utiliser la couleur noire). Et voilà ce que ça donne. Comme vous pouvez le voir, la couleur d'un point sur la face est interpolée entre chaque sommet, et ça donne des résultats plutôt sympas. On voit un peu mieux les arêtes, on a même l'impression qu'elles sont éclairées, mais en fait ce n'est qu'une illusion.

Grâce à ce tutorial en tout cas nous avons découvert que :

  • On peut modifier la couleur active avec glColor();
  • On peut choisir la couleur active avec glColorMaterial();
  • On peut activer certaines options (test de profondeur, coloriage...) grâce à glEnable() - utilisez l'aide de votre compilateur pour découvrir tout ce que cette fonction recèle ;
  • Un objet est composé de différentes couleurs : émission, ambiente, diffuse et spéculaire.
  • Une face peut avoir des sommets de couleurs différentes

Un "truc" de graphiste (je suis pas graphiste mais bon c'est toujours un conseil) : évitez le plus possible de modifier la couleur ambiente d'un objet car elle affecte aussi les autres couleurs, et donc demande des réglages très subtils, voire impossibles. Laissez-la donc noire et utilisez plutôt les couleurs diffuse et spéculaire qui sont plus pratiques et rapides à régler.

Antoche

← Les bases ↑ Tutoriaux OpenGL ↑ Les lumières →
Last modified on Friday December 23rd, 2005 E-mail any comment


OpenGL : les lumières

Accueil
Recherche
Publications
Enseignement
ENSIMAG
ESISAR
Tutoriaux OpenGL
FAQ
Introduction
Initialiser OpenGL avec Windows
Initialiser OpenGL avec GLUT
Les bases
Les couleurs
» Les lumières
Les spots
Les textures
L'input avec GLUT
L'input avec Windows
Matrices 3DSMAX
Format BMP
Tutorials DOS
CV
Coin perso
1. On démarre
2. Facteur de brillance et couleur spéculaire
3. Lumière directionnelle et lumière ponctuelle
4. Position de la source
4.1 Premier exemple : lumière fixe
4.2 Deuxième exemple : lumière mobile
4.3 Troisième exemple : lumière relative au point de vue
5. Atténuation
6. Couleur
Conclusion
Menu

OpenGL : les lumières
Contenu

1. On démarre

⇑

"Que la lumière soit."
Moi ;-)

Nous allons maintenant discuter des sources de lumière. C'est un sujet assez étendu et un peu plus compliqué que ce qu'on a vu précédement. Et comme il y a beaucoup à dire, nous n'allons voir dans une première partie que les généralités et les sources omnidirectionnelles (les 'omni ' pour les fans 3DS). Nous verrons les spots dans le deuxième chapitre. Je vous préviens tout de suite : il y a beaucoup plus de bla-bla que dans les autres, parce qu'il y a beaucoup à expliquer, même si certains connaissent déjà ces notions.

Comme d'habitude, reprenons notre programme de base avec la fonction Draw() réduite au minimum :

void Draw()
{ |
 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 | SwapBuffers(DC) | // glutSwapBuffers(); pour glut
 | glutPostRedisplay(); | // Uniquement pour GLUT
} |

Le meilleur objet pour illustrer l'exemple des lumières est la sphère. Nous aurions pu dessiner une sphère face par face comme pour le cube du tutorial sur les couleurs, mais bon on n'est pas maso non plus. Alors on va utiliser une procédure bien pratique de glut.h : glutSolidSphere(). Elle prend en arguments le rayon de la sphère, le nombre de parallèles, et le nombre de méridiens. Nous allons illuminer cette sphère par une source de lumière ponctuelle située au même endroit que nous. Mais pour que la sphère soit éclairée, il faut d'abord faire quelques initialisations :

void InitGL()
{ |
 | glEnable(GL_DEPTH_TEST); | // Active le test de profondeur
 | glEnable(GL_LIGHTING); | // Active l'éclairage
 | glEnable(GL_LIGHT0); | // Allume la lumière n°1
} |

Le code parle de lui-même : glEnable(GL_DEPTH_TEST) active le test de profondeur, c'est-à-dire que si un point d'une face se situe derrière une autre face, il n'est pas dessiné. glEnable(GL_LIGHTING)active la gestion de lumières, et glEnable(GL_LIGHT0)allume la lumière n°1. Vous aurez bien sûr compris qu'il suffit de faire glEnable(GL_LIGHT1)pour allumer la lumière n°2 etc. Vous disposez d'au moins 8 sources de lumières, le nombre maximum (définit par la constant GL_MAX_LIGHT) étant limité par votre système. Maintenant que nous avons allumé la lumière, nous pouvons donc dessiner notre sphère :

void Draw()
{ |
 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 | glMatrixMode(GL_MODELVIEW);
 | glTranslated(0,0,-5); |
 | glutSolidSphere(1,30,30); |
 | SwapBuffers(DC) | // glutSwapBuffers(); pour glut
 | glutPostRedisplay(); | // Uniquement pour GLUT
} |

2. Facteur de brillance et couleur spéculaire

⇑
Et voilà ce que ça donne. Bon OK en 256 couleurs c'est pas super mais on voit quand même bien que c'est une sphère, alors que sans lumière ça aurait fait juste un disque tout blanc et tout pas beau. Maintenant plongons-nous encore plus dans les lumières : nous avons vu dans le tutorial précédent que chaque objet était constitué d'un matériau, avec des propriétés comme la couleurs ambiente, diffuse etc... Ces paramètres définissent comment est réfléchie la lumière sur les sommets pour chaque composante (0 : composante totalement absorbée par l'objet ; 1 : composante totalement réfléchie par l'objet). Mais un matériau dispose aussi d'une autre propriété : la brillance. Ce paramètre définit la taille de la tache spéculaire. Une valeur de 0 définit une tache spéculaire la plus grande possible, et une valeur de 128 définit une tache toute petite. La valeur par défaut est de 0, donc normalement la partie illuminée devrait être de la couleur spéculaire, sans dégradé. Or ici c'est bien la couleurs diffuse que l'on voit, et non la spéculaire. Pourquoi ? Et bien tout simplement parce que la couleur spéculaire par défaut du matériau est nulle. Nous allons donc la changer. Pour cela, ce n'est pas dur, nous l'avons vu dans le tutorial précédent, il suffit d'appeler glColor() en ayant préalablement choisi GL_SPECULAR avec glColorMaterial(). Mais il y a un autre moyen de la modifier : c'est glMaterial(). En fait, glColor() est une alternative à glMaterial() pour modifier les couleurs d'un matériau. Si GL_COLOR_MATERIAL est désactivé (par défaut), on utilise glMaterial() pour changer les couleurs. S'il est activé, alors on utilisera glColor(), et glMaterial() n'aura plus d'effet sur les couleurs (par contre il en aura sur les autres paramètres des matériaux). glMaterial() a l'avantage de pouvoir être appelé entre glBegin() et glEnd() (glColor() aussi, mais pas glColorMaterial() ), mais il est quand même plus lent que glColor(), c'est pourquoi il est conseillé d'utiliser glColor() plutôt que glMaterial() pour modifier les couleurs d'un matériau.
Bon, bref, si on met la couleur spéculaire de notre sphère à [image] (1,1,1) comme elle le devrait, on obtiendrait l'image que vous voyez à droite. On a plutôt l'impression d'être revenu au point de départ : l'objet est tout blanc, on ne distingue rien etc etc... Mais je vous l'ai dit : c'est parce que l'indice de brillance (shininess) est à 0, ce qui fait que la tache spéculaire est tellement grande qu'elle a bouffé la partie diffuse. Il suffit donc de modifier ce paramètre grâce à glMaterial() pour tout de suite avoir un aspect beaucoup plus réaliste. Si on règle ce paramètre à 100 (sa valeur peut être comprise entre 0 et 128), on obtient quelque chose dans le genre de ce que vous pouvez voir à votre gauche. C'est quand même autrement plus joli. Mais pour ma part, je ne suis pas entièrement satisfait. En effet, la source de lumière est au même endroit que la caméra, ce qui fait que toutes les partie visibles de l'objet sont éclairées. Ceci serait plus beau si la source de lumière provenait d'un point dans l'espace. Et c'est là que nous allons parler des lumières directionnelles (directionnal lights) et ponctuelles (positionnal lights).

3. Lumière directionnelle et lumière ponctuelle

⇑
Ici encore les adeptes de 3DS ou autres POV n'auront aucun mal à saisir ces notions, puisque ce sont les bases de tout modeleur 3D qui se respecte. Allons-y : une source de lumière ponctuelle est une source dont les rayons partent tous du centre de la source. Une source de lumière directionnelle est une source dont les rayons ont la même direction en tout point de l'espace, comme une source ponctuelle qui serait située tellement loin de la scène que les rayons pourraient être considérés comme parallèles. L'exemple parfait de lumière directionnelle dans notre monde, qui est d'ailleurs cité dans n'importe quel bouquin expliquant ce genre de trucs, est bien sûr le soleil : tous ses rayons vont dans la même direction, que l'on soit à Paris où à Québec, même si des milliers de km séparent ces 2 villes. Voilà un petit shéma histoire de vous éclaircir les idées, plutôt que de vous embrouiller avec mes explications douteuses :

4. Position de la source

⇑

Voilà, là je pense que vous comprenez ce que je veux dire. Nous allons donc modifier notre source de lumière pour qu'elle rayonne de biais par rapport à la sphère. Mais au fait, quels sont les paramètres par défaut de GL_LIGHT0 ? Et bien elle est directionnelle et rayonne le long de l'axe z, dans le sens des valeur négatives. Donc si on se place par exemple en (5,5,5) en regardant le point (0,0,0) avec gluLookAt(), on devrait voir une sphère éclairée de biais. Or, si vous faites le test, vous verrez exatcement la même image que lorsque vous étiez en (0,0,-5). Pourquoi donc ? Et bien en fait la position des lumières est déterminée de la même façon que pour les primitives : avec la matrice modelview. Lorsque vous définissez la postition de la lumière, elle est multipliée par la matrice modelview courante. Or la position par défaut de la lumière est définie à l'initialisation et plus du tout après. Donc s'il y a une modification de matrice après la définition de la position de la lumière, cette dernière n'est pas affectée, tout comme une primitive.
Il faut donc redéfinir la position de la lumière après les modifications de matrice, comme pour tout objet, si vous voulez avoir une lumière stationnaire. Ca a l'air bizarre et compliqué, mais en fait c'est plus pratique : vous pouvez effectuer des transformations aux sources de lumière comme vous voulez comme si elles étaient des objets normaux, et en plus si vous voulez une source de lumière relative au point de vue (comme par exemple une lampe de casque de mineur), il suffit de la définir avant toute modification de matrice. Voilà trois exemples-type pour illustrer ces longues explications :

Lumière fixe, point de vue mobile | Point de vue fixe, lumière mobile | Point de vue mobile, lumière relative au point de vue

Vous aurez deviné que le point jaune représente la lumière et les lignes rouges et jaunes les axes X et Z. Voyons donc maintenant le code corespondant à chacun des exemples. Je ne rajoute pas les fonctions de dessin des lignes et du point, histoire de ne conserver que l'essentiel.

4.1 Premier exemple : lumière fixe

⇑
Il suffit d'effectuer en premier lieu les transformations, et ensuite de placer la lumière et les objets :

double a=0;
int LightPos[4] = {0,0,3,1};
int MatSpec [4] = {1,1,1,1};
void Draw()
{ |
 | glMaterialiv(GL_FRONT_AND_BACK,GL_SPECULAR,MatSpec);
 | glMateriali(GL_FRONT_AND_BACK,GL_SHININESS,100);
 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 | glMatrixMode(GL_MODELVIEW);
 | glLoadIdentity();
 | gluLookAt(0,6,6,0,0,0,0,1,0);
 | glRotated(a,0,1,0);
 | glLightiv(GL_LIGHT0,GL_POSITION,LightPos);
 | glutSolidSphere(1,50,50);
 | a=+1;
 | SwapBuffers(DC) | // glutSwapBuffers(); pour glut
 | glutPostRedisplay(); | // Uniquement pour GLUT
} |

On modifie d'abord les paramètres du matériau courant : couleur spéculaire blanche et braillance 100 (remarquez que comme le matériau est toujours le même, on aurait pu mettre ces lignes dans InitGL() ).
Vous voyez ensuite que l'on modifie les paramètres d'une source de lumière par glLight{if}[v](). Il suffit de lui transmettre le nom de la lumière, le nom du paramètre que l'on veut changer, et sa nouvelle valeur. Ici encore, le postfixe 'i' signifie que l'on passe des entiers, 'f' que l'on passe des float, et 'v' que le troisième paramètre est un pointeur sur les nouvelles valeurs.
Voyons maintenant de plus près ce que l'on transmet comme position : il ne s'agit pas comme on pourrait s'y attendre de trois valeurs x,y et z, mais de 4 valeurs. Les trois premières sont effectivement les coordonnées cartésiennes, mais que vient foutre ici le 4e élément ? Ben en fait c'est grâce à lui que vous allez déterminer si une source est ponctuelle ou directionnelle (c'est vrai, quoi : je vous bassine avec ça depuis tout-à-l'heure, il fallait bien qu'il se trouve quelque part). Si w (c'est son nom) est à 0 comme dans l'exemple, alors la lumière est ponctuelle. Sinon elle est directionnelle et son sens part du point position transmis vers le point (0,0,0) (donc évitez de créer une lumière directionnelle avec comme position le point (0,0,0) ). Essayez de changer la lumière de l'exemple en lumière directionnelle, pour voir.

4.2 Deuxième exemple : lumière mobile

⇑
Là non plus c'est pas compliqué : il suffit de ne faire tourner que la lumière et non la sphère, donc d'écrire :

double a=0;
int LightPos[4] = {0,0,3,1};
int MatSpec [4] = {1,1,1,1};
void Draw()
{ |
 | glMaterialiv(GL_FRONT_AND_BACK,GL_SPECULAR,MatSpec);
 | glMateriali(GL_FRONT_AND_BACK,GL_SHININESS,100);
 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 | glMatrixMode(GL_MODELVIEW);
 | glLoadIdentity();
 | gluLookAt(0,6,6,0,0,0,0,1,0);
 | glRotated(a,0,1,0);
 | glLightiv(GL_LIGHT0,GL_POSITION,LightPos);
 | glRotated(-a,0,1,0);
 | glutSolidSphere(1,50,50);
 | a=+1;
 | SwapBuffers(DC) | // glutSwapBuffers(); pour glut
 | glutPostRedisplay(); | // Uniquement pour GLUT
} |

Et voilà. Au lieu de faire glLoadIdentity() puis gluLookAt() avant de dessiner la sphère, j'ai mis glRotated(-a,0,1,0), cequi annule la rotation, donc revient au même. Si vous testez cet exemple, vous ne verrez en fait aucune différence par rapport au précédent, car vous n'avez pas de repère fixe. Le résultat sera le même : on a l'impression que la lumière tourne autour de la sphère, car celle-ci est tellement parfaite qu'on ne distingue pas si elle tourne ou non. Par contre, si vous lui attribuez 5 méridiens et 5 parallèles au lieu de 50, là vous verrez une différence.

4.3 Troisième exemple : lumière relative au point de vue

⇑
Comme je l'ai également expliqué, il suffit de définir la position de la lumière avant toute transformation pour que la position soit toujours la même par rapport à la caméra. Donc notre fonction Draw() sera comme ceci :

double a=0;
int LightPos[4] = {0,0,0,1};
int MatSpec [4] = {1,1,1,1};
void Draw()
{ |
 | glMaterialiv(GL_FRONT_AND_BACK,GL_SPECULAR,MatSpec);
 | glMateriali(GL_FRONT_AND_BACK,GL_SHININESS,100);
 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 | glMatrixMode(GL_MODELVIEW);
 | glLoadIdentity();
 | glLightiv(GL_LIGHT0,GL_POSITION,LightPos);
 | gluLookAt(0,6,6,0,0,0,0,1,0);
 | glRotated(a,0,1,0);
 | glutSolidSphere(1,50,50);
 | a=+1;
 | SwapBuffers(DC) | // glutSwapBuffers(); pour glut
 | glutPostRedisplay(); | // Uniquement pour GLUT
} |

Ici la lumière est ponctuelle et située exactement au même endroit que l'oeil (remarquez que comme elle ne bouge jamais et ne se préoccupe pas des transformations, on aurait pu mettre glLightiv() dans InitGL() ). Si vous conservez 50 méridiens et 50 parallèles, vous aurez du mal à voir la sphère tourner. Ici encore, essayez 5 méridiens et 5 parallèles, vous y verrez plus clair.

5. Atténuation

⇑

Bon, vous en avez déjà pas mal vu jusqu'ici, mais il reste encore quelques points à voir. Et notemment l'atténuation. En effet, il est rare de voir une lumière ponctuelle pouvant éclairer à une distance infinie. Si vous êtes capable de voir la Grande Ourse de chez vous, je doute que votre lampe de chevet puisse éclairer aussi loin que cette étoile. OpenGL vous permet d'ajouter des facteurs d'atténuation à vos sources de lumière. Comme une lumière directionnelle est considérée comme une lumière étant à une distance infinie, l'atténuation n'est pas valable pour ce genre de sources.
Vous disposez de 3 constantes d'atténuation pour chaque lumière : GL_CONSTANT_ATTENUATION (appelons-la Kc), GL_LINEAR_ATTENUATION (Kl) et GL_QUADRATIC_ATTENUATION (Kq). Si d est la distance d'un point à la source de lumière, la quantité de chaque composante de la lumière qui arrive à ce point est multipliée par : 1 / (Kc + Kl*d + Kq*d²). Pour changer les constantes d'atténuation il suffit d'appeler glLight(). Par exemple,
glLightf(GL_LIGHT0,GL_QUADRATIC_ATTENUATION,.01f);
Mettra à 0.01 la constante d'atténuation Kq de la lumière GL_LIGHT0.
Par défaut, on a Kc=1, Kl=0 et Kq=0, donc la lumière n'est pas atténuée. Si par exemple vous choisissez pour Kc, Kl et Kq des valeurs respective de 0.1, 0 et 0.01, vous pourrez obtenir quelque chose de ce genre :

Chaque sphère a un rayon de 0.4 et l'espace entre 2 centres est de 1. Notez qu'il n'est pas possible d'atténuer une seuke composante (rouge, bleue ou verte) et que toutes les couleurs de la lumière (ambiente, diffuse et spéculaire) sont atténuées.

6. Couleur

⇑

Tiens, justement, qu'est-ce que c'est que ça, "les couleurs de la lumière" ? On connaissait déjà les couleurs des matériaux, mais pas ça. Et bien, comme vous auriez pu vous en douter, chaque lumière a ses propres couleurs. En effet, un girophare est rouge ou bleu, une lampe à incandescence émet une lumière plutôt jaune, le soleil couchant lance ses ultimes rayons rouge-orangés sur la verte colline... Bref, y'a pas que les objets qui ont des couleurs : les lumières aussi ont droit à la différence. Et comme les objets, elles possèdent chacune une couleur ambiente, une diffuse et une spéculaire. Prenons l'exemple d'un plafonnier dans une pièce : les rayons rebondissent sur les murs et les objet recoivent une lumière dont la provenance est impossible à déterminer tant la lumière a été dispersée : c'est la composante ambiente de la lumière. La lumière diffuse est ce qu'on pourrait appeler la couleur réelle de la source : c'est la couleur des rayons qui viennent directement frapper l'objet. La couleur spéculaire est la couleur de la tache spéculaire que produira la source. Pour un effet réaliste, elle doit être identique à la couleur diffuse.
Ces composantes ne suppriment bien sûr pas les couleurs des matériaux : lors du calcul de la couleur des sommets, les couleurs de la lumière et du matériau sont mélangées. Par exemple, une lumière cyan (0,1,1) éclairant une sphère jaune (1,1,0) donnera une sphère verte (0,1,0). Pour modifier la couleur d'une source de lumière, il faut bien sûr appeler glLight(). Par exemple, pour mettre la lumière diffuse de la lumière n°1 à (.5,.5,1), il suffit de faire
float LightDif[4] = {.5f,.5f,1.f,1.f};
glLightfv(GL_LIGHT0,GL_DIFFUSE,LightDif);
Pour ce qui est de la 4e composante, il s'agit de la composante alpha. Ne vous en préoccupez pas, elle ne sert à rien pour l'instant (vous pouvez la mettre à 0 si ça vous amuse, ça ne changera rien).

Conclusion

⇑

Et ben voilà, je crois qu'on a fait le tour des sources ponctuelles (et directionnelles, qui en sont des dérivées). On a tout de même vu dans ce tutorial comment allumer une lumière, changer sa couleur, sa position, ses facteurs d'atténuation, et si elle est ponctuelle ou directionnelle. Entrainez-vous bien avec les exemples que je vous ai donné (surtout au niveau de la position). On continuera la prochaine fois avec les spots.

Antoche

← Les couleurs ↑ Tutoriaux OpenGL ↑ Les spots →
Last modified on Friday December 23rd, 2005 E-mail any comment


OpenGL : les spots

Accueil
Recherche
Publications
Enseignement
ENSIMAG
ESISAR
Tutoriaux OpenGL
FAQ
Introduction
Initialiser OpenGL avec Windows
Initialiser OpenGL avec GLUT
Les bases
Les couleurs
Les lumières
» Les spots
Les textures
L'input avec GLUT
L'input avec Windows
Matrices 3DSMAX
Format BMP
Tutorials DOS
CV
Coin perso
1. Les normales
2. Per vertex lighting
3. C'est parti
4. Cutoff
5. Direction
6. Exposant
7. Le LightModel
Annexe : Calculer les normales
Menu

OpenGL : les spots
Contenu
Et voici le deuxième et dernier chapitre sur les bases des lumières. Après avoir bien lu et compris ce tutorial, vous devriez être capables de créer toutes les lumières que vous voulez. Allez, c'est parti :

1. Les normales

⇑

Je dois vous avouer un truc : je vous ai caché quelque chose. Pas une petite chose sans importance, mais vraiment un gros truc, qui mérite pas mal de pages dans un bon bouquin. Et ce truc, c'est les normales. Si vous êtes accros des bons bouquins sur les modeleurs 3D, vous devez en avoir entendu parler, car il n'y a pas de lumière sans normales. Laissez-moi vous expliquer : vous savez déjà que lorsque un objet illuminé, il est coloré par 3 couleurs : ambiente, diffuse et spéculaire. Pour trouver la lumière ambiente, c'est pas dur : on multiplie les composantes ambientes du matériau par celle de la lumière et paf on applique ça à toutes les faces :
Ambiente(R/G/B) = AmbienteMatériau(R/G/B) * AmbienteLumière(R/G/B)
Par contre, pour la lumière diffuse, l'intensité de cette lumière dépend de chaque face : si la face est perpendiculaire à la direction de la lumière, l'intensité de la lumière est maximum. Si par contre la face est en biais, la lumière diffuse est plus faible. Et si la face tourne le dos à la source, alors la lumière diffuse est nulle. Donc en gros, pour calculer la lumière diffuse, on multiplie la composante diffuse de la lumière par la composante diffuse du matériau, et ensuite on multiplie ça par le sinus de l'angle entre la direction de la lumière et la face :
Diffuse(R/G/B) = DiffuseMatériau(R/G/B) * DiffuseLumière(R/G/B) * Sin(alpha)
Si on regarde le dessin, et si on sait ce qu'est un sinus et un cosinus (je vais quand même pas redonner les définitions), on remarquera aisément que Sin(alpha) = Cos(Beta). Je suppose que ce n'est un secret pour personne. Donc maintenant, puisqu'on cherche à faire du temps réel, faudrait voir à trouver une forumle suffisamment rapide et autre qu'une table de sin pour trouver ce cosinus (ou ce sinus). Et là, si vous êtes intelligent (ou si avez déjà lu un bouquin quelconque sur la 3D), vous allez me répondre : le produit scalaire ! En effet, pourvu que deux vecteurs soient unitaires, on récupère directement le cosinus entre les deux ! Puisqu'on connait déjà le vecteur lumière, il suffit de trouver l'autre, en pointillés sur le shéma, orthogonal à la face. Et ce vecteur orthogonal, ou vecteur normal (et oui, c'est ça !), comptez pas sur OpenGL pour le trouver tout seul. Et oui, c'est vous qui devez lui dire quelle est la normale de chaque face. Et si je ne vous en ai jamais parlé auparavant, c'est tout simplement parce que GLUT calculait lui-même les normales dans glutSphere(), donc on n'avait pas à s'en préoccuper. Donc la formule de la lumière diffuse est :
Diffuse(R/G/B) = DiffuseMatériau(R/G/B) * DiffuseLumière(R/G/B) * (VecteurLumière.Normale)
De même, la lumière spéculaire s'obtient en vérifiant si les rayons de lumière, réfléchis par la face, sont dirigés vers la caméra. Et pour savoir dans quelle direction est réfléchie la lumière, il faut aussi connaître la normale de la face.

2. Per vertex lighting

⇑

En fait, OpenGL ne calcule pas les lumières face par face, mais sommet par sommet. En effet, si la lumière était calculée pour chaque face, alors toutes les faces auraient une couleur uniforme, ce qui ferait apparaître les arêtes entre les faces (ce qu'on appelle le flat shading), et en plus il faudrait calculer le centre de gravité de la face pour connaitre la direction du vecteur lumière. Alors que si la lumière est calculé sommet par sommet, non seulement on connait tout de suite la direction du vecteur lumière, mais en plus si deux sommets sont illuminés différemment, on fait un petit dégradé entre les deux au moment du remplissage de la face, et hop on voit plus les arêtes. C'est ce qu'on appelle le gouraud shading. Mais on voit aussi l'inconvénient : moins il y a de sommets, et moins la tache de lumière sera précise. Laissez-moi vous expliquer ceci en images :
Les deux images sont les mêmes : il s'agit d'un plan illuminé par un spot rouge. Sauf que sur l'image de gauche, le plan est constitué par une grille de quelques points alors qu'à droite les mailles de la grille sont beaucoup plus reserrées. Vous voyez bien que plus il y a de points, et plus l'image est réaliste. Mais aussi s'il y a plus de points, l'image demandera plus de temps de calcul.
En fait, l'idéal serait de faire du per pixel lighting, c'est-à-dire que la lumière serait calculée pour chaque pixel de la face, au moment du remplissage de la face (ca donne ce qu'on appelle le phong shading). Evidemment, là ca serait parfait, quel que soit le nombre de sommets, mais ce serait tellement lent qu'on ne pourrait plus appeler ca du temps réel. C'est donc une technique qui n'est utilisée que dans les modeleurs, où la qualité prévaut sur la vitesse.
En fait je vous dis ça pour une bonne raison : c'est que dans ce tutorial, pour voir les spots, on va s'amuser à éclairer un mur avec des spots et voir ce que ca donne. "Pas compliqué de dessiner un mur : on fait un petit glBegin(GL_QUADS), 4 glVertex(), un glEnd() et c'est fini", me diriez-vous. Et ben non justement. Parce que si on fait ca, on aura juste 4 sommets aux 4 coins, ca risque de faire un peu trop juste pour voir la tache d'un spot. Donc il faut que notre mur soit en fait une grille plane, de façon à ce qu'on distingue bien la lumière du spot sur le mur.

3. C'est parti

⇑
Et bien allons-y, créons notre mur et notre source de lumière, pour commencer :

float MatSpec[4] = {1.0f, 1.0f, 1.0f, 1.0f};
float MatDif[4] = {1.0f, 1.0f, 1.0f, 1.0f};
float MatAmb[4] = {0.3f, 0.3f, 0.3f, 1.0f};
float Light1Pos[4] = {0.0f, 0.0f, 20.0f, 1.0f};
float Light1Dif[4] = {1.0f, 0.2f, 0.2f, 1.0f};
float Light1Spec[4] = {1.0f, 0.2f, 0.2f, 1.0f};
float Light1Amb[4] = {0.5f, 0.5f, 0.5f, 1.0f};
float Spot1Dir[3] = {0.0f, 0.0f, -1.0f};
void InitGL()
{
 | glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,MatSpec); |  |  | //On applique les paramètres du matériau
 | glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MatDif);
 | glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT,MatAmb);
 | glLightfv(GL_LIGHT0, GL_DIFFUSE, Light1Dif); |  |  | //Et ceux de la lumière
 | glLightfv(GL_LIGHT0, GL_SPECULAR, Light1Spec);
 | glLightfv(GL_LIGHT0, GL_AMBIENT, Light1Amb);
 | glEnable(GL_LIGHTING); |  |  | //Et on allume la lumière
 | glEnable(GL_LIGHT0);
} |
void Draw()
{ |
 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 | glMatrixMode(GL_MODELVIEW);
 | glLoadIdentity();
 | gluLookAt(40,40,100,0,0,0,0,1,0);
 | glLightfv(GL_LIGHT0, GL_POSITION, Light1Pos);
 | glNormal3i(0,0,1); |  |  | //Définit la normale commune à tous les sommets
 | for (int i=-50;i<50;i++)
 | { |
 |  | glBegin(GL_QUAD_STRIP);
 |  | glVertex2i(i,-50);
 |  | glVertex2i(i+1,-50);
 |  | for (int j=-50;j<50;j++)
 |  | { |
 |  |  | glVertex2i(i,j+1); |
 |  |  | glVertex2i(i+1,j+1); |
 |  | } |
 |  | glEnd();
 | } |
 | SwapBuffers(DC) |  |  | // glutSwapBuffers(); pour glut
 | glutPostRedisplay(); |  |  | // Uniquement pour GLUT
} |

Les autres fonctions restent les mêmes. Repompez-les sur les tutorials précédents.
Ici vous aurez remarqué qu'on n'a rien appris de nouveau, à part comment spécifier les normales pour un sommet : il suffit d'appeler glNormal3{fdbsi}[v]() avant l'appel à glVertex(). Ici, comme la grille est plane, tous les sommets on la même normale : (0,0,1).
Le mur est constitué d'un matériau blanc et mat, c'est-à-dire qu'aucune tache spéculaire n'apparaitra : on ne verra que les couleurs ambiente et diffuse.
Pour ce qui est de la source de lumière, elle est telle qu'on en a déjà vu : omnidirectionnelle, et rouge. Donc si on lance le programme, on verra l'image à gauche:

4. Cutoff

⇑
Or ce qu'on veut, nous, c'est un spot. Et ben en fait, notre lumière est déjà à moitié un spot. En effet; qu'est-ce qui distingue un spot d'une source omni ? L'angle qui définit son cône de lumière, appelé cutoff. Ce cutoff, il est en réalité déjà défini, car une omni, c'est tout simplement un spot avec un cutoff de 180° ! Donc tout ce qu'on a à faire pour changer notre omni en spot est de changer ce cutoff, disons à 30° (il peut être compris entre 0 et 90, ou 180), grâce bien sûr à glLight(). Rajoutez donc dans InitGL()
glLighti(GL_LIGHT0, GL_SPOT_CUTOFF, 30);
Et relancez le programme : vous obtiendrez un truc dans ce genre :

5. Direction

⇑
Ca fait un peu bizarre : le spot n'est pas dirigé vraiment perpenciculairement au mur, mais plutôt dans la direction de la caméra. C'est parce qu'on doit aussi dire au spot dans quelle direction éclairer. Et comme on ne lui a pas dit, il éclaire dans la direction par défaut : (0,0,-1), et relativement à la caméra, car le vecteur direction n'a pas subi les transformations de la matrice modelview. Il faut donc lui dire d'éclairer droit vers le mur, donc selon le vecteur (0,0,-1), mais en prenant compte de la transformation de glutLookAt(). Donc, comme pour spécifier la position de la source, il faut spécifier la direction du sopt par glLight() après les transformations appliquées à la matrice modelview. Rajoutez donc les deux lignes suivantes dans Draw(), après glLight() :
float Light1Dir[3] = {0.0f, 0.0f, -1.0f};
glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, Light1Dir);
Et maintenant le résultat est bien ce à quoi on s'attend :

6. Exposant

⇑

Mais ce spot n'est pas super convaincant : la tache a une couleur uniforme, et en plus on distigue trop le crénelage de la grille. Généralement, un spot éclaire plus fort au centre et moins sur le bords. Pour reproduire cet effet, on ne dispose pas comme dans 3DS d'un 'hotspot' et d'un 'falloff', mais on a quand même un truc y ressemblant : l'exposant du spot. En effet OpenGL est capable de diminuer exponentiellement l'intensité de la lumière du spot en fonction de la distance par rapport au centre de la tache. Pour accentuer ce dégradé on peut modifier le facteur expotentiel : plus il est grand et plus la lumière sera faible sur les bords. Un petit exemple pour mieux expliquer :

5 10 20 30 128 (max)

Comme vous pouvez le voir, non seulement la tache du spot est beaucoup plus réaliste, mais en plus on ne distingue plus le crénelage de la grille à partir d'un certain niveau. Pour changer cet exposant, il suffit bien sûr d'appeler glLight() avec comme argument GL_SPOT_EXPONENT. Les spots peuvent également bénéficier de l'atténutation en fonction de la distance, comme pour toute lumière ponctuelle. Notez qu'on ne peut pas faire de spots avec une lumière directionnelle, puisque, la source étant à une distance infinie, on ne peut tenir compte du cutoff.

7. Le LightModel

⇑

Notre petit tour sur les lumière ne serait pas complet sans avoir parlé du LightModel. Qu'est-ce que c'est (je dois bien déjà avoir posé cette question un bon nombre de fois) ? Et bien c'est un ensemble de variables d'état qui définissent comment est gérée l'illumination dans OpenGL, sans se rapporter à une source de lumière spécifique. Ces variables sont au nombre de 3 : la lumière ambiente (GL_LIGHT_MODEL_AMBIENT) de la scène, le modèle de vue (GL_LIGHT_MODEL_LOCAL_VIEWER) et le modèle double face (GL_LIGHT_MODEL_TWO_SIDE). Pour changer une de ces variables, on appelle la fonction glLightModel{if}[v](paramètre, valeur), où paramètre est le nom de la variable à modifier (GL_LIGHT_MODEL_BIDULE), et valeur la valeur à assigner à cette variable.

  • La variable GL_LIGHT_MODEL_AMBIENT est, comme son nom a l'air de l'indiquer, la lumière ambiente de la scène entière. En fait, lorsque la gestion des lumière est activée, il y une source qui est toujours présente, et qui émet une lumière ambiente sur tout les objets. La valeur par défaut de cette lumière est (0.2, 0.2, 0.2, 1.0) (ne vous préoccupez pas du 4e paramètre). Pour la modifier, il suffit d'appeler glLightModel{if}v() avec comme nouvelle valeur un tableau de 4 valeurs (int ou float, suivant la fonction).
  • La variable GL_LIGHT_MODEL_LOCAL_VIEWER est le pendant des lumières directionnelles/ponctuelles pour la caméra. En effet, lorsque la lumière spéculaire d'une face est calculée, elle résulte d'une opération entre le vecteur lumière->objet et le vecteur caméra->objet. Dans la réalité, tous les vecteurs caméra->objet rayonnent à partir du centre de la caméra, un peu comme une lumière ponctuelle rayonne à partir de sa position. On dit alors que le point de vue est local. Ceci oblige OpenGL à recalculer le vecteur caméra->objet à chaque fois qu'il a besoin de calculer la composante spéculaire d'un sommet. Pour accélérer un peu les choses, on peut lui dire que tous les vecteurs caméra->objet sont parallèes à l'axe z de la caméra, comme pour une source directionnelle, où tous les veteurs lumière sont parallèles. Cela diminue le réalisme, mais la vitesse de rendu est sensiblement augmentée. Plus le FOV de la caméra sera grand, et moins le calcul de la lumière semblera réaliste. Par défaut, le modèle local est désactivé (GL_LIGHT_MODEL_LOCAL_VIEWER = 0). Pour l'activer, il suffit d'affecter à la variable une valeur différente de 0.
  • Et enfin, GL_LIGHT_MODEL_TWO_SIDE définit si on travaille en illumination simple ou double face. Attention, si vous êtes en simple-face, cela ne veut pas dire qu'OpenGL ne dessinera que la face avant et pas la face arrière (pour cela il faut utiliser glEnable(GL_CULL_FACE)), mais que le matériau attribué à la face avant sera utilisé pour la face arrière, et que la normale sera la même pour les deux cotés. Si par contre l'illumination est en double-face, la face arrière sera dessinée avec son propre matériau, et sa normale sera l'opposée de celle de la face avant. Pour définir à quel coté (avant ou arrière) de la face on affecte tel ou tel matériau, il faut spécifer GL_FRONT, GL_BACK, ou GL_FRONT_AND_BACK comme premier paramètre de glMaterial() (se reporter aux tutorials précédents). Si GL_LIGHT_MODEL_TWO_SIDE est à 0 (défaut), le modèle simple-face est défini. Sinon, on est en mode double-face.

Et bien voilà, on a fait le tour complet des sources de lumières. Vous pouvez maintenant faire ce que vous voulez avec les lumières pour créer votre petite disco personnelle. Pour ceux qui se poseraient des questions, je rapelle quand même qu'OpenGL est un moteur temps réel basique, et que même s'il est professionnel, il ne gère tout de même pas les ombres ou autres réflections. Si vous en voulez, vous serez obligé de les calculer vous-même. Il existe plusieurs méthodes, plus ou moins rapides et réalistes, peut-être en parlerais-je plus tard. Pour les impatients, allez voir sur le site de sgi, vous aaurez pas mal d'infos là-dessus (en anglais, bien sûr). En attendant le prochain tutorial, sur les textures, je vous propose de lire la petite annexe ci-dessous :

Annexe : Calculer les normales

⇑

Dans l'exemple que je viens de vous montrer, on n'a pas vraiment eu de mal à calculer les normales des points du mur. Mais il se pourrait bien que vous ayez quelques fois des normales à calculer autrement plus complexes, notemment si vous importez des modèles 3DS ou ASE, où les normales ne sont pas forcément spécifiées. Voici donc quelques techniques couramment utilisées pour calculer les normales.
Essayons d'abord de calculer une normale unique pour tous les sommets d'un triangle. Si vous avez des notions de maths, vous aurez remarqué qu'un vecteur normal à un plan peut être obtenu à partir de deux vecteurs de ce plan, du moment qu'ils ne sont pas colinéaires. Et bien nous somme gâtés : dans un triangle, il y a trois [image] côtés, donc 3 vecteurs : il suffit d'en prendre 2 et d'en faire le produit vectoriel, et nous avons notre normale (attention à l'ordre du produit vectoriel : il faut que la normale soit orientée dans le bon sens !). Il faut ensuite la normaliser (c'est-à-dire faire en sorte que sa norme soit égale à 1) pour que le produit scalaire donne directement le cos(Beta), en divisant chaque composante par la norme du vecteur, car sinon les calculs seront faussés. Notez qu'OpenGL peut normaliser les normales pour vous (il suffit pour cela de faire glEnable(GL_NORMALIZE) ), mais ce sera au détriment de la rapidité. Donc maintenant que nous avons la normale de notre face, il suffit de l'appliquer aux 3 sommets et c'est fini.
Mais ce n'est pas totalement satisfaisant : cela nous donnera du flat shading : il n'y aura pas de dégradé entre les sommets, et on verra distinctement les arêtes, comme dans la piètre image que vous voyez au dessus. Pour qu'on ne voie pas les arêtes, ce qu'il faudrait, c'est que deux sommets ayant les mêmes coordonnées aient la même normale, quelle que soit la face à laquelle ils appartiennent. Donc il faut parcourir les sommets et lorsqu'on en trouve plusieurs au même endroit, on fait la moyenne des normales de ces sommets et on leur attribue à tous cette normale moyenne. Et là ch'est machik : cha marche !

Voilà le tutorial est terminé. Si vous avez des questions, vous savez à qui vous adresser. Dans les sources à télécharger se trouve un petit programme qui montre 3 spots se courant après.

Antoche

← Les lumières ↑ Tutoriaux OpenGL ↑ Les textures →
Last modified on Monday May 26th, 2008 E-mail any comment


OpenGL : les textures

Accueil
Recherche
Publications
Enseignement
ENSIMAG
ESISAR
Tutoriaux OpenGL
FAQ
Introduction
Initialiser OpenGL avec Windows
Initialiser OpenGL avec GLUT
Les bases
Les couleurs
Les lumières
Les spots
» Les textures
L'input avec GLUT
L'input avec Windows
Matrices 3DSMAX
Format BMP
Tutorials DOS
CV
Coin perso
Introduction
1. C'est parti
2. Coordonnées de textures (TexCoord)
3. Filtering
4. Répétition
Conclusion
Annexe : charger des images BMP
Menu

OpenGL : les textures
Contenu

Introduction

⇑

Autant vous le dire tout de suite : le texturing est un sujet tellement vaste qu'il ne suffirait pas d'un seul tutorial pour en faire le tour, ni même 3 ou 4. Les possibilités qu'offrent OpenGL sont tellement vastes dans ce domaine que les applications de cet outils sont quasi infinies. Donc là, on va juste voir les bases (ce qui vous permettra quand même de faire des trucs bien sympa), c'est-à-dire comment déclarer une texture à OpenGL, l'appliquer sur un polygone grâce aux TexCoord, et aussi quelques outils qui vont avec tel que le filtering, la répétition... Ca en fait déjà pas mal je pense, et pour ce qui est du mipmapping, de la transparence ou des textures d'environnement, on verra ça plus tard, OK ?

1. C'est parti

⇑

Commencons par le commencement, c'est-à-dire par activer la gestion des textures. Si vous avez bien tout pigé, vous aurez deviné qu'il suffit de faire un petit glEnable() avec le paramètre approprié. Vous avez gagné ! Rajoutez donc dans notre petite InitGL() la ligne suivante :

glEnable(GL_TEXTURE_2D);

Ca active la gestion des textures 2D (ben ouais, ca existe les textures 1D ou 3D). Nous devons maintenant créer un nouvel objet texture. Pour nous, ce sera juste un numéro, mais c'est ce numéro qui nous permettra de spécifier à OpenGL toutes les paramètre de la texture, la texture elle-même, et aussi de lui dire quelle texture on veut mapper. Pour cela il faut utiliser glGenTextures(), qui nous renvoie autant de numéros que de textures demandées. Ici on veut qu'une seule texture, donc on va récupérer un numéro, à bien retenir.
Ensuite, nous décidons quelle est la texture courante grâce à glBindTexture(). La texture courante, c'est celle sur laquelle OpenGL va effectuer les modifs lorsqu'on lui demandera, et c'est aussi la texture qiu sera mappée sur les objets. Donc si vous avez des objets avec des textures différentes, il suffira d'appeler glBindTexture() avec le bon numéro de texture juste avant de dessiner l'objet sur lequel devra être mappé la texture.
Et enfin, nous allons transmettre à OpenGL toutes les caractéristiques de la texture : largeur, hauteur, format, etc... et bien sûr l'image elle-même, grâce à glTexImage2D(). Pour l'instant, pour simplifier les choses, j'ai choisi d'appliquer à notre objet une texture de 2 pixels par 2 pixels, en forme d'échiquier (et ouais, je me suis pas foulé). Voilà donc tout ce que nous devons écrire :

GLubyte Texture[16] =
{
0,0,0,0, 0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF, 0,0,0,0
};

 //Image (2x2)
GLuint Nom;
void InitGL()
{
glClearColor(.5,.5,.5,0); | //Change la couleur du fond
glEnable(GL_DEPTH_TEST); | //Active le depth test
glEnable(GL_TEXTURE_2D); | //Active le texturing
glGenTextures(1,&Nom); | //Génère un n° de texture
glBindTexture(GL_TEXTURE_2D,Nom); | //Sélectionne ce n°
glTexImage2D (
GL_TEXTURE_2D, | //Type : texture 2D
0, | //Mipmap : aucun
4, | //Couleurs : 4
2, | //Largeur : 2
2, | //Hauteur : 2
0, | //Largeur du bord : 0
GL_RGBA, | //Format : RGBA
GL_UNSIGNED_BYTE, | //Type des couleurs
Texture | //Addresse de l'image
);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}

Voilà, j'ai fait ce que j'avais annoncé : J'appelle glGenTextures() avec le nombre de textures voulues et le tableau dans lequel il doit les renvoyer (ici il n'y en a qu'une donc autant mettre &Nom). J'appelle ensuite glBindTexture() pour lui dire avec quelle texture je vais travailler. Et enfin, glTexImage2D(), pour lui balancer toutes les infos dont il a besoin. Les commentaire vous disent déjà à quoi correspondent les paramètres, mais je vais quand même insister sur 2 ou 3 choses :

  • Pour ce qui est du mipmap, j'ai dit qu'on verra ca plus tard, donc je n'en parlerais pas. Contentez-vous de mettre ce paramètre à 0
  • Le 3e paramètre c'est le nombre de composantes de couleurs par pixel. Ici il y en a 4 : R,G, B et A (pour Rouge, Vert, Bleu et Alpha, pour ceux qu'auraient pas encore tout pigé). On peux en avoir de 1 à 4.
  • Ensuite viennent la largeur et la hauteur. Elles ne sont pas forcément égales, mais doivent être impérativement de la forme 2^n. Si une de ces valeurs n'est pas une puissance de 2, ca ne marchera pas. Si vous êtes obligé de bosser sur des textures batardes, utilisez gluScaleImage() pour les mettre à l'échelle (voyez avec votre compilateur ou le Reference Manual pour en savoir plus sur cette fonction).
  • Le paramètre suivant est la largeur du bord de la texture. En effet, on peut rajouter une bordure d'un pixel autour de l'image, et d'une couleur unique. Cela peut servir pour éviter d'avoir des résultat bizarre sur les bords avec le filtering, mais on ne l'utilise pas souvent.
  • Vient ensuite le format de stockage. Ici, j'ai rangé les octets de ma texture dans le format classique RGBA, donc je dis à OpenGL de les lire suivant le format RGBA. On peut mettre GL_COLOR_INDEX, GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA, GL_BGR_EXT, GL_BGRA_EXT, GL_LUMINANCE, et GL_LUMINANCE_ALPHA. Amusez-vous à changer ce paramètre pour voir ce que ca donne.
  • Le 8e paramètre définit sous quel type sont enregistrées les composantes. Ici, ma texture est un tableau de GLubyte (c'est-à-dire unsigned char), donc je dit à OpenGL de les lire en tant que GLubyte. Ce paramètre peut être GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, ou GL_FLOAT.
  • Et enfin, on donne à OpenGL l'endroit où est stockée l'image, histoire qu'il puisse la mapper.

Remarque : Vous vous êtes peut-être demandé pourquoi j'utilise une texture RGBA et pas RGB, alors qu'on a même pas envore vu ce qu'était la composante A. En fait, je fais ça pour deux raisons : d'abord, comme on bosse sur des processeurs 32 bits, la gestion de la mémoire est optimisée pour les types 32 bits. De plus, si vous essayez de mettre une texture RGB à la place, il y de fortes chances pour que le résultat ne soit pas celui que vous attendez. En effet, par défaut, OpenGL considère que chaque pixel occupe 4 octets. Donc il va lire les pixels de l'image 4 par 4, ce qui n'est pas vraiment ce qu'on veut. Pour résoudre ce problème, il faut appeler glPixelStorei(GL_UNPACK_ALIGNMENT,1), mais je n'en dirais pas plus, car si on veut quelque chose d'optimisé et de compatible, on utilise des textures RGBA.

Je vous entends déjà dire "Et les deux dernières lignes ?". Pour l'instant je n'en parle pas, contentez-vous de les recopier bêtement, on verra ça plus tard.

2. Coordonnées de textures (TexCoord)

⇑
Et ben ça y est, mine de rien, notre texture est chargée et prète à être apliquée : on n'a plus qu'à dessiner un truc sur lequel l'appliquer. Pourquoi pas un joli p'tit cube ? Et ben allons-y alors !

void Draw()
{
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); | //Efface le framebuffer et le depthbuffer
glMatrixMode(GL_MODELVIEW); | //Un petit gluLookAt()...
glLoadIdentity();
gluLookAt(3,2,3,0,0,0,0,1,0);
glBegin(GL_QUADS); | //Et c'est parti pour le cube !

glTexCoord2i(0,0);glVertex3i(-1,-1,-1);
glTexCoord2i(1,0);glVertex3i(+1,-1,-1);
glTexCoord2i(1,1);glVertex3i(+1,+1,-1);
glTexCoord2i(0,1);glVertex3i(-1,+1,-1);

 //1 face

glTexCoord2i(0,0);glVertex3i(-1,-1,+1);
glTexCoord2i(1,0);glVertex3i(+1,-1,+1);
glTexCoord2i(1,1);glVertex3i(+1,+1,+1);
glTexCoord2i(0,1);glVertex3i(-1,+1,+1);

//2 faces

glTexCoord2i(0,0);glVertex3i(+1,-1,-1);
glTexCoord2i(1,0);glVertex3i(+1,-1,+1);
glTexCoord2i(1,1);glVertex3i(+1,+1,+1);
glTexCoord2i(0,1);glVertex3i(+1,+1,-1);

 //3 faces

glTexCoord2i(0,0);glVertex3i(-1,-1,-1);
glTexCoord2i(1,0);glVertex3i(-1,-1,+1);
glTexCoord2i(1,1);glVertex3i(-1,+1,+1);
glTexCoord2i(0,1);glVertex3i(-1,+1,-1);

//4 faces

glTexCoord2i(1,0);glVertex3i(-1,+1,-1);
glTexCoord2i(1,1);glVertex3i(+1,+1,-1);
glTexCoord2i(0,1);glVertex3i(+1,+1,+1);
glTexCoord2i(0,0);glVertex3i(-1,+1,+1);

 //5 faces

glTexCoord2i(1,0);glVertex3i(-1,-1,+1);
glTexCoord2i(1,1);glVertex3i(+1,-1,+1);
glTexCoord2i(0,1);glVertex3i(+1,-1,-1);
glTexCoord2i(0,0);glVertex3i(-1,-1,-1);

 //6 faces
glEnd();
SwapBuffers(DC); | //glutSwapBuffers(); pour GLUT
glutPostRedisplay(); | //Uniquement pour GLUT
}

Et voilà. Si vous lancez le programme maintenant, ca va donner une magnifique image de cube mappé avec une texture d'échiquier, comme ça :

Euh, bah non en fait elle est pas si magnifique que ca l'image : y s'passe des trucs bizarres. En fait, c'est parce que OpenGL y va un peu trop à la barbare pour appliquer les textures. En effet, si on réfléchit bien, on se rend compte que pour que l'effet de mapping soit réaliste, il faut tenir compte de la profondeur. Prenons l'exemple d'un triangle dont un des sommets est très éloigné de l'utilisateur par rapport aux deux autres, comme ça :
Là la texture est correctement appliquée, et on a bien l'impression que le triangle est aligné sur l'axe Z.

Et bien si on ne tient pas compte de la profondeur, la texture est appliquée comme si le triangle était face à nous, comme OpenGL le fait actuellement pour notre cube, et ca donnerait ça :
Et là, c'est carrément pas ce qu'on veut. Donc il faut dire à OpenGL d'effectuer une correction de perspective sur les textures (et par la même occasion sur les couleurs). Pour cela, rajoutez dans InitGL() la ligne suivante :

glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST);

Et maintenant, voilà la magnifique image qu'on obtient. C'est quand même un petit peu mieux, non ?
Bon maintenant voyons un peu ce que notre fonction a dans la bide : ici on découvre une nouvelle fonction : glTexCoord{1234}{sifd}[v](). C'est elle qui nous permet de dire à OpenGL quel morceau de la texture est attaché à quel sommet. Ici on a une texture 2 dimensions, donc on utilise glTexCoord2*(). On lui passe en paramètre 2 valeurs, qui sont les coordonnées du point de la texture à attacher au sommet qui va suivre. Le point (0,0) correspond au coin supérieur gauche de la texture, et (1,1) au coin inférieur droit. Mais on peut aussi choisir n'importe quel point de la texture (ou texel) entre 0 et 1. Par exemple si on avait voulu mettre que du blanc, il aurait suffit de mapper la partie supérieure gauche de la texture, donc de remplacer les 1 par des 0.5 (et aussi d'utiliser glTexCoord2f(), parce que sinon les TexCoord auraient été transformées en int).

3. Filtering

⇑
Imaginez que vous êtes dans un simulateur de vol dernier cri : si vous regardez le sol, et même si vous êtes très près, normalement vous ne disinguez pas les pixels qui composent la texture. Alors que si vous jouez à un vieux je du genre de Duke Nukem ou Quake, si vous vous collez à un mur vous verrez distinctement les pixels de la texture, comme dans notre exemple. Mais dans Quake 3, ou dans un simulateur de vol, vous ne verrez jamais la séparation nette entre les pixels : il y aura toujours un dégradé entre le pixel et son voisin. Cette petite révolution du monde de la 3D s'appelle le bilinear filtering, et est parfaitement gérée par OpenGL. Vous vous rappelez les deux lignes que je n'ai pas expliqué tout-à-l'heure ? Et bien elles disaient à OpenGL de ne pas faire de bilinear filtering, mais de prendre le texel le plus proche (d'où le terme GL_NEAREST) lorsqu'il dessine le pixel d'un polygone. Changez donc ce paramètre en GL_LINEAR dans les deux lignes, et voyez le résultat à droite.
On voit bien qu'il y a un dégradé entre chaque texel, d'ailleurs on le voit un peu trop bien, puisque là on a une texture de 4 pixels (2x2), ça fait un peu juste, mais bon. Voyons plutôt cette fonction glTexParameter{if}[v]() : avec elle on règle le filtering, mais en 2 fois. Pourquoi ? Et bien en fait il y a 2 filtering : un lorsqu'il y a magnification (GL_MAG), c'est-à-dire lorsque un texel s'étend sur plusieurs pixels, comme ici, en un lorsqu'il y a minification (GL_MIN), c'est-à-dire lorsqu'un pixel contient plusieurs texels. Dans le cas de la magnification, on l'a vu, le pixel est le résultat de la moyenne des texels environnants. Dans le cas de la minification, s'il y a filtering, le pixel est le résultat de la moyenne des texels contenus.

4. Répétition

⇑
Mais il y a quand même un truc bizarre : sur les bords des faces, la texture subi un dégradé vers le gris, alors que normalement elle devrait subi un dégradé vers la coumeur du pixel de la texture (blanc ou noir). Pourquoi ? Et bien parce qu'OpenGL est configuré pour répéter la texture. C'est-à-dire que si on avait mis une valeur n>1 pour les texcoords, il aurait répété n fois la texture (essayez pour voir). Et lorsqu'il est au bord, il fait un filtering avec le bord opposé de la texture, ce qui conduit à rendre tous les bords... gris !
En mode GL_CLAMP ("bridé"), les coordonnées sont ramenées entre 0 et 1 et sur les côtés de la texture, le filtering est fait entre la texture et le bord (celui que l'on spécifie dans glTexImage2D() ). Pour choisir le mode de texuring , il faut appeler glTexParameter() avec comme premier paramètre GL_TEXTURE_2D, comme second paramètre GL_TEXTURE_WRAP_S (pour affecter les coordonnées horizontales) ou GL_TEXTURE_WRAP_T (pour les coord. verticales), et comme troisième paramètre GL_CLAMP (pour bridé) ou GL_REPEAT (pour répété). Et si on est en mode répété, on obtient bien le résultat que voici. Ici, comme la couleur du bord est (0,0,0,0) par défaut, il y a un dégradé vers le noir sur les bords des faces.
Vous êtes en train de vous demander comment ne pas faire de filtering sur les bord ? Et ben vous pourrez chercher longtemps, parce que... on peut pas. Hé non ! Donc le meilleur moyen est encore de se mettre en GL_REPEAT de de faire des textures dont les bords opposés sont semblables, ou d'utiliser le bord en mode GL_CLAMP si les couleurs ne varient pas trop.
[Rectification : Maintenant, avec l'extension ARB_texture_border_clamp, il est possible de ne pas faire de filtering sur les bords. Il suffit d'utiliser la constante CLAMP_TO_BORDER_ARB (ou CLAMP_TO_BORDER_ARB, suivant votre version d'OpenGL) à la place de GL_REPEAT ou GL_CLAMP.]
Ah oui j'oubliais : pour changer la couleur du bord, on appelle glTexParameter{if}v() avec GL_TEXTURE_BORDER_COLOR comme 2e paramètre et un tableau de 4 valeurs (RGBA pour changer) comme 3e paramètre.

Conclusion

⇑

On en a déjà vu pas mal dans ce tutorial, et pourtant il reste encore beaucoup à dire sur le sujet : il y a le mipmapping, le blending... Et saviez-vous par exemple que les textures possèdent aussi leur propre matrice ? Ben ouais, après tout, appliquer une texture, c'est une simple affaire de projection, non ? (ouh là, je commence à me Weberiser, moi - private joke). Donc tout ça, on le verra plus tard, parce que moi, là, j'ai faim. Alors amusez-vous bien avec les sources que vous pouvez télécharger en bas, et avec l'annexe que voici.

Annexe : charger des images BMP

⇑

Bon OK je dois bien l'avouer, y'a pas que des échiquers de 4 pixels dans le monde de la 3D. Généralement, y'a plutôt des textures assez complexe qu'on aurait du mal à la main, mais qui, bizarrement, sont plus facilement éditables sous formes d'images (.bmp par exemple...). Hé hé, je vous vois venir : "cool, il va nous filer un [image] code tout prêt pour loader des bmp !!". Et ben nan, na ! D'abord je me suis fais chier à décrire le format BMP sur ce site (allez, cherchez un peu), alors c'est pas pour des prunes, vous pouvez faire un loader vous-mêmes, ca devrait durer 15 min à tout casser. Mais bon pour les quiches qui auront le courage de pomper le tutorial et les sources, je vais quand même inclure une petite fonction de chargement de BMP, histoire de pas être trop salaud (vous pouvez voir le résultat à droite).
Et si vous n'avez vraiment pas envie de vous cassez le cul, il existe dans la librairie glaux.h une fonction toute faite pour ce genre de choses. Allez sur le site de Nehe pour voir comment l'utiliser.
Sinon, pour ceux qui auraient l'intention de charger leurs images tous seuls, quelques remarques :

  • D'abord, n'oubliez pas que l'image est inversée. Cela veut non seulement dire que la ligne du bas est stockée en premier, mais aussi que le format est donc BGR, et non RGB. Donc logiquement il suffirait d'appeler glTexImage2d() avec comme format GL_BGR_EXT et paf ca marche. En effet, théoriquement, ça marche. Mais pas partout. En effet, sur certaines cartes 3D (des ATI All-InWonder, pour ne pas le nommer), la texture était quand même chargée en RGB. Donc préférez inerser les composantes R et B de chaque pixel avant d'envoyer la texture à OpenGL, ca vous évitera de faire des patchs. Vous pouvez aussi par la même occasion la mettre sens dessus-dessous (je veux dire faire un 'flip' pour la remettre dans le bon sens).
  • Ensuite, gare au champ 'Taille de l'image' à l'offset 0x22 dans les fichiers BMP : il n'est pas toujours rempli par tous les logiciels, car on peu le retrouver grâce à la largeur et la hauteur. Donc éviter de l'utiliser.
Antoche

← Les spots ↑ Tutoriaux OpenGL ↑ L'input avec GLUT →
Last modified on Tuesday October 18th, 2005 E-mail any comment


OpenGL : l'input avec GLUT

Accueil
Recherche
Publications
Enseignement
ENSIMAG
ESISAR
Tutoriaux OpenGL
FAQ
Introduction
Initialiser OpenGL avec Windows
Initialiser OpenGL avec GLUT
Les bases
Les couleurs
Les lumières
Les spots
Les textures
» L'input avec GLUT
L'input avec Windows
Matrices 3DSMAX
Format BMP
Tutorials DOS
CV
Coin perso
Introduction
1. Généralités
2. Le clavier
1.1 glutKeyboardFunc()
1.2 glutSpecialFunc()
3. La souris
4. Les autres
Menu

OpenGL : l'input avec GLUT
Contenu

Introduction

⇑

Jusqu'à maintenant, nous avons fait pas mal de trucs, tous toujours plus beaux les uns que les autres, mais nous n'avons jamais pu vraiment contrôler les objets. Ce que nous faisions de mieux c'était tourner autour plus ou moins vite et puis c'est tout. En plus la vitesse de rotation dépendait de la vitesse d'affichage, et vu que c'était prévu pour tourner de façon fluide en software, on distinguait plus grand chose avec une GeForce... Nous allons donc apprendre comment gérer les interactions utilisateurs : souris, clavier, et pourquoi pas tablette graphique. Il n'y aura pas de code OpenGL spécifique dans ce tutorial, mais vous pourrez l'utiliser pour modifier les exemples des tuts précédents.

1. Généralités

⇑

Bon, vous avez vraiment du bol que je fasse ça, parce que sous GLUT, c'est tellement simple que c'est presque inutile de faire un tutorial là-dessus. Mais bon puisque y'en a un sous Windows, autant faire le pendant pour GLUT.
Donc, si vous connaissez l'anglais et que vous avez pas envie de vous taper toute cette page, allez directement à la page de specs de GLUT, et vous trouverez tout de suite ce qu'il vous faut. En tout cas le principe n'est pas dur à comprendre : jusqu'ici, on utilisait par exemple glutDisplayFunc() pour dire à GLUT quelle fonction se chargeait de l'affichage, et ben c'est toujours pareil : on va dire à GLUT quelle fonction gèrera le clavier ou la souris, c'est pas plus dur que ça !

2. Le clavier

⇑

Les touches reçues par GLUT sont divisées en 2 parties : les touches simples, qui peuvent être traduites par un char, et les touches spéciales, qui correspondent à un code ASCII étendu (flèches, touches de fonction...). La gestion du clavier est donc séparée en 2 fonctions : celle qui gère les touches normales, et celle qui gère les touches spéciales :

1.1 glutKeyboardFunc()

⇑

Cette fonction sert à spécifier à GLUT quelle fonction s'occupe de la gestion des touches normales. Elle prend donc en paramètre le nom de la fonction. La fonction de gestion doit avoir 3 paramètres : le code ASCII de la touche, et la position x et y de la souris. Un petit exemple :

void GestionClavier(unsigned char key, int x, int y)
{ |
 | printf("vous avez appuyé sur %c ",key);
 | printf("position de la souris : ");
 | printf("%d,%d ",x,y);
 | if (key==27) exit(0);
} |
int main()
{ |
 | // Initialisation de GLUT etc...
 | glutKeyboardFunc(GestionClavier);
 | glutMainLoop();
} |

C'est aussi simple que ça : votre fonction peut s'appeler n'importe comment, mais elle doit forcément recevoir les paramètres (unsigned char, int, int), sinon GLUT n'est pas content. Ici la fonction ne fait qu'afficher les paramètres, et quitte le programme si l'utilisateur a appuyé sur Escape (dont le code ASCII est 27 comme chacun sait).

1.2 glutSpecialFunc()

⇑

Bon, je suppose que vous aurez compris à quoi sert cette fonction. La différence ici, c'est que la touche n'est plus un caractère ASCII, mais un int. Un petit exemple :

void GestionSpecial(int key, int x, int y)
{

printf("vous avez appuyé sur ")
switch (key)
{
case GLUT_KEY_F1 :
case GLUT_KEY_F2 :
case GLUT_KEY_F3 :
case GLUT_KEY_F4 :
case GLUT_KEY_F5 :
case GLUT_KEY_F6 :
case GLUT_KEY_F7 :
case GLUT_KEY_F8 :
case GLUT_KEY_F9 :
case GLUT_KEY_F10 :
case GLUT_KEY_F11 :
case GLUT_KEY_F12 :
case GLUT_KEY_LEFT :
case GLUT_KEY_UP :
case GLUT_KEY_RIGHT :
case GLUT_KEY_DOWN :
case GLUT_KEY_PAGE_UP :
case GLUT_KEY_PAGE_DOWN :
case GLUT_KEY_HOME :
case GLUT_KEY_END :
case GLUT_KEY_INSERT :

printf("F1 ");break;
printf("F2 ");break;
printf("F3 ");break;
printf("F4 ");break;
printf("F5 ");break;
printf("F6 ");break;
printf("F7 ");break;
printf("F8 ");break;
printf("F9 ");break;
printf("F10 ");break;
printf("F11 ");break;
printf("F12 ");break;
printf("Gauche ");break;
printf("Haut ");break;
printf("Droite ");break;
printf("Bas ");break;
printf("Page up ");break;
printf("Page down ");break;
printf("Home ");break;
printf("End ");break;
printf("Insert ");break;

}
printf("position de la souris : ");
printf("%d,%d ",x,y);
}
int main()
{
// Initialisation de GLUT etc...
glutSpecialFunc(GestionSpecial);
glutMainLoop();
}

Et voilà, grâce à cet exemple vous avez tout pigé, et en plus vous avez vu toutes les valeurs que pouvait prendre la touche.
Voilà pour ce qui est du clavier. La fonction glutGetModifiers() est aussi en relation avec le clavier, mais comme elle s'applique aussi à la souris, je n'en parlerais qu'à la fin.

3. La souris

⇑

Cette fois-ci, GLUT nous fournit un peu plus de fontions. En voici la liste :

  • glutMouseFunc() : définit la fonction à appeler lorsqu'un bouton de la souris est appuyé ou relâché. La fonction utilisateur doit avoir les paramètres (int button, int state, int x, int y), où button peu valoir GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON, ou GLUT_RIGHT_BUTTON, state vaut GLUT_UP ou GLUT_DOWN, et x et y sont les coordonnées de la souris par rapport à le fenêtre.
  • glutPassiveMotionFunc() : définit la foncion à appeler lorsque la souris bouge. Les paramètres de la fonction utilisateur doivent être du type (int x, int y), où x et y sont les coordonnées du pointeur de la souris.
  • glutMotionFunc() : même chose que la précédente, sauf que la fonction utilisateur ne sera appelée que si la souris bouge et qu'un bouton est appuyé (comme dans un drag'n drop, par exemple)
  • glutEntryFunc() : c'est pas très utile, mais bon. Cette fonction sert à définir la fonction à appeler lorsque la souris sort ou rentre dans la fenêtre. La fonction utilisateur ne recoit qu'un paramètre, de type int, qui vaut GLUT_LEFT si la souris sort de la fenêtre, ou GLUT_ENTERED si la souris rentre dans la fenêtre.

4. Les autres

⇑

GLUT sait également gérer les tablettes graphiques et autres spaceballs, mais comme tout le monde ne s'en sert pas, je ne vais pas encombrer ce tutorial avec ça. Pour ceux que ca intéresse, ils n'auront qu'à aller voir eux-même dans les pages de références de GLUT.
Par contre il reste une fonction qui sert à la fois pour la souris et pour le clavier : la fonction glutGetModifiers(). Ce n'est pas une fonction de callback comme les précédentes, mais une fonction d'état : elle sert à savoir si les touches Ctrl, Alt ou Shift sont appuyées. Il ne faut l'appeler que dans un fonction de gestion du clavier ou de la souris. Elle ne prend aucun paramètre et renvoie un int qui peut valoir GLUT_ACTIVE_SHIFT, GLUT_ACTIVE_CTRL ou GLUT_ACTIVE_ALT.

Ben voilà c'est déjà terminé. Pas de source à télécharger aujourd'hui, puisqu'on a rien vu de particulier niveau OpenGL.

Antoche

← Les textures ↑ Tutoriaux OpenGL ↑ L'input avec Windows →
Last modified on Tuesday October 18th, 2005 E-mail any comment


OpenGL : l'input avec Windows

Accueil
Recherche
Publications
Enseignement
ENSIMAG
ESISAR
Tutoriaux OpenGL
FAQ
Introduction
Initialiser OpenGL avec Windows
Initialiser OpenGL avec GLUT
Les bases
Les couleurs
Les lumières
Les spots
Les textures
L'input avec GLUT
» L'input avec Windows
Matrices 3DSMAX
Format BMP
Tutorials DOS
CV
Coin perso
Introduction
1. Généralités
2. Le clavier
3. La souris
Menu

OpenGL : l'input avec Windows
Contenu

Introduction

⇑

Jusqu'à maintenant, nous avons fait pas mal de trucs, tous toujours plus beaux les uns que les autres, mais nous n'avons jamais pu vraiment contrôler les objets. Ce que nous faisions de mieux c'était tourner autour plus ou moins vite et puis c'est tout. En plus la vitesse de rotation dépendait de la vitesse d'affichage, et vu que c'était prévu pour tourner de façon fluide en software, on distinguait plus grand chose avec une GeForce... Nous allons donc apprendre comment gérer les interactions utilisateurs : souris, clavier, et pourquoi pas tablette graphique. Il n'y aura pas de code OpenGL spécifique dans ce tutorial, mais vous pourrez l'utiliser pour modifier les exemples des tuts précédents.

1. Généralités

⇑

Bon, vous aurez compris que les interactions se gèrent comme le reste : grâce aux messages envoyés à la WinProc. Donc tout ce que nous allons faire c'est lister les messages qui nous intéressent.

2. Le clavier

⇑

Le principal message concernant le clavier est WM_CHAR. Le caractère est contenu dans wParam (à convertir en TCHAR). Ce message n'est pas généré directement, mais c'est une traduction du message WM_KEYDOWN. Ce dernier gère tous les messages provenant du clavier, même les touches étendues. Si la touche est une touche ASCII normale, alors TranslateMessage() génèrera un message WM_CHAR à partir du WM_KEYDOWN, pour que le message soit plus simple à gérer. Si vous n'appelez pas TranslateMessage(Msg) pour chaque message, vous devrez gérer tous les messages provenant du clavier à travers le message WM_KEYDOWN. Pour ce message, wParam contient le code de la touche virtuelle enfoncée. Voici la liste des touches virtuelles possibles :

VK_LBUTTON (bouton gauche de la souris)
VK_RBUTTON (bouton droit de la souris)
VK_MBUTTON (bouton milieu de la souris)
VK_CANCEL
VK_BACK
VK_TAB
VK_CLEAR
VK_RETURN
VK_SHIFT
VK_CONTROL
VK_MENU (Alt)
VK_PAUSE
VK_CAPITAL (Caps Lock)
VK_ESCAPE
VK_SPACE
VK_PRIOR (Page Up)
VK_NEXT (Page Down)
VK_END
VK_HOME
VK_LEFT
VK_UP
VK_RIGHT
VK_DOWN
VK_SELECT (???)
VK_EXECUTE (???)
VK_SNAPSHOT (Print Screen)
VK_INSERT
VK_DELETE
VK_HELP
VK_0, ... , VK_9
VK_A , ... , VK_Z
VK_LWIN (touche 'Windows' gauche - pour les claviers windows)
VK_RWIN (touche 'Windows' droite - pour les claviers windows)
VK_APPS (touche 'Apps' - pour les claviers windows)
VK_NUMPAD0, ... , VK_NUMPAD9
VK_MULTIPLY (*)
VK_ADD (+)
VK_SUBTRACT (-)
VK_DECIMAL (.)
VK_DIVIDE (/)
VK_F1, ... , VK_F24
VK_NUMLOCK
VK_SCROLL (Scroll Lock)
VK_ATTN (???)
VK_CRSEL (???)
VK_EXSEL (???)
VK_EREOF (???)
VK_PLAY (???)
VK_ZOOM (???)
VK_NONAME (???)
VK_PA1 (???)
VK_OEM_CLEAR (???)

Les touches sans parenthèses se passent de commentaires, celles commentées par (???) ne sont pas spécifiques aux claviers standards PC.

Vous aurez aussi remarqué que les clics de souris peuvent aussi être gérés grâce aux touches virtuelles : le message WM_KEYDOWN n'est donc pas un simple message clavier, mais gère aussi pas mal de touches spéciales.

3. La souris

⇑

Là par contre y'a pas mal de messages. Voici la liste des messages utiles :

  • WM_LBUTTONDBLCLK, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDBLCLK, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_RBUTTONDBLCLK, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_MOUSEMOVE : Ces messages se comprennent d'eux-mêmes. Les valeurs de wParam et lParam sont les mêmes pour tous : wParam contient les autres touches appuyés en même temps : MK_CONTROL, MK_SHIFT ... et MK_LBUTTON, MK_MBUTTON, MK_RBUTTON. LOWORD(lParam) et HIWORD(lParam) renvoient quand à eux les coordonnées x et y du curseur.
  • WM_MOUSEWHEEL : C'est à peu près la même chose que pour WM_MOUSEMOVE, sauf que là les touches parallèles sont stockées dans LOWORD(wParam), et HIWORD(wParam) contient la valeur de la rotation de la molette.

Il y en a quelques autres, mais ils ne sont pas suffisamment utiles pour être présentés ici, et de toute façon vous avez la liste de tous les messages dans l'aide de VC++.

Ben voilà c'est déjà terminé. Pas de source à télécharger aujourd'hui, puisqu'on a rien vu de particulier niveau OpenGL.

Antoche

← L'input avec GLUT ↑ Tutoriaux OpenGL ↑
Last modified on Tuesday October 18th, 2005 E-mail any comment


Exemples OpenGL

Exemples OpenGL Vu dans ... http://raphaello.univ-fcomte.fr

Avec la librairire GLUt
Un bras robot | Schéma de fonctionnement du glOrtho |
Un carré avec dégradé de couleurs | Schéma de fonctionnement du glFrustum |
Une courbe de Bézier | Schéma de fonctionnement du gluPerspective |
Élimination des parties cachées | Schéma facteur de forme en radiosité |
Les ensembles de facettes | L'hémisphère en radiosité |
Ombrages flat et smooth (uni et lissé) | L'hémicube en radiosité |
Des roues dentées | Valeur des éléments de l'hémicube en radiosité |
L'animation avec la librairie GLUt | L'occulation entre facettes en radiosité |
Une liste d'affichage | Le Z-Buffer sur l'hémicube en radiosité |
Une autre liste d'affichage | Des boules s'entrechoquant |
Les lumières | Un objet en déformation |
Un maillage de Bézier | Une boule bondissante |
Les matériaux | Une sphère spéculaire |
L'utilisation de la souris au sein de la librairie GLUt | Un Tie Bomber (OpengGL 1.1 nécessaire) |
Les NURBS | Un Tie Raptor (OpengGL 1.1 nécessaire) |
Les objets de la librairie GLUt | Un AT-AT de l'Empire (OpengGL 1.1 nécessaire) |
Les primitives graphiques | Une X-Wing ouverte (OpengGL 1.1 nécessaire) |
La visualisation | Un oiseau de proie Klingon (OpengGL 1.1 nécessaire) |
Les projections | Un oiseau de guerre Romulien (OpengGL 1.1 nécessaire) |
L'utilisation de la fonction Reshape au sein de la librairie GLUt | Une police de caractères bitmap |
La sauvegarde d'images en OpenGL | Une police de caractères vectorielle |
Une sphère blanche | Calcul des normales pour Lambert, Gouraud et Phong |
Une surface de Bézier | Problème lié à l'utilisation de normales moyennes pour les calculs d'illumination |
Le survol d'un plan constitué de sphères | Décomposition d'un rayon lumineux incident |
Des teapots | Calcul de la lumière spéculaire |
Un système Terre-Lune | L'utilisation du buffer accumulation pour réaliser de l'anti-aliasing |
Une surface de Bézier texturée | Une animation sur base d'anneaux Olympiques |
Une surface texturée modélisant un échiquier | Des teapots clippés |
L'utilisation des viewports | Le brouillard |
La sélection d'objets | - | -

Avec la librairie Aux
Un bras robot | Décomposition d'un rayon lumineux incident |
Un carré blanc | Calcul de la lumière spéculaire |
Un carré avec dégradé de couleurs | L'utilisation du buffer accumulation pour réaliser de l'anti-aliasing |
Une courbe de Bézier | Une animation sur base d'anneaux Olympiques |
Élimination des parties cachées | Des teapots clippés |
Les ensembles de facettes | Le brouillard |
Ombrages flat et smooth (uni et lissé) | Des boules s'entrechoquant |
Des roues dentées | Un objet en déformation |
L'animation avec la librairie Aux | Une boule bondissante |
Une liste d'affichage | Une sphère spéculaire |
Une autre liste d'affichage | Un Tie Bomber (OpengGL 1.1 nécessaire) |
Les lumières | Un Tie Raptor (OpengGL 1.1 nécessaire) |
Un maillage de Bézier | Un AT-AT de l'Empire (OpengGL 1.1 nécessaire) |
Les matériaux | Une X-Wing ouverte (OpengGL 1.1 nécessaire) |
L'utilisation de la souris au sein de la librairie Aux | Un oiseau de proie Klingon (OpengGL 1.1 nécessaire) |
Les NURBS | Un oiseau de guerre Romulien (OpengGL 1.1 nécessaire) |
Les objets de la librairie Aux | Une police de caractères bitmap |
Les primitives graphiques | Une police de caractères vectorielle |
La visualisation | Des teapots |
Les projections | Un système Terre-Lune |
L'utilisation de la fonction Reshape au sein de la librairie Aux | Une surface de Bézier texturée |
La sauvegarde d'images en OpenGL | Une surface texturée modélisant un échiquier |
Une sphère blanche | L'utilisation des viewports |
Une surface de Bézier | La sélection d'objets |
Calcul des normales pour Lambert, Gouraud et Phong | Le survol d'un plan constitué de sphères |
Problème lié à l'utilisation de normales moyennes pour les calculs d'illumination | - | -

RETOUR


OpenGL - Partie 1

OpenGL (Partie 1) Vu dans ... http://raphaello.univ-fcomte.fr

Définition

GL et OpenGL sont des librairies graphiques d'affichage trois dimensions.

GL (Iris GL): Librairie graphique standard de Silicon Graphics Inc. (orientée visualisation). Utilisée sur les stations de travail graphiques de SGI (sous système d'exploitation IRIX) ainsi que sur les matériels équivalents d'autres constructeurs (sous licence).

OpenGL: Sous-ensemble de GL ne nécessitant pas une licence commerciale SGI

Conçu et spécifié à la fin des années 80 - début des années 90

Introduction

Interface logicielle avec les adaptateurs graphiques 3D

Disponible pour un nombre important de plateformes et de langages (initialement C et Fortran)

Environ 120 instructions distinctes exécutables au moyen du langage de programmation utilisé

Spécification d'objets graphiques et production d’applications graphiques 3D interactives

Conçu initialement pour fonctionner en environnement Unix

-> Exploitation de X-Window
-> Exploitation des réseaux

Standard de la programmation d'applications graphiques en CAO

Claire évolution vers l'industrie du jeu (support de techniques de rendu améliorant la qualité des images, exemples: textures évoluées, shaders)

Indépendant de la machine hôte

Existence des versions successives suivantes (ajout de nouvelles fonctionnalités, compatibilité ascendante?!):

  • 1.0, (01/92): Version de base, mode immédiat
    • 1.1, (01/97): Introduction des textures
    • 1.2
    • 1.3
    • 1.4
    • 1.5, (07/03): Introduction des Vertex Buffer Objects (VBOs), mode retenu
  • 2.0, (09/04): Introduction du langage GLSL pour la programmation de "shaders"
    -> implantation et exécution de programmes "utilisateurs" sur la carte graphique
    • 2.1
  • 3.0, (07/08): Introduction d'un mécanisme de "deprecation" pour les caractéristiques anciennes
    • 3.1
    • 3.2
  • 4.0, (03/10): Introduction de l'arithmétique 64 bits, convergence vers les nouvelles fonctionnalités de DirectX'11
    • 4.1
    • 4.2
    • 4.3
    • 4.4

Existence des releases suivantes:

  • OpenGL: Version pour systèmes classiques: Stations de travail, ordinateurs portables, ... (programmation en C, Fortran, ...)
  • OpenGL ES (Embedded Systems): Version pour systèmes embarqués: Téléphones mobiles, tablettes, GPS, ...
  • OpenGL SC (Safety Critical): Version pour systèmes critiques: Aviation, industrie nucléaire, industrie de l'armement, ...
  • WebGL: Version dédiée à l'affichage 3D dans les navigateurs Internet (Programmation en Javascript) (Exemples)

Géré par le consortium OpenGL (3Dlabs, Apple, ATI, Dell, IBM, Intel, NVidia, Oracle, ..., http://www.opengl.org)

Fonctionnalités principales

  • Dessin de facettes
  • Dessin de quadriques
  • Dessin de surfaces B-Splines
  • Dessin de NURBS
  • Affichages en fil de fer et en plein (surfacique)
  • Affichage avec élimination des parties cachées
  • Visualisations en projection orthographique
    ou en projection en perspective
  • Lumières et matériaux
  • Ombrages plat et lissé (Gouraud)
  • Anticrénelage (antialiasing)
  • Gestion des textures
  • Depth cueing et effets atmosphériques (brouillard,...)
  • Flou de déplacement et profondeur de champ
  • Double-Buffering
  • OpenGL Shading Language (GLSL) depuis la version 2.0
  • ...

Exemple

#include <~~~~~~~~.h>
void main() {
...
glViewport(0,0,512,512);
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0,1.0,1.0);
glOrtho(-1.0,1.0,-1.0,1.0,-1.0,1.0);
glBegin(GL_POLYGON);
glVertex2f(-0.5,-0.5);
glVertex2f(-0.5,0.5);
glVertex2f(0.5,0.5);
glVertex2f(0.5,-0.5);
glEnd();
glFlush();
...
}

Ce programme dessine un "carré" blanc au centre d’une "fenêtre" de fond noir.

Architecture simplifiée

Architecture en pipeline avec des étages successifs réalisant les opérations nécéssaires au processus de visualisation d'une scène (création d'une image)

Circulation des données gérées au cours du rendu entre ces étages avec transformations de leurs types au sein des étages:

  • En entrée : Des sommets en 3D, les paramètres d'affichage, ...
  • En sortie : Une image

Fonctionnement en parallèle des étages du pipeline
Fonctionnement en tâches élémentaires parallèles identiques (parallélisme sur les données) au sein des étages du pipeline

Bibliothèques d'OpenGL

(1) OpenGL Library (GL)

Fonctions de base pour l'affichage 3D en OpenGL:

  • Dessin de sommets, de segments de droite et de facettes
  • Matériaux et lumières
  • Transformations géométriques
  • Caméras
  • Textures
  • Gestion des paramètres de rendu
  • ...

Pas de fonction pour la construction d'une interface utilisateur (fenêtres, souris, clavier, gestionnaire d'événements, ...)

Préfixe des fonctions: gl

Librairie standard

En langage C, déclarations dans le fichier GL/gl.h

(2) OpenGL Utility Library (GLU)

Commandes bas-niveau écrites en GL:

  • Transformations géométriques spécifiques à certaines actions
  • Transformation des polygones quelconques en ensembles de polygones triangulaires
  • Rendu des surfaces paramétriques et quadriques
  • ...

Préfixe des fonctions: glu

Librairie standard

En langage C, déclarations dans le fichier GL/glu.h

(3) OpenGL extension to X-Window (GLX)

Utilisation d’OpenGL en association avec X-Window pour la construction d'une interface utilisateur

Préfixe des fonctions: glX

Librairie non standard

(4) Auxiliary Library

Bibliothèque écrite pour rendre simple la programmation de petites applications dans le cadre d'interfaces graphiques interactives simples:

  • Gestion d'une fenêtre d'affichage
  • Gestion de la souris
  • Gestion du clavier
  • ...

Programmation événementielle

Préfixe des fonctions: aux

Librairie non standard

Librairie non spécifique à un système d'exploitation

L'Auxiliary Library: Aux

(5) OpenGL Tk Library (GLTk)

Équivalent à Aux mais avec l'utilisation de TclTk pour l'interface graphique

Préfixe des fonctions: tk

Librairie non standard

Librairie non spécifique à un système d'exploitation

(6) OpenGL Utility Toolkit (GLUT)

Équivalent à Aux du point de vue des fonctionnalités principales et des principes d'utilisation

Interface utilisateur programmable plus élaborée (multifenêtrage, menus popup)
-> plus grande puissance et mais plus grande complexité

Préfixe des fonctions: glut

Librairie non standard mais très couramment utilisée

Librairie non spécifique à un système d'exploitation

En langage C, déclarations dans le fichier GL/glut.h

Fonctionnalités supplémentaires par rapport à Aux:

  • Gestion des menus
  • Gestion des environnements multifenêtrés
  • Existence de polices de caractères bitmap et vectorielles intégrées
  • Gestion de périphériques d'entrée supplémentaires
  • ...

Autres exemples

L'Utility Toolkit: GLUT

Syntaxe de la librairie GL (fichier GL/gl.h)

D'une manière générale, emploi des règles syntaxiques du langage C

Spécificités supplémentaires:

  • Les noms de fonction de la librairie GL commencent par le préfixe gl.

  • Les noms de constante (#define ou enum) sont donnés en majuscule et commencent par le préfixe GL_.

  • Certains noms d'instruction se terminent par un suffixe.

Exemple: glVertex2f

Ce suffixe indique le nombre et le type des arguments de la fonction.

- | Type | Type C 32 bits | Type OpenGL
b | entier 8 bits | signed char | GLbyte
s | entier 16 bits | short | GLshort
i | entier 32 bits | long, int | GLint, GLsizei
f | réel 32 bits | float | GLfloat, GLclampf
d | réel 64 bits | double | GLdouble, GLclampd
ub | entier non signé 8 bits | unsigned char | GLubyte, GLboolean
us | entier non signé 16 bits | unsigned short | GLushort
ui | entier non signé 32 bits | unsigned int | GLuint, GLenum, GLbitfield

Exemple

glVertex2i(1,3);

<=>

glVertex2d(1.0,3.0);

Une lettre terminale v supplémentaire (v pour vector) indique que la fonction prend comme paramètre un pointeur sur un tableau.

glColor3i(1,0,1);

<=>

int c[3]={1,0,1};
glColor3iv(c);

Variables d'état

OpenGL est une state-machine.

-> Des instructions existent pour placer le système dans certains "états".
Ceux-ci seront utilisés à cette valeur jusqu'à ce qu'ils soient changés (couleur de tracé, matrice(s) de transformation représentative(s) du(des) repère(s) courant(s), activation de l'élimination des parties cachées, lumières, matériel, texture, ...).

Chaque fois que le dessin d'un objet est réalisé (sommet, segment de droite, facette), l'ensemble de ces "variables d'états" définit la manière avec laquelle l'objet est dessiné.

L'ensemble des variables d'états constitue l'"environnement" OpenGL.

Toute variable d'état possède une valeur par défaut affectée à l'amorçage du programme utilisant OpenGL.

Fonctions de manipulation directe des variables d'état

  • glEnable(GLenum pname)

Activation d'une variable d'état booléenne

pname: Variable d'état à activer

Exemples:
glEnable(GL_LIGHTING) -> activation de la gestion des lumières et des matériaux,
glEnable(GL_DEPTH_TEST) -> activation de l'élimination des parties cachées

  • glDisable(GLenum pname)

Inhibition d'une variable d'état booléenne

pname: Variable d'état à désactiver

  • glGetBooleanv(GLenum pname,GLboolean *param)

  • glGetIntegerv(GLenum pname,GLinteger *param)

  • glGetFloatv(GLenum pname,GLfloat *param)

  • glGetDoublev(GLenum pname,GLdouble *param)

Consultation de variables d'état de l'environnement OpenGL

pname: Variable d'état consultée

param: Résultat (pointeur sur une variable si une seule valeur, tableau de variables si plusieurs valeurs)

  • GLboolean glIsEnabled(GLenum pname)

Consultation de variables d'état booléennes

pname: Variable d'état consultée

  • glPushAttrib(GLbitfield mask)

Sauvegarde de variables d'état par empilement dans la pile dédiée de l'environnement OpenGL

mask: Variables d'état à sauvegarder (descriptif complet non réalisé ici car trop long)

  • glPopAttrib()

Restoration de variables d'état par dépilement depuis la pile dédiée de l'environnement OpenGL

Exemple

glEnable(GL_DEPTH_TEST);
GLboolean v = glIsEnabled(GL_DEPTH_TEST);

Active l'affichage avec élimination des parties cachées puis récupère la valeur d'activation de l'affichage avec élimination des parties cachées.

Instructions de base d'OpenGL

Version d'OpenGL

  • GLubyte *glGetString(GLenum name);

Retour d'une chaine de caractères de description de la librairie OpenGL utilisée

name: Information recherchée
GL_VENDOR: Fabriquant de la librairie
GL_RENDERER: Nom de la librairie
GL_VERSION: Version de la librairie
GL_EXTENSIONS: Liste des extensions disponibles (items séparés par des espaces)

Programme GLUt

Exécutable GLUt

Détection d'erreur

  • GLenum glGetError(void);

Retour de la valeur actuelle de la variable d'état erreur de l'environnement OpenGL

7 valeurs retournées possibles:
GL_NO_ERROR,
GL_INVALID_ENUM,
GL_INVALID_VALUE,
GL_INVALID_OPERATION,
GL_STACK_OVERFLOW,
GL_STACK_UNDERFLOW,
GL_OUT_OF_MEMORY.
Replacement de la variable d'état erreur à GL_NO_ERROR après interrogation

Programme GLUt

Exécutable GLUt

Effacement de la fenêtre de dessin (viewport)

L'environnement OpenGL inclut plusieurs tampons mémoire effaçables individuellement ou par lot:

  • Le tampon couleur (frame buffer)
    -> Stockage de la couleur des pixels de l'image calculée (le tampon couleur doit habituellement être vidé à la couleur de fond au début du calcul d'une nouvelle image, il est généralement utilisé et transmis automatiquement comme image à afficher dans la fenêtre de visualisation),
  • Le tampon profondeur
    -> Stockage, pour chaque pixel de l'image calculée, d'une information de profondeur utilisée lors de l'élimination des parties cachées (si l'élimination des parties cachées est activée, le tampon profondeur doit habituellement être vidé avant le calcul de toute nouvelle image),
  • Le tampon accumulation
    -> Accumulation d'images les unes sur les autres,
  • Le tampon stencil.

  • void glViewport(GLint x,GLint y,GLsizei tx,GLsizei ty);

Choix de la position et de la taille du viewport d'affichage (résolution des tampons mémoire)

x,y: Position
tx,ty: Taille

  • void glClear(GLbitfield mask);

Effacement d'un (ou de plusieurs) buffer écran

mask: Buffer à éffacer

mask

GL_COLOR_BUFFER_BIT
Effacement des pixels du buffer d'affichage

GL_DEPTH_BUFFER_BIT
Effacement de l'information de profondeur
associée à chaque pixel du buffer d'affichage
(information utilisée
pour l'élimination des parties cachées)

GL_ACCUM_BUFFER_BIT
Effacement du tampon accumulation utilisé
pour composer des images

GL_STENCIL_BUFFER_BIT
Non renseigné

Les valeurs de masque sont composables par "ou" (| en C) pour effectuer plusieurs opérations d'effacement en une seule instruction glClear et rendre possible l'optimisation de ces opérations.

  • void glClearColor(GLclampf r,GLclampf v,GLclampf b, GLclampf alpha) ;

Choix de la couleur d'éffacement du tampon couleur

r, v, b, alpha: Couleurs de remplissage du tampon couleur lors d'un effacement par glClear

  • void glClearDepth(GLclampf depth) ;

Choix de la profondeur d'initialisation du tampon profondeur

depth: Valeur de remplissage du tampon profondeur lors d'un effacement par glClear

Choix de la couleur de tracé

  • void glColor3{b s i f d ub us ui} (TYPE r,TYPE v,TYPE b);

  • void glColor4{b s i f d ub us ui} (TYPE r,TYPE v,TYPE b,TYPE alpha);

r, v, b, alpha: Composantes colorées élémentaires de la couleur de tracé

  • void glColor3{b s i f d ub us ui}v (const TYPE *v);

  • void glColor4{b s i f d ub us ui}v (const TYPE *v);

v: Tableau contenant les composantes de la couleur de tracé

Programme Aux | Programme GLUt
Exécutable Aux | Exécutable GLUt

La couleur choisie est active pour tous les objets créés après spécification, jusqu'à nouvelle spécification.

Cette couleur n'est utilisée que si la gestion des lumières et des matériaux est désactivée. En cas d'activation de cette fonctionnalité, ce sont les calculs d'éclairage, compte tenu des lumieres, des matériaux et éventuellement des textures spécifiés, qui permettent le calcul de la couleur des pixels.

Terminaison du tracé d'une image

Instruction utilisée pour forcer l'exécution entière des commandes GL qui la précèdent dans le flux d'exécution OpenGL (vidage d'un pipeline d'affichage sur une station graphique, exécution des ordres de tracé sur un réseau, ...).
Souvent utilisée pour indiquer qu'une image est entièrement tracée et peut donc être affichée.

  • void glFlush(void);

  • void glFinish(void);

glFinish se met en attente d'une notification d'exécution de la part du dispositif graphique tandis que glFlush n'est pas bloquant.

Principe de programmation OpenGL et élimination des parties cachées

Généralement, la programmation OpenGL de l'affichage d'une scène correspond à la programmation de l'exécution d'une boucle infinie d'affichage d'images ayant pour conséquence la création d'une suite d'images (éventuellement identiques):

while(1) {
definir_camera();
glClear(GL_COLOR_BUFFER_BIT);
dessiner_objet_3D_A();
dessiner_objet_3D_B();
dessiner_objet_3D_C();
...
dessiner_objet_3D_Z();
afficher_Image();
attendreEtGererEvenement();
}

-> L'élimination des parties cachées n'est pas forcément correctement réalisée car on utilise un algorithme du peintre pour créer chacune des images. Les objets sont affichés les uns sur les autres dans l'ordre d'exécution du programme et donc l'ordre d'affichage a une influence sur le résultat obtenu. Si l'ordre est bon, l'élimination des parties cachées est réalisée.

-> Utilisation de la fonction glEnable pour valider l'élimination des parties cachées (généralement par Z-Buffer en OpenGL) via modification de la variable d'état GL_DEPTH_TEST.
-> L'effacement des tampons couleur et profondeur doit être réalisé entre chaque image au lieu du seul tampon couleur.
-> L'ordre de dessin des objets n'a plus d'importance.

glEnable(GL_DEPTH_TEST);
while(1) {
definir_camera();
glClear(GL_COLOR_BUFFER_BIT|
GL_DEPTH_BUFFER_BIT);
dessiner_objet_3D_A();
dessiner_objet_3D_B();
dessiner_objet_3D_C();
...
dessiner_objet_3D_Z();
afficher_Image();
attendreEtGererEvenement();
}

Sans élimination des parties cachées Avec élimination des parties cachées
Programme Aux | Programme GLUt
Exécutable Aux | Exécutable GLUt

Sommets, lignes et polygones

  • Sommet: Ensemble de trois coordonnées représentant une position dans l'espace

Remarque: OpenGL travaille en coordonnées homogènes.

  • Ligne: Segment de droite entre deux sommets

  • Polygone: Surface plane délimitée par une boucle fermée de lignes

Par définition, un polygone OpenGL ne se recoupe pas, n'a pas de trou et est de bord convexe.
Si on veut représenter un polygone ne vérifiant pas ces conditions, on devra le subdiviser (tessèlation) en un ensemble de polygones convenables. GLU propose des fonctions pour gérer les polygones spéciaux.
OpenGL ne vérifie pas les hypothèses de définition des polygones et les dessine même s'ils ne les respectent pas. Le résultat à l'affichage n'est alors pas déterministe.

Déclaration de la position d'un sommet

  • glVertex{234}{sifd}[v](TYPE coords) ;

coords: Coordonnées de la position
Si seulement 2 valeurs sont spécifiés, ces valeurs représentent x et y et z est défini à 0.0.

Cette fonction est utilisée uniquement pour la déclaration de sommets au sein d'une primitive graphique.

Déclaration d'une primitive graphique

Une primitive graphique est constituée d'un ensemble de sommets.

Elle est créée par la réalisation successive des instructions glVertex permettant la définition des positions de ses sommets.

La primitive est délimitée au début et à la fin par

  • void glBegin(GLenum mode) ;

mode: Type de primitive à dessiner à choisir parmi GL_POINTS, GL_LINES, GL_LINE_STRIP, GL_LINE_LOOP, GL_POLYGON, GL_QUADS, GL_QUAD_STRIP, GL_TRIANGLES, GL_TRIANGLE_STRIP ou GL_TRIANGLE_FAN

et

  • void glEnd(void) ;

Type de primitive graphique
GL_POINTS | GL_LINES
GL_LINE_STRIP | GL_LINE_LOOP
GL_POLYGON | GL_QUADS
GL_QUAD_STRIP | GL_TRIANGLES
GL_TRIANGLE_STRIP | GL_TRIANGLE_FAN

IMPORTANT : A l'exception du cas particulier des bitmaps, la librairie GL ne comporte aucune autre fonction de dessin que celle permettant de dessiner une des 10 primitives existantes.
-> Pas de modélisation à base d'objets canoniques en n'utilisant directement que les fonctions de la librairie GL.

Exemple: Création d'un polygone

glBegin(GL_POLYGON);
glVertex2f(0.0F,0.0F) ;
glVertex2f(2.0F,1.0F) ;
glVertex2f(1.0F,3.0F) ;
glVertex2f(5.0F,0.5F) ;
glVertex2f(-.5F,2.0F) ;
glVertex2f(1.5F,2.5F) ;
glEnd() ;

Outre ses sommets, la définition d'une primitive peut requérir d'autres informations. Celles-ci sont aussi renseignées via des appels à des fonctions particulières réalisés entre glBegin et glEnd.

Fonctions utilisables entre glBegin et glEnd

Fonction

But

glVertex*()

Coordonnées d'un sommet

glColor*()

Couleur de tracé

glIndex*()

Indexe de couleur de tracé

glNormal*()

Vecteur normal à un sommet

glEvalCoord*()

Génération de coordonnées

glCallList()
glCallLists()

Exécution de listes d'affichage

glTexCoord*()

Coordonnées d'une texture

glEdgeFlag*()

Contrôle du tracé d'un coté

glMaterial*()

Propriété du matériau d'un objet

Parmi ces fonctions, l'une des plus importantes permet la configuration de la valeur de la normale courante (vecteur orthogonal à la surface et utilisé pour les calculs d'éclairage):

  • void glNormal3{bsidf}(TYPE x,TYPE y,TYPE z);

  • void glNormal3{bsidf}v(const TYPE *v);

x, y, z ou v: Coordonnées attribuées au vecteur "normal" (valeur par défaut : (0.0,0.0,1.0).

Exemple

glBegin(GL_POLYGON);
glColor3f(1.0F,0.0F,0.0F) ;
glVertex2f(0.0F,0.0F) ;
glNormal3f(-1.0F,0.0F,0.0F) ;
glVertex2f(2.0F,1.0F) ;
glColor3f(0.0F,1.0F,0.0F) ;
glVertex2f(1.0F,3.0F) ;
glEnd() ;

Définit un polygone à trois sommets.
Donne la couleur rouge aux premier et deuxième sommets, et la couleur verte au troisième.
Donne la normale (-1.0,0.0,0.0) aux deuxième et troisième sommets. Le premier adopte comme normale la valeur courante (inconnue dans l'exemple) définie avant le glBegin.

Les 10 primitives graphiques

Programme Aux | Programme GLUt
Exécutable Aux | Exécutable GLUt

Déclaration d'un ensemble de facettes

Utilisation fréquente de surfaces polygonales (ensembles de facettes adjacentes) pour modéliser des objets complexes.

Recommandations

  • Conserver constante l'orientation des polygones.
  • Eviter les facettes non triangulaires.
  • Trouver un bon équilibre entre le nombre de polygones et la vitesse et la qualité d'affichage.
  • Multiplier le nombre de polygones sur la silhouette des objets.
  • Éviter des intersections en T.

Exemple

#define X .525731112119133606
#define Z .850650808352039932
static GLfloat vdata[12][3] = {
{-X,0,Z},{X,0,Z},{-X,0,-Z},
{X,0,-Z},{0,Z,X},{0,Z,-X},
{0,-Z,X},{0,-Z,-X},{Z,X,0},
{-Z,X,0},{Z,-X,0},{-Z,-X,0}} ;
static GLint t[20][3] = {
{0,4,1},{0,9,4},{9,5,4},{4,5,8},
{4,8,1},{8,10,1},{8,3,10},{5,3,8},
{5,2,3},{2,7,3},{7,10,3},{7,6,10},
{7,11,6},{9,2,5},{0,1,6},{6,1,10},
{9,0,11},{9,11,2},{11,0,6},
{7,2,11}};
for ( i = 0 ; i < 20 ; i++ ) {
glBegin(GL_TRIANGLES) ;
glNormal3fv(&vdata[t[i][0]][0]);
glVertex3fv(&vdata[t[i][0]][0]);
glNormal3fv(&vdata[t[i][1]][0]);
glVertex3fv(&vdata[t[i][1]][0]);
glNormal3fv(&vdata[t[i][2]][0]);
glVertex3fv(&vdata[t[i][2]][0]);
glEnd() ; }

Programme Aux | Programme GLUt
Exécutable Aux | Exécutable GLUt

Fonctions basiques pour la configuration de l'affichage des primitives graphiques

  • void glPointSize(GLfloat size) ;

Configuration de la taille de tracé des sommets (par défaut 1.0 pixel)

size: Taille de tracé

  • void glLineWidth(GLfloat width) ;

Configuration de la largeur de tracé des segments de droite (par défaut 1.0 pixel)

width: Largeur de tracé

  • void glLineStipple(GLint facteur, GLushort motif) ;

Configuration du motif de tracé des lignes (par défaut continu)
Le motif est une série de 16 bits (poids faible à poids fort) où les 1 représentent des pixels allumés et des 0 des pixels non modifiés. Le facteur (de 1 à 255) permet l'étirement du motif par multiplication (un 1 devient factor 1 consécutifs).
L'utilisation des motifs pour les segments est subordonnée à autorisation via un glEnable(GL_LINE_STIPPLE).

facteur: Facteur de zoom du motif
motif: Motif de tracé

  • void glPolygonMode(GLenum face, GLenum mode) ;

Configuration de la technique de dessin des faces avant et arrières des polygones (par défaut avec remplissage)

face: Face configurée (GL_FRONT, GL_BACK ou GL_FRONT_AND_BACK)
mode: Mode d'affichage (GL_POINT, GL_LINE ou GL_FILL pour un dessin des sommets, en fil de fer ou avec remplissage)

  • void glFrontFace(GLenum mode) ;

Configuration de la technique de détermination de la face frontale des polygones
La valeur par défaut est GL_CCW (anti-horaire). Elle peut être inversée par GL_CW.

mode: GL_CCW ou GL_CW

  • void glCullFace(GLenum mode) ;

Configuration de quels polygones doivent être éliminés au cours du processus d'affichage en fonction de leur orientation vers l'observateur (par défaut tous les polygones sont affichés)
Permet, par exemple, de ne pas dessiner les polygones orientés vers le fond de la scène qui seraient donc obligatoirement masqués par d'autres objets si on ne dessine que des volumes.
L'utilisation du culling est subordonnée à autorisation via un glEnable(GL_CULL_FACE).

mode: GL_FRONT, GL_BACK ou GL_FRONT_AND_BACK

  • void glPolygonStipple(GLubyte *mask) ;

Définition du motif de remplissage des polygones (par défaut plein)
L'utilisation des motifs pour les polygones est subordonnée à autorisation via un glEnable(GL_POLYGONE_STIPPLE).

mask: Pointeur sur une bitmap de 32x32 pixels interprétée comme un masque de bits (128 octets). Si un 1 est présent, le pixel est dessiné, sinon, il ne l'est pas.

Programme Aux | Programme GLUt
Exécutable Aux | Exécutable GLUt

Suite


OpenGL - Partie 2

OpenGL (Partie 2) Vu dans ... http://raphaello.univ-fcomte.fr

Visualisation en OpenGL

Processus de visualisation

Quatre transformations utilisées au cours du processus de création d'une image:

(1) Transformation d'affichage (Viewport)

Permet de fixer la taille et la position de l'image sur la fenêtre d'affichage.

(2) Transformation de projection (Projection)

Permet de fixer les caractéristiques optiques de la caméra de visualisation (type de projection, ouverture, ...).

(3) Transformation de visualisation (View)

Permet de fixer la position et l'orientation de la caméra de visualisation.

(4) Transformation de modélisation (Model)

Permet de créer la scène à afficher par création, placement et orientation des objets qui la composent.

Les transformations de visualisation et de modélisation, de même nature, n'en forment qu'une pour OpenGL (transformation GL_MODELVIEW). Cette transformation fait partie de l'environnement OpenGL.

La transformation de projection existe en tant que telle dans OpenGL, et fait elle aussi partie de l'environnement OpenGL (transformation GL_PROJECTION).

Chacune de ces deux transformations peut être modifiée indépendamment de l'autre. Cela permet d'obtenir une indépendance des scènes modélisées vis à vis des caractéristiques de la "caméra" qui les visualise.

La transformation d'affichage est elle aussi paramétrable.

Exemple

// w et h variables globales de type int

static float rotx = 10.0f ;
static float roty = 20.0f ;
static float rotz = 30.0f ;

glViewport(0,0,w,h);                    // 1
glMatrixMode(GL_PROJECTION);            // 2
glLoadIdentity();                       // 2
glFrustum(-1.0,1.0,-1.0,1.0,1.5,20.0);  // 2
glMatrixMode(GL_MODELVIEW);             // 3-4
glLoadIdentity();                       // 3
glTranslatef(0.0,0.0,-5.0);             // 3
glRotatef(rotz,0.0,0.0,1.0);            // 3
glRotatef(rotx,1.0,0.0,0.0);            // 3
glRotatef(roty,0.0,1.0,0.0);            // 3
glScalef(1.0,2.0,3.0);                  // 4
glutWireCube(1.0);                      // 4

Programme Aux | Programme GLUt
Exécutable Aux | Exécutable GLUt

Choix de la transformation OpenGL de travail

  • void glMatrixMode(GLenum mode);

mode: Transformation sur laquelle (GL_MODELVIEW ou GL_PROJECTION) les transformations géométriques à venir vont être composées de manière incrémentale.

Pour réaliser un affichage (construction d'une image), glMatrixMode est généralement appelé successivement une fois sur chacun des deux paramètres de manière à établir les transformations géométriques (matrices) modelview et projection.
Ces appels sont habituellement réalisés au sein de la fonction reshape si les librairies Aux ou GLUt sont utilisées. Dans la fonction display de Aux ou GLUt, l'habitude veut que l'on ne travaille qu'en modelview (sauf exception, par exemple dans le cas de la programmation d'une animation de la caméra).

Transformations géométriques

Transformations d'utilité générale applicables en modelview ou en projection

  • void glLoadIdentity(void);

Affecte la transformation courante avec la transformation identité.

  • void glLoadMatrix{f d}(const TYPE *m);

Affecte la transformation courante avec la transformation caractérisée mathématiquement par la matrice m (16 valeurs en coordonnées homogènes).

  • void glMultMatrix{f d}(const TYPE *m);

Compose la transformation courante par la transformation de matrice m (16 valeurs en coordonnées homogènes).

  • void glTranslate{f d}(TYPE x,TYPE y,TYPE z);

Compose la transformation courante par la translation de vecteur (x,y,z).
Transformation très utilisée en modélisation.

  • void glRotate{f d}(TYPE a,TYPE dx,TYPE dy,TYPE dz);

Compose la transformation courante par la rotation d'angle a degrés autour de l'axe (dx,dy,dz) passant par l'origine.
Transformation très utilisée en modélisation.

  • void glScale{f d}(TYPE rx,TYPE ry,TYPE rz);

Compose la transformation courante par la transformation composition des affinités orthogonales d'axes x, y et z, de rapports respectifs rx, ry et rz selon ces axes.
Transformation très utilisée en modélisation.

Transformations de projection

  • void glFrustum(GLdouble g, GLdouble d, GLdouble b, GLdouble h, GLdouble cmin, GLdouble cmax);

Compose la transformation courante par la transformation de projection en perspective de volume de visualisation défini par la pyramide tronquée de sommet l'origine O (position de la caméra virtuelle de visualisation), orientée selon l'axe -z (orientation de la caméra virtuelle de visualisation) et de base supérieure définie par la diagonale (g,b,-cmin) (d,h,-cmin).
cmin et cmax sont les distances entre l'origine et les plans de clipping en z "near" (-cmin en z) et "far" (-cmax en z). Etant des distances, cmin et cmax doivent avoir une valeur positive. Ces valeurs doivent aussi respecter cmin < cmax. Ces valeurs étant comptées selon l'axe -z, seuls les objets placés en z négatif peuvent être visibles.

Programme GLUt

Exécutable GLUt

  • void gluPerspective(GLdouble fov, GLdouble ratio, GLdouble cmin, GLdouble cmax);

Compose la transformation courante par la transformation de projection en perspective de volume de visualisation défini par la pyramide tronquée de sommet l'origine O (position de la caméra virtuelle de visualisation), orientée selon l'axe -z (orientation de la caméra virtuelle de visualisation), possédant l'angle fov comme angle d'ouverture verticale, l'apect-ratio ratio (rapport d'ouverture largeur/hauteur) et les plans de clipping near et far en z = -cmin et z = -cmax.
cmin et cmax doivent avoir une valeur positive et respecter cmin < cmax car il s'agit de distances à l'origine (comptées selon l'axe -z).s selon l'axe -z).

Programme GLUt

Exécutable GLUt

Exemple:
gluPerspective(40.0,1.5,50.0,100.0)
Cet appel de fonction configure une caméra de visualisation en perspective placée à l'origine (c'est obligatoire) et orientée selon l'axe -z (c'est obligatoire) avec un angle d'ouverture verticale de 40.0°, un angle d'ouverture horizontale de 40.0*1.5 = 60.0°, un plan de clipping qui élimine tous les objets ou morceaux d'objet situés en z > -50.0 et un plan de clipping qui élimine tous les objets ou morceaux d'objet situés en z < -100.0.
Ces valeurs sont considérées dans le repère courant au moment de l'appel de fonction.

  • void glOrtho(GLdouble g, GLdouble d, GLdouble b, GLdouble h, GLdouble cmin, GLdouble cmax);

Compose la transformation courante par la transformation de projection orthographique selon l'axe -z et définie par le volume de visualisation parallélépipèdique (g,d,b,h,-cmin,-cmax). cmin et cmax peuvent être positifs ou négatifs mais doivent respecter cmin < cmax.

Programme GLUt

Exécutable GLUt

Exemples

Visualisation en projection parallèle
et projection en perspective

Projections orthographique et en perspective

Projection en perspective avec rapprochement de la scène

Projection en perspective: très grosses déformations

Programme Aux

Programme GLUt

Exécutable Aux

Exécutable GLUt

OpenGL est peu souple en terme de gestion des caméras:
- Elles sont obligatoirement orientées avec la direction de visualisation -z (0.0, 0.0, -1.0).
- Les caméras de projection en perspective sont obligatoirement placées à l'origine.

Il revient au développeur de placer et d'orienter la scène devant la caméra pour fournir le point de vue souhaité.
Le code est toujours structuré pour que l'ordre d'exécution soit (cf exemple tout en haut de cette page):
- Définition et paramétrage de la caméra (mode GL_PROJECTION).
- Changement de repère de placement et d'orientation de la scène devant la caméra (mode GL_MODELVIEW).
- Modélisation de la scène (mode GL_MODELVIEW).

Transformation spécifique à la visualisation (view)

  • void gluLookAt(GLdouble ex, GLdouble ey, GLdouble ez, GLdouble cx, GLdouble cy, GLdouble cz, GLdouble upx, GLdouble upy, GLdouble upz);

Compose la transformation courante (généralement GL_MODELVIEW) par la transformation donnant un point de vue depuis la position (ex,ey,ez) avec une direction de visualisation passant par la position (cx,cy,cz).
(upx,upy,upz) indique la direction du repère courant (fréquemment le repère global) qui devient la direction y (0.0, 1.0, 0.0) dans le repère écran.

gluLookAt est une fonction de la bibliothèque GLU qui permet de faciliter la résolution du problème du placement d'une caméra ailleurs qu'à l'origine.

Exemple:
gluLookAt(10.0,15.0,10.0,3.0,5.0,-2.0,0.0,1.0,0.0)
place la caméra en position (10.0,15.0,10.0),
l'oriente pour qu'elle vise le point de position (3.0,5.0,-2.0) et
visualise la direction (0.0,1.0,0.0) de telle manière qu'elle apparaît verticale dans la fenêtre de visualisation.
Ces valeurs sont considérées dans le repère courant (fréquemment le repère global).

Dans la pratique, gluLookAt place et oriente la scène devant la caméra de telle manière que la caméra semble placée et orientée selon les paramètres d'entête.
-> L'appel à gluLookAt doit être exécuté en mode GL_MODELVIEW (donc après la définition de la caméra en mode GL_PROJECTION) et doit être placé dans le code avant la création de la scène pour porter sur elle.

Transformation d'affichage

  • void glViewport(GLint x,GLint y,GLsizei l,GLsizei h);

x, y: Position du coin supérieur gauche (en pixels)
l,h: Résolution (en pixels)

Définit le rectangle de pixels de la fenêtre d'affichage dans lequel l'image calculée sera affichée.

Changement de viewport

Utilisation de toute la surface de la fenêtre d'affichage

Utilisation d'une partie seulement
de la fenêtre d'affichage

Programme Aux

Programme GLUt

Exécutable Aux

Exécutable GLUt

Problème

L'affichage des scènes fait appel à un repère virtuel associé au viewport de visualisation. Les dimensions fournies au moment de l'exécution de la fonction de projection (GL_PROJECTION) définissent ce repère qui peut ne pas être homothétique au viewport.

Exemple: glFrustum(-1.0,1.0,-1.0,1.0,1.5,20.0) associe le repère virtuel de diagonale (-1.0,-1.0) - (1.0,1.0) au viewport d'affichage. Si celui-ci n'a pas des résolutions en x et y identiques, les objets dessinés sont déformés.

Adaptation de la transformation de projection au viewport d'affichage
Programme Aux | Programme GLUt
Exécutable Aux | Exécutable GLUt

Solution: Adapter aux dimensions du viewport les dimensions données pour la définition de la projection.

Exemple: Pour un viewport défini par
glViewport(0,0,w,h)
on pourra utiliser
gluPerspective(65.,(double) w/h,1.0,20.0)
ce qui revient à adapter le ratio de gluPerspective au ratio du viewport.

Utilisation du reshape : homothétique
Programme Aux | Programme GLUt
Exécutable Aux | Exécutable GLUt

Opérations diverses liées à la modélisation et à la visualisation

  • void glPushMatrix(void);

Empile la matrice courante dans la pile de matrices.
ATTENTION, il existe deux piles, une en mode GL_MODELVIEW et une en mode GL_PROJECTION.

  • void glPopMatrix(void);

Dépile la matrice en haut de pile du mode courant et remplace la matrice courante par celle-ci.

  • void glClipPlane(GLenum plane,const GLdouble *equ);

Définit un plan de clipping 3D (découpage).

equ: tableau de quatre coefficients (a,b,c,d) qui définissent l'équation ax+by+cz+d = 0 du plan de clipping.

plane: GL_CLIP_PLANEi avec i compris entre 0 et 5.

glClipPlane(plane,equ) est à utiliser en association avec glEnable(plane) et glDisable(plane) pour autoriser/interdire la gestion du plan de clipping plane.

Découpage
Programme Aux | Programme GLUt
Exécutable Aux | Exécutable GLUt

Exemples

#include <GL/gl.h>
#include <GL/glu.h>
#include "glaux.h"

static int shoulder = 0, elbow = 0;

void elbowAdd(void) {
  elbow = (elbow + 5) % 360;
}

void elbowSubtract(void) {
  elbow = (elbow - 5) % 360;
}

void shoulderAdd(void) {
  shoulder = (shoulder + 5) % 360;
}

void shoulderSub(void) {
  shoulder = (shoulder - 5) % 360;
}

void display(void) {
  glClear(GL_COLOR_BUFFER_BIT);
  glColor3f(1.0,1.0,1.0);
  glPushMatrix();
  glTranslatef(-1.0,0.0,0.0);
  glRotatef((GLfloat) shoulder,
            0.0,0.0,1.0);
  glTranslatef(1.0,0.0,0.0);
  auxWireBox(2.0,0.4,1.0);
  glTranslatef(1.0,0.0,0.0);
  glRotatef((GLfloat) elbow,
            0.0,0.0,1.0);
  glTranslatef(1.0,0.0,0.0);
  auxWireBox(2.0,0.4,1.0);
  glPopMatrix();
  glFlush();
}

void myinit(void) {
  glShadeModel(GL_FLAT);
}

void myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(65.,(float)w/(float)h,
                 1.0,20.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef(0.0,0.0,-5.0);
}

int main(int argc,char** argv) {
  auxInitDisplayMode(AUX_SINGLE|
                     AUX_RGB|
                     AUX_DIRECT);
  auxInitPosition(0,0,400,400);
  auxInitWindow(argv[0]);
  myinit();
  auxKeyFunc(AUX_LEFT,shoulderSub);
  auxKeyFunc(AUX_RIGHT,shoulderAdd);
  auxKeyFunc(AUX_UP,elbowAdd);
  auxKeyFunc(AUX_DOWN,elbowSubtract);
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}

Programme Aux | Programme GLUt
Exécutable Aux | Exécutable GLUt

#include <GL/gl.h>
#include <GL/glu.h>
#include "glaux.h"

static int year = 0, day = 0;

void dayAdd(void) {
  day = (day + 10) % 360;
}

void daySubtract(void) {
  day = (day - 10) % 360;
}

void yearAdd(void) {
  year = (year + 5) % 360;
}

void yearSubtract(void) {
  year = (year - 5) % 360;
}

void display(void) {
  glClear(GL_COLOR_BUFFER_BIT);
  glColor3f(1.0,1.0,1.0);
  glPushMatrix();
  auxWireSphere(1.0);
  glRotatef((GLfloat) year,
            0.0,1.0,0.0);
  glTranslatef(2.0,0.0,0.0);
  glRotatef((GLfloat) day,
            0.0,1.0,0.0);
  auxWireSphere(0.2);
  glPopMatrix();
  glFlush();
}

void myinit(void) {
  glShadeModel(GL_FLAT);
}

void myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(60.0,
                 (float) w/(float) h,
                 1.0,20.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef(0.0,0.0,-5.0);
}

int main(int argc,char** argv) {
  auxInitDisplayMode(AUX_SINGLE|
                     AUX_RGB);
  auxInitPosition(0,0,500,500);
  auxInitWindow(argv[0]);
  myinit();
  auxKeyFunc(AUX_LEFT,yearSubtract);
  auxKeyFunc(AUX_RIGHT,yearAdd);
  auxKeyFunc(AUX_UP,dayAdd);
  auxKeyFunc(AUX_DOWN,daySubtract);
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}

Programme Aux | Programme GLUt
Exécutable Aux | Exécutable GLUt

Les listes d'affichage

Définition

Une liste d'affichage est une suite de commandes OpenGL stockées pour une utilisation future.

Quand une liste d'affichage est invoquée, les commandes qu'elle contient sont exécutées dans l'ordre où elles ont été stockées.

Les commandes d'une liste d'affichage sont les mêmes que les commandes d'affichage immédiat.

Exemple

Dessin d'un polygone régulier de 100 sommets.

void cercle(){
  GLint i ;
  GLfloat cosinus,sinus ;
  glBegin(GL_POLYGON) ;
  for ( i = 0 ; i < 100 ; i++ ) {
    cosinus = cos(i*2*PI/100.0);
    sinus = sin(i*2*PI/100.0);
    glVertex2f(cosinus,sinus) ; }
  glEnd() ;
}

Cette méthode sans liste d'affichage est inefficace car tout appel à cercle() entraîne l'exécution des calculs de sinus et cosinus.

#define CERCLE 1
void cercle() {
  GLint i ;
  GLfloat cosinus,sinus ;
  glNewList(CERCLE,GL_COMPILE) ;
  glBegin(GL_POLYGON) ;
  for ( i = 0 ; i < 100 ; i++ ) {
    cosinus = cos(i*2*PI/100.0);
    sinus = sin(i*2*PI/100.0);
    glVertex2f(cosinus,sinus) ; }
  glEnd() ;
  glEndList() ;
}

glCallList(CERCLE) ;

Programme Aux | Programme GLUt
Exécutable Aux | Exécutable GLUt

Fonctionnalités

Les listes d'affichages sont des macros et, à ce titre, ne peuvent pas être modifiées ou paramétrées.

Les listes d'affichage optimisent les temps de calcul et d'affichage car elles permettent la suppression de calculs redondants sur:

  • les opérations matricielles,
  • les conversions de formats d'image bitmap,
  • les calculs de lumière,
  • les calculs de propriétés des matériaux de surface,
  • les calculs de modèle d'éclairage,
  • les textures,
  • les motifs de tracé.

L'inconvénient principal des listes d'affichage est qu'elles peuvent occuper beaucoup de place en mémoire.

Syntaxe des commandes

Commandes principales

  • void glNewList(GLuint liste,GLenum mode);

Indique le début de la définition d'une liste d'affichage.

liste: entier positif unique qui identifiera la liste.

mode: GL_COMPILE ou GL_COMPILE_AND_EXECUTE suivant que l'on désire seulement stocker la liste ou la stocker et l'exécuter simultanément.

  • void glEndList(void);

Indique la fin d'une liste d'affichage.

Toutes les commandes depuis le dernier glNewList ont été placées dans la liste d'affichage (quelques exceptions).

  • void glCallList(GLuint liste);

Exécute la liste d'affichage référencée par liste.

Commandes auxiliaires

  • GLuint glGenLists(GLsizei nb);

Alloue nb listes d'affichage contiguës, non allouées auparavant.

L'entier retourné est l'indice qui donne le début de la zone libre de références.

  • GLboolean glIsList(GLuint liste);

Retourne vrai si liste est utilisé pour une liste d'affichage, faux sinon.

  • void glDeleteLists(GLuint l,GLsizei nb);

Détruit les nb listes à partir de la liste l.

Exemple

#include <GL/gl.h>
#include <GL/glu.h>
#include "glaux.h"

GLuint listName = 1;

void myinit(void) {
  glNewList(listName,GL_COMPILE);
  glColor3f(1.0,0.0,0.0);
  glBegin(GL_TRIANGLES);
  glVertex2f(0.0,0.0);
  glVertex2f(1.0,0.0);
  glVertex2f(0.0,1.0);
  glEnd();
  glTranslatef(1.5,0.0,0.0);
  glEndList();
  glShadeModel(GL_FLAT);
}

void drawLine(void) {
  glBegin(GL_LINES);
  glVertex2f(0.0,0.5);
  glVertex2f(15.0,0.5);
  glEnd();
}

void display(void) {
  GLuint i;
  glClear(GL_COLOR_BUFFER_BIT);
  glColor3f(0.0,1.0,0.0);
  for ( i = 0 ; i < 10 ; i++ )
    glCallList(listName);
  drawLine();
  glFlush();
}

void myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if ( w <= h )
    gluOrtho2D(0.,2.,
               -0.5*(float)h/(float)w,
               1.5*(float)h/(float)w);
    else
    gluOrtho2D(0.,
               2.*(float)w/(float)h,
               -.5,1.5);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

int main(int argc,char** argv) {
  auxInitDisplayMode(AUX_SINGLE|
                     AUX_RGB);
  auxInitPosition(0,0,400,50);
  auxInitWindow(argv[0]);
  myinit();
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}

Programme Aux | Programme GLUt
Exécutable Aux | Exécutable GLUt

Mode de lissage

  • void glShadeModel(GLenum mode)

mode: mode de lissage (GL_FLAT: uni, GL_SMOOTH: Gouraud)

Lissé (utilisé par défaut) Non lissé
Programme Aux | Programme GLUt
Exécutable Aux | Exécutable GLUt

Les calculs d'illumination

Les matériaux et les lumières

La couleur d'un objet dépend des pourcentages (coefficients) des lumières rouges, vertes et bleues (RVB) le touchant qu'il réfléchit par réflexions ambiante, diffuse et spéculaire et de la "lumière" qu'il "émet" intrinsèquement (pour lui-même).

Réflexions ambiante et diffuse, lumière émise -> couleur intrinsèque d'un objet

Réflexion spéculaire -> couleurs des lumières de la scène (généralement blanches, grises ou jaunes)

Modélisation de l'éclairage reçu sur un objet en un point vu par l'observateur par trois catégories de lumières:

- de la lumière ambiante provenant de réflexions multiples et donc uniformément de toutes les directions (réfléchie dans toutes les directions et donc visible de partout),
- de la lumière diffuse provenant d'une direction particulière (réfléchie dans toutes les directions et donc visible de partout),
- de la lumière spéculaire provenant d'une direction particulière (réfléchie dans une direction préférentielle et donc visible seulement si l'observateur est situé dans cette direction).

Modéliser l'éclairage d'une scène nécessite de définir toutes les lumières y existant mais aussi d'attribuer un matériel à chaque objet de la scène.

Exemple

#include <GL/gl.h>

void myinit(void) {
  GLfloat diffuse[] = { 0.9,0.9,0.9,1.0 };
  GLfloat specular[] = { 1.0,1.0,1.0,1.0 };
  GLfloat shinines[] = { 50.0 };
  GLfloat l_pos[] = { 1.0,1.0,1.0,0.0 };
  glMaterialfv(GL_FRONT,GL_DIFFUSE,diffuse);
  glMaterialfv(GL_FRONT,GL_SPECULAR,specular);
  glMaterialfv(GL_FRONT,GL_SHININESS,shinines);
  glLightfv(GL_LIGHT0,GL_POSITION,l_pos);
  glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuse);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
}

Programme Aux | Programme GLUt
Exécutable Aux | Exécutable GLUt

Définition des propriétés d'un matériau

  • void glMaterial{i f}[v](GLenum f,GLenum pn,TYPE v);

Configuration des caractéristiques d'un matériau

f: GL_FRONT, GL_BACK ou GL_FRONT_AND_BACK pour spécifier quelle face est configurée
pn: Caractéristique affectée
v: Valeurs affectées à la caractéristique pn

pn | Rôle
GL_AMBIENT | Couleur ambiante (coefficient ambiant)
GL_DIFFUSE | Couleur diffuse (coefficient de diffusion)
GL_AMBIENT_AND_DIFFUSE | Couleurs ambiante et diffuse
GL_SPECULAR | Couleur spéculaire (coefficient de réflexion)
GL_SHININESS | Focalisation spéculaire (0 -> 128)
GL_EMISSION | Couleur émise (coefficient d'émission)
GL_COLOR_INDEXES | Indexes des couleurs ambiante, diffuse et spéculaire

pn | Valeur par défaut
GL_AMBIENT | (0.2,0.2,0.2,1.0)
GL_DIFFUSE | (0.8,0.8,0.8,1.0)
GL_AMBIENT_AND_DIFFUSE |
GL_SPECULAR | (0.0,0.0,0.0,1.0)
GL_SHININESS | 0.0
GL_EMISSION | (0.0,0.0,0.0,1.0)
GL_COLOR_INDEXES | (0,1,1)

Caractéristiques possiblement différentes pour les différents objets d'une scène

Pour une même primitive graphique, utilisation des mêmes caractéristiques de matériel pour chacun de ses sommets ou bien changement d'un sommet à un autre -> dégradé de matériaux

Récupération de la configuration du matériel

  • void glGetMaterial{i f}v(GLenum f,GLenum pn,TYPE *v);

Lecture des caractéristiques du matériel courant

f: GL_FRONT ou GL_BACK pour spécifier quelle face est concernée par l'opération
pn: Caractéristique lue
v: Tableau affecté au résultat de la lecture de la caractéristique pn

Exemple

void myinit(void) {
  GLfloat no_mat[] = { 0.0F,0.0F,0.0F,1.0F };
  GLfloat mat_diffuse[] = { 0.1F,0.5F,0.8F,1.0F };
  GLfloat no_shininess[] = { 0.0F };
  glMaterialfv(GL_FRONT,GL_AMBIENT,no_mat);
  glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
  glMaterialfv(GL_FRONT,GL_SPECULAR,no_mat);
  glMaterialfv(GL_FRONT,GL_SHININESS,no_shininess);
  glMaterialfv(GL_FRONT,GL_EMISSION,no_mat);

Le programme Aux | Le programme GLUt
Exécutable Aux | Exécutable GLUt

Programme GLUt

Exécutable GLUt

Configuration de sources lumineuses

Gestion d'un nombre minimum de 8 sources lumineuses désignées par les constantes GL_LIGHT0, GL_LIGHT1, GL_LIGHT2,...

Les paramètres de ces sources doivent être renseignés.

  • void glLight{i f}[v](GLenum nb,GLenum pname,TYPE p);

Configuration d'une source lumineuse

nb: GL_LIGHT0, GL_LIGHT1, ..., GL_LIGHT7, soit au total 8 sources lumineuses possibles en VC++
pname: Caractéristique affectée
p: Valeur affectée à la caractéristique pname

pname

Rôle

GL_AMBIENT

(R,G,B,A): Energie colorée de la lumière ambiante ajoutée à la scène

GL_DIFFUSE

(R,G,B,A): Energie colorée de la lumière diffuse liée à la source lumineuse

GL_SPECULAR

(R,G,B,A): Energie colorée de la lumière spéculaire liée à la source lumineuse

GL_POSITION

(x,y,z,w): "Position" de la lumière

  • si w = 0, lumière placée à l'infini, (x,y,z) donne la direction vers la source de lumière
    -> lumière directionnelle
  • si w != 0, lumière non placée à l'infini, (x,y,z) donne la position
    -> lumière ponctuelle

GL_SPOT_DIRECTION

Orientation du cone d'ouverture dans le cas d'un spot

GL_SPOT_EXPONENT

Exposant (focalisation) d'un spot (plus l'exposant est grand plus le spot est focalisé sur son axe)

GL_SPOT_CUTOFF

Angle d'ouverture d'un spot

pname | Rôle
GL_CONSTANT_ATTENUATION | Facteur d'attenuation constant: Kc
GL_LINEAR_ATTENUATION | Facteur d'attenuation linéaire: Kl
GL_QUADRATIC_ATTENUATION | Facteur d'attenuation quadratique: Kq

Facteur d'atténuation en fonction de la distance d entre une lumière ponctuelle et un point éclairé:

pname | Valeur par défaut
GL_AMBIENT | (0.0,0.0,0.0,1.0)
GL_DIFFUSE | GL_LIGHT0:(1.0,1.0,1.0,1.0) autres:(0.0,0.0,0.0,1.0)
GL_SPECULAR | GL_LIGHT0:(1.0,1.0,1.0,1.0) autres:(0.0,0.0,0.0,1.0)
GL_POSITION | (0.0,0.0,1.0,0.0)
GL_SPOT_DIRECTION | (0.0,0.0,-1.0)
GL_SPOT_EXPONENT | 0.0
GL_SPOT_CUTOFF | 180.0
GL_CONSTANT_ATTENUATION | 1.0
GL_LINEAR_ATTENUATION | 0.0
GL_QUADRATIC_ATTENUATION | 0.0

Exemple

void myinit(void) {
  GLfloat amb[] = {0.0,0.0,0.0,1.0};
  GLfloat dif[] = {1.0,1.0,1.0,1.0};
  GLfloat spe[] = {1.0,1.0,1.0,1.0};
  GLfloat pos[] = {0.0,3.0,2.0,0.0};
  glLightfv(GL_LIGHT0,GL_AMBIENT,amb);
  glLightfv(GL_LIGHT0,GL_DIFFUSE,dif);
  glLightfv(GL_LIGHT0,GL_SPECULAR,spe);
  glLightfv(GL_LIGHT0,GL_POSITION,pos);
  ...

Programme GLUt

Exécutable GLUt

Exemples de définition de spots

Deux spots très focalisés

Ces deux spots moins focalisés

Aberrations liées à la facettisation
des objets à courbure

Le programme Aux et GLUt

Exécutable Aux et GLUt

Obtention de lumières colorées
Programme Aux | Programme GLUt et Aux
Exécutable Aux | Exécutable GLUt et Aux

Lecture de la configuration des sources lumineuses

  • void glGetLight{i f}v(GLenum nb,GLenum pname,TYPE *p);

Lecture de la configuration d'une des sources lumineuses

nb: GL_LIGHT0, GL_LIGHT1, ..., GL_LIGHT7
pname: Caractéristique lue
p: Tableau destiné à la récupération de la caractéristique pname

Autorisation des calculs d'éclairage

Les calculs d'éclairage ne sont réalisés qu'après exécution de:

  • glEnable(GL_LIGHTING);

Faute de cette autorisation, ce sont les informations de couleurs spécifiées par les fonctions glColor* qui sont prises en compte.

Chaque lumière (8 au maximum) doit être elle-aussi autorisée:

  • glEnable(GL_LIGHTi);

Ces options peuvent être annulées au moyen de glDisable.

Normalisation automatique

Les normales spécifiées sur les objets subissent les transformations géométriques GL_MODELVIEW et GL_PROJECTION.
-> Problème s'il y a des scales ou une visualisation en perspective car alors une normale peut ne plus conserver sa norme égale à 1.0. Ceci peut conduire à des erreurs dans les calculs d'illumination.

  • glEnable(GL_NORMALIZE);

Normalisation automatique systématique avant utilisation de tout vecteur connu comme étant une normale.

Choix d'un modèle d'illumination

Le modèle d'illumination OpenGL gère les trois caractéristiques suivantes:

- l'intensité de lumière ambiante globale permettant de fixer l'éclairage minimum au sein de la scène via les composantes ambiantes des matériaux,

- la position du point de vue (position de l'observateur, local ou à l'infini) utilisé pour l'évaluation des réflexions spéculaires,

- des calculs d'éclairage différents ou non pour les deux faces des facettes modélisant les objets.

  • void glLightModel{if}[v](GLenum md,TYPE v);

Configuration du modèle d'illumination

md: GL_LIGHT_MODEL_AMBIENT, GL_LIGHT_MODEL_LOCAL_VIEWER, GL_LIGHT_MODEL_TWO_SIDE

v: valeur affectée à md

Le blending (transparence)

OpenGL permet d'afficher un objet avec composition de la couleur de chacun de ses pixels avec la couleur du pixel déjà présent dans l'image à cette place -> transparence.

  • glEnable(GL_BLEND);

Activation du blending.

  • glBlendFunc(GLenum sfactor,GLenum dfactor);

Choix de l'arithmétique de composition des pixels sources et destination (sfactor pour les pixels de l'objet affiché, dfactor pour les pixels déjà présents).

Pour le blending les valeurs habituelles de sfactor et dfactor sont GL_SRC_ALPHA et GL_ONE_MINUS_SRC_ALPHA
-> on garde 1-alpha du pixel présent et on ajoute alpha du pixel calculé
-> objet non transparent avec alpha = 1
-> objet transparent avec alpha = 0.

ATTENTION: La composition entre les pixels présents et placés implique que l'ordre de dessin des objets graphiques a une influence sur l'image finale.

 | GLUt
Deux sphères transparentes |

Les bitmaps

Introduction

OpenGL permet la gestion de bitmaps en autorisant les opérations de transfert entre le buffer couleur et la mémoire centrale.

Buts

  • gestion de fichiers

  • gestion de polices de caractères (non traité)

  • gestion de textures (voir plus loin)

Commandes

  • void glReadPixels(GLint x,GLint y,GLsizei l,GLsizei h,GLenum format,GLenum type,GLvoid *pixels);

Lecture des pixels d'un frame-buffer et stockage en mémoire

x et y: position du coin supérieur gauche du rectangle de pixels

l et w: dimensions du rectangle de pixels

format: information à lire sur les pixels

type: type du résultat à fournir

pixels: tableau destiné à recevoir les résultats

format | Type de données pour les pixels
GL_COLOR_INDEX | Indexe de couleurs
GL_RGB | Couleurs RVB
GL_RGBA | Couleurs RVBA
GL_RED | Composante rouge
GL_GREEN | Composante verte
GL_BLUE | Composante bleue
GL_ALPHA | Composante alpha
GL_LUMINANCE | Luminance
GL_LUMINANCE_ALPHA | Luminance puis composante alpha
GL_STENCIL_INDEX | Stencil
GL_DEPTH_COMPONENT | Composante de profondeur

type | Type C
GL_UNSIGNED_BYTE | Entier 8 bits non signé
GL_BYTE | Entier 8 bits signé
GL_BITMAP | Bits dans des entiers 8 bits non signés
GL_UNSIGNED_SHORT | Entier 16 bits non signé
GL_SHORT | Entier 16 bits signé
GL_UNSIGNED_INT | Entier 32 bits non signé
GL_INT | Entier 32 bits signé
GL_FLOAT | Réel simple précision

  • void glDrawPixels(GLsizei l,GLsizei h,GLenum f,GLenum type,GLvoid *pixels);

Écriture dans un frame-buffer de pixels stockés en mémoire. Le rectangle de pixels est affiché avec son coin supérieur gauche en position raster courante

l et w: dimensions du rectangle de pixels

f: information à écrire sur les pixels

type: type des données transmises

pixels: tableau destiné à transmettre les pixels

  • void glRasterPos{234}{sifd}{v}(TYPE x,TYPE y,TYPE z,TYPE w);

Fixe la position raster courante

Si 2 est utilisé z est fixé à 0 et w à 1. Si c'est 3, w est fixé à 1

  • void glCopyPixels(GLint x,GLint y,GLsizei l,GLsizei h,GLenum type);

Copie depuis un buffer dans lui-même

x et y: position du coin supérieur gauche du rectangle source (la position du rectangle destination donnée par la position raster courante)

l et w: dimensions du rectangle source

type: buffer sur lequel agir (GL_COLOR, GL_STENCIL ou GL_DEPTH)

Exemple: Sauvegarde d'une image

void sauve(char *nom,int px,
           int py,int dx,int dy){
  GLubyte *im ;
  FILE *num ;
  int y,x,t = dx*dy*3 ;
  im =(GLubyte *) calloc(t,
                    sizeof(GLubyte));
  glReadPixels(px,py,dx,dy,GL_RGB,
               GL_UNSIGNED_BYTE,im);
  num = fopen(nom,"wb") ;
  if ( num ) {
    for ( y = dy-1 ; y >= 0 ; y-- ) {
      for ( x = 0 ; x < dx ; x++ ) {
        fwrite((void *)&im[(x+y*dx)*3],
               sizeof(GLubyte),
               3,num) ; } }
  fclose(num) ; }
  free(im) ;
}

Ce programme lit une portion rectangulaire (px, py, px+dx, py+dy) de la fenêtre d'affichage et la stocke dans un tableau.

Ce tableau est ensuite sauvegardé dans un fichier.

Programme Aux | Programme GLUt
Exécutable Aux | Exécutable GLUt

Suite


OpenGL - Partie 3

OpenGL (Partie 3) Vu dans ... http://raphaello.univ-fcomte.fr

Placage de texture

Introduction

Les textures sont utilisables en OpenGL. Elles permettent d'éviter de rendre les objets trop complexes par facettisation.

Etapes de l'utilisation d'une texture:

(1) spécification de la texture,

(2) spécification de la technique à utiliser pour appliquer la texture à chaque pixel,

(3) autorisation du plaquage,

(4) dessin de la scène qui sera implicitement texturée au moyen des paramètres définis en (1) et (2).

Exemple

Programme Aux | Programme GLUt
Exécutable Aux | Exécutable GLUt

#include <GL/gl.h>
#include <GL/glu.h>
#include "glaux.h"

#define LI 64
#define LH 64
GLubyte image[LI][LH][3];

void makeImage(void) {
  int i,j,r,c;
  for( i = 0 ; i < LI ; i++ ) {
    for( j = 0 ; j < LH ; j++ ) {
      c = (((i&0x8)==0)^
           ((j&0x8)==0))*255;
      image[i][j][0] =(GLubyte) c;
      image[i][j][1] =(GLubyte) c;
      image[i][j][2] =(GLubyte) c; } }
}

void myinit(void) {
  glClearColor(0.0,0.0,0.0,0.0);
  glEnable(GL_DEPTH_TEST);
  glDepthFunc(GL_LESS);
  makeImage();
  glPixelStorei(GL_UNPACK_ALIGNMENT,1);
  glTexImage2D(GL_TEXTURE_2D,0,3,
               LI,LH,0,GL_RGB,
               GL_UNSIGNED_BYTE,
               &image[0][0][0]);
  glTexParameterf(GL_TEXTURE_2D,
                  GL_TEXTURE_WRAP_S,
                  GL_CLAMP);
  glTexParameterf(GL_TEXTURE_2D,
                  GL_TEXTURE_WRAP_T,
                  GL_CLAMP);
  glTexParameterf(GL_TEXTURE_2D,
                 GL_TEXTURE_MAG_FILTER,
                 GL_NEAREST);
  glTexParameterf(GL_TEXTURE_2D,
                 GL_TEXTURE_MIN_FILTER,
                 GL_NEAREST);
  glTexEnvf(GL_TEXTURE_ENV,
            GL_TEXTURE_ENV_MODE,
            GL_DECAL);
  glEnable(GL_TEXTURE_2D);
  glShadeModel(GL_FLAT);
}

void display(void) {
  glClear(GL_COLOR_BUFFER_BIT|
          GL_DEPTH_BUFFER_BIT);
  glBegin(GL_QUADS);
  glTexCoord2f(0.0,0.0);
  glVertex3f(-2.0,-1.0,0.0);
  glTexCoord2f(0.0,1.0);
  glVertex3f(-2.0,1.0,0.0);
  glTexCoord2f(1.0,1.0);
  glVertex3f(0.0,1.0,0.0);
  glTexCoord2f(1.0,0.0);
  glVertex3f(0.0,-1.0,0.0);
  glTexCoord2f(0.0,0.0);
  glVertex3f(1.0,-1.0,0.0);
  glTexCoord2f(0.0,1.0);
  glVertex3f(1.0,1.0,0.0);
  glTexCoord2f(1.0,1.0);
  glVertex3f(2.41421,1.0,-1.41421);
  glTexCoord2f(1.0,0.0);
  glVertex3f(2.41421,-1.0,-1.41421);
  glEnd();
  glFlush();
}

void myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(60.0,
                 1.0*w/(float)h,
                 1.0,30.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef(0.0,0.0,-3.6);
}

int main(int argc,char** argv) {
  auxInitDisplayMode(AUX_SINGLE|
                     AUX_RGB|
                     AUX_DEPTH);
  auxInitPosition(0,0,500,500);
  auxInitWindow(argv[0]);
  myinit();
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}

Commandes

(1) Spécification de la texture

  • void glTexImage2D(GLenum target,GLint level,GLint component,GLsizei l,GLsizei h,GLint b,GLenum format,GLenum type,const GLvoid *pix);

Définit une texture 2D.

target: GL_TEXTURE_2D

level: 0 pour une texture à 1 niveau

component: nombre de composantes à gérer

1 -> rouge
2 -> R et A
3 -> R, V et B
4 -> R, V, B et A

l et h: dimension de l'image représentant la texture. l et h sont obligatoirement des puissances de 2.

b: largeur du bord (usuellement 0). Si b <> 0 alors l et h sont incrémentés de 2xb.

format et type: format et type de données de l'image (voir glReadPixels)

pix: image à placer stockée dans un tableau

  • void glTexImage1D(GLenum target,GLint level,GLint component,GLsizei l,GLint b,GLenum format,GLenum type,const GLvoid *pix);

Définit une texture 1D.

target: GL_TEXTURE_1D

...

(2) Spécification des techniques de placage

Echantillonnage

Après transformation en coordonnées écran, les pixels de la texture (texels) correspondent rarement à des pixels de l'image (un pixel = un morceau de texel -> agrandissement, un pixel = plusieurs texels -> réduction)
-> Visualisation claire des texels s'ils sont trop grands à l'écran
-> non visualisation des texels existant entre deux pixels s'ils sont trop petits.

OpenGL propose le choix de la ou des techniques à employer pour effectuer les calculs d'"échantillonnage" nécessaire à la détermination de la couleur d'affichage dans les cas d'agrandissement (resp. réduction) quand moins (resp. plus) d'un texel correspond à un pixel de l'image.

  • void glTexParameter{if}{v}(GLenum target,GLenum pn, TYPE param);

Configuration du placage de texture

target: GL_TEXTURE_2D ou GL_TEXTURE_1D

pn: paramètre sur lequel est effectuée la configuration

param: valeur adoptée

pn | param
GL_TEXTURE_MAG_FILTER | GL_NEAREST GL_LINEAR
GL_TEXTURE_MIN_FILTER | GL_NEAREST GL_LINEAR ...

GL_TEXTURE_MAG_FILTER: méthode d'agrandissement

GL_TEXTURE_MIN_FILTER: méthode de réduction

GL_NEAREST: choix du texel le plus proche du centre du pixel considéré et utilisation de sa couleur
-> plus grande rapidité mais affichage de moins bonne qualité

GL_LINEAR: choix et moyennage pondéré du carré de 2x2 texels le plus proche du centre du pixel
-> rapidité moins importante mais affichage de meilleure qualité

Répétition et seuillage de texture

Les coordonnées textures sont définies dans les images sources entre les coordonnées 0 et 1. Au delà elles peuvent être répétées ou seuillées.

Répétition: toute coordonnée supérieure à 1 ou inférieure à 0 voit sa partie entière ignorée.

Seuillage: toute coordonnée supérieure à 1 (resp. inférieure à 0) est ramenée à 1 (resp. à 0).

  • void glTexParameter{if}{v}(GLenum target,GLenum pn, TYPE param);

Configuration du placage de texture

target: GL_TEXTURE_2D ou GL_TEXTURE_1D

pn: paramètre sur lequel est effectuée la configuration

param: valeur adoptée

pn | param
GL_TEXTURE_WRAP_S | GL_CLAMP GL_REPEAT
GL_TEXTURE_WRAP_T | GL_CLAMP GL_REPEAT
GL_TEXTURE_BORDER_COLOR | Quatre valeurs entre 0 et 1

- GL_CLAMP: seuillage

- GL_REPEAT: répétition

Programme Aux | Programme GLUt
Exécutable Aux | Exécutable GLUt

(3) Autorisation du placage

  • glEnable(int mode);

mode: GL_TEXTURE_2D ou GL_TEXTURE_1D

(4) Coordonnées de la texture

Pour chaque sommet définissant un objet, les coordonnées lui correspondant dans la texture doivent être précisées. Les coordonnées dans la texture des pixels à l'intérieur des facettes sont obtenues par interpolation entre ces valeurs.

1, 2, 3 ou 4 coordonnées (s,t,r,q) suivant le type de texture utilisée:

  • s affecté pour les textures 1D (t et r à 0, q à 1)
  • s et t affectés pour les textures 2D (r à 0, q à 1)
  • s, t et r affectés pour les textures 3D (q à 1)

q est l'équivalent de w en coordonnées homogènes.

  • void glTexCoord{1234}{sifd}{v}(TYPE coords);

Configuration d'une position de sommet dans une texture

coords: coordonnées dans la texture

1: remplit s, t=0, r=0, q=1
2: remplit s et t, r=0, q=1
3: remplit s, t et r, q=1
4: remplit s,t,r et q

glTexCoord s'emploie comme glNormal et permet de fixer la position dans une texture du prochain sommet défini.

Programme Aux | Programme GLUt
Exécutable Aux | Exécutable GLUt

Depuis OpenGL 1.2, il est possible d'employer des textures 3D. Le mode opératoire et les fonctions correspondantes sont une simple généralisation des versions 1D et 2D.

Le programme GLUt

Exécutable GLUt

Génération automatique des coordonnées de texture

OpenGL permet, dans une certaine mesure, de générer automatiquement les coordonnées de texture.

Les courbes et surfaces lissées

Introduction

Modélisation et rendu de B-Splines et de surfaces basées sur les courbes de Bézier.

"évaluateurs" de courbe ou de surface de Bézier
-> spécification des points de contrôle pour une courbe ou une surface.

Pour les surfaces, calcul automatique possible des normales utilisées pour les éclairages.

Affichage réalisé par subdivision en facettes
-> précision aussi importante que désirée.

Interface pour l'utilisation des NURBS dans la librairie GLU.

Interface pour l'utilisation de certaines quadriques dans la librairie GLU.

Exemples

#include <GL/gl.h>
#include <GL/glu.h>
#include "glaux.h"

GLfloat points[4][3] = {
  {-4.0,-4.0,0.0},{-2.0,4.0,0.0},
  { 2.0,-4.0,0.0},{ 4.0,4.0,0.0}};

void display(void) {
  int i;
  glClear(GL_COLOR_BUFFER_BIT);
  glColor3f(1.0,1.0,1.0);
  glBegin(GL_LINE_STRIP);
  for( i = 0 ; i <= 30 ; i++ )
    glEvalCoord1f((GLfloat) i/30.0);
  glEnd();
  glPointSize(5.0);
  glColor3f(1.0,1.0,0.0);
  glBegin(GL_POINTS);
  for( i = 0 ; i < 4 ; i++ )
    glVertex3fv(&points[i][0]);
  glEnd();
  glFlush();
}

void myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if( w <= h )
    glOrtho(-5.,5.,-5.*h/w,5.*h/w,
            -5.,5.);
    else
    glOrtho(-5.*w/h,5.*w/h,-5.,5.,
            -5.,5.);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void myinit(void) {
  glClearColor(0.0,0.0,0.0,1.0);
  glMap1f(GL_MAP1_VERTEX_3,0.0,1.0,3,4,
          &points[0][0]);
  glEnable(GL_MAP1_VERTEX_3);
  glShadeModel(GL_FLAT);
}

int main(int argc,char** argv) {
  auxInitDisplayMode(AUX_SINGLE|
                     AUX_RGB);
  auxInitPosition(0,0,500,500);
  auxInitWindow(argv[0]);
  myinit();
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}

Programme Aux | Programme GLUt
Exécutable Aux | Exécutable GLUt

#include <GL/gl.h>
#include <GL/glu.h>
#include "glaux.h"

GLfloat pts[4][4][3] = {
  { {-1.5,-1.5,4.0},{-0.5,-1.5,2.0},
    {0.5,-1.5,-1.0},{1.5,-1.5,2.0} },
  { {-1.5,-0.5,1.0},{-0.5,-0.5,3.0},
    {0.5,-0.5,0.0},{1.5,-0.5,-1.0} },
  { {-1.5,0.5,4.0},{-0.5,0.5,0.0},
    {0.5,0.5,3.0},{1.5,0.5,4.0} },
  { {-1.5,1.5,-2.0},{-0.5,1.5,-2.0},
    {0.5,1.5,0.0},{1.5,1.5,-1.0} } };

void display(void) {
  int i,j;
  glClear(GL_COLOR_BUFFER_BIT|
          GL_DEPTH_BUFFER_BIT);
  glColor3f(1.0,1.0,1.0);
  glPushMatrix();
  glRotatef(85.0,1.0,1.0,1.0);
  for( j = 0 ; j <= 8 ; j++ ) {
    glBegin(GL_LINE_STRIP);
    for( i = 0 ; i <= 30 ; i++ )
      glEvalCoord2f((float)i/30.0,
                    (float)j/8.0);
    glEnd();
    glBegin(GL_LINE_STRIP);
    for(i = 0; i <= 30; i++)
      glEvalCoord2f((float)j/8.0,
                    (float)i/30.0);
    glEnd(); }
  glPopMatrix();
  glFlush();
}

void myinit(void) {
  glClearColor(0.0,0.0,0.0,1.0);
  glMap2f(GL_MAP2_VERTEX_3,
          0,1,3,4,
          0,1,12,4,
          &pts[0][0][0]);
  glEnable(GL_MAP2_VERTEX_3);
  glMapGrid2f(20,0.0,1.0,20,0.0,1.0);
  glEnable(GL_DEPTH_TEST);
  glShadeModel(GL_FLAT);
}

void myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if ( w <= h )
    glOrtho(-4.0,4.0,
            -4.0*(float)h/(float)w,
             4.0*(float)h/(float)w,
            -4.0,4.0);
    else
    glOrtho(-4.0*(float)w/(float)h,
             4.0*(float)w/(float)h,
            -4.0,4.0,-4.0,4.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

int main(int argc,char** argv) {
  auxInitDisplayMode(AUX_SINGLE|
                     AUX_RGB|
                     AUX_DEPTH);
  auxInitPosition(0,0,500,500);
  auxInitWindow(argv[0]);
  myinit();
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}

Programme Aux | Programme GLUt
Exécutable Aux | Exécutable GLUt

Commandes

Courbes

  • void glMap1{fd}(GLenum target,TYPE u1,TYPE u2,GLint stride,GLint ordre,const TYPE *p);

Définit un évaluateur 1D.

target: ce que représentent les points de contrôle (voir ci-dessous)

u1,u2: intervalle de la variable u définissant la courbe (généralement de 0 à 1)

stride: nombre de valeurs simple ou double précision par point de contrôle dans le tableau p

ordre: degré + 1 (nombre de points de contrôle)

p: tableau des points de contrôle

target | Valeurs possibles
GL_MAP1_VERTEX_3 | coordonnées x, y et z
GL_MAP1_VERTEX_4 | coordonnées x, y, z et w
GL_MAP1_INDEX | indexe de couleurs
GL_MAP1_COLOR_4 | R, V, B et A
GL_MAP1_NORMAL | coordonnées de normales
GL_MAP1_TEXTURE_COORD_1 | coordonnée s de texture
GL_MAP1_TEXTURE_COORD_2 | coord. s et t de texture
GL_MAP1_TEXTURE_COORD_3 | coord. s, t et r de texture
GL_MAP1_TEXTURE_COORD_4 | coord. s, t, r et q de texture

  • void glEvalCoord1{fd}{v}(TYPE u);

Evalue l'évaluateur de la courbe activée.

u: valeur du paramètre d'évaluation

Surfaces

  • void glMap2{fd}(GLenum target,TYPE u1,TYPE u2,GLint us,GLint uo,TYPE v1,TYPE v2,GLint vs,GLint vo,const TYPE *p);

Définit un évaluateur 2D.

target: ce que représentent les points de contrôle

u1,u2 et v1,v2: intervalles des variables u et v

us et vs: nombres de nombres simple ou double précision par bloc suivant u et v

uo et vo: degré + 1 en u et en v

p: tableau des points de contrôle

  • void glEvalCoord2{fd}{v}(TYPE u,TYPE V);

Evalue l'évaluateur de la surface 2D activée.

u,v: valeurs des paramètres d'évaluation

Evaluation globale

OpenGL propose un mécanisme permettant l'utilisation automatique d'un évaluateur 1D sur l'ensemble d'un intervalle particulier avec n points générés.

Pour un évaluateur 2D, on fournit deux intervalles avec deux nombres de points à générer.

-> génèration d'une suite de points, de segments ou de polygones pouvant définir une primitive graphique.

  • void glMapGrid1{fd}(GLint n,TYPE u1,TYPE u2);

Définit une grille d'évaluation sur l'intervalle [u1,u2] avec n pas répartis régulièrement entre ces bornes.

  • void glEvalMesh1(GLenum mode,GLint p1,GLint p2);

Applique la grille d'évaluation courante à tous les évaluateurs 1D activés.

mode: GL_POINT ou GL_LINE pour dessiner des points ou une ligne polygonale lissant les points de contrôle

p1, p2: indices de début et de fin des points à évaluer

  • void glMapGrid2{fd}(GLint nu,TYPE u1,TYPE u2,GLint nv,TYPE v1,TYPE v2);

  • void glEvalMesh2(GLenum mode,GLint p1,GLint p2,GLint q1,GLint q2);

Définition identique à la version 1D sauf pour mode: GL_POINT, GL_LINE ou GL_FILL (remplissage de la surface)

Autres exemples

GLfloat pts[4][4][3] = {
  { {-1.5,-1.5,4.0},{-0.5,-1.5,2.0},
    {0.5,-1.5,-1.0},{1.5,-1.5,2.0}},
  { {-1.5,-0.5,1.0},{-0.5,-0.5,3.0},
    {0.5,-0.5,0.0},{1.5,-0.5,-1.0}},
  { {-1.5,0.5,4.0},{-0.5,0.5,0.0},
    {0.5,0.5,3.0},{1.5,0.5,4.0}},
  { {-1.5,1.5,-2.0},{-0.5,1.5,-2.0},
    {0.5,1.5,0.0},{1.5,1.5,-1.0}}};

void initlights(void) {
  GLfloat amb[] = {0.2,0.2,0.2,1.0};
  GLfloat pos[] = {0.0,0.0,2.0,1.0};
  GLfloat dif[] = {0.6,0.6,0.6,1.0};
  GLfloat spec[] = {1.0,1.0,1.0,1.0};
  GLfloat shininess[] = {50.0};
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glLightfv(GL_LIGHT0,GL_AMBIENT,
            amb);
  glLightfv(GL_LIGHT0,GL_POSITION,
            pos);
  glMaterialfv(GL_FRONT,GL_DIFFUSE,
            dif);
  glMaterialfv(GL_FRONT,GL_SPECULAR,
               spec);
  glMaterialfv(GL_FRONT,GL_SHININESS,
               shininess);
}

void display(void) {
  glClear(GL_COLOR_BUFFER_BIT|
          GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  glRotatef(85.0,1.0,1.0,1.0);
  glEvalMesh2(GL_FILL,0,20,0,20);
  glPopMatrix();
  glFlush();
}

void myinit(void) {
  glClearColor(0.0,0.0,0.0,1.0);
  glEnable(GL_DEPTH_TEST);
  glMap2f(GL_MAP2_VERTEX_3,0,1,
          3,4,0,1,12,4,&pts[0][0][0]);
  glEnable(GL_MAP2_VERTEX_3);
  glEnable(GL_AUTO_NORMAL);
  glEnable(GL_NORMALIZE);
  glMapGrid2f(20,0.0,1.0,20,0.0,1.0);
  initlights();
}

Programme Aux | Programme GLUt
Exécutable Aux | Exécutable GLUt

#define iw 64
#define ih 64
GLubyte im[3*iw*ih];
GLfloat pts[4][4][3] = {
  { {-1.5,-1.5,4.0},{-0.5,-1.5,2.0},
    {0.5,-1.5,-1.0},{1.5,-1.5,2.0}},
  { {-1.5,-0.5,1.0},{-0.5,-0.5,3.0},
    {0.5,-0.5,0.0},{1.5,-0.5,-1.0}},
  { {-1.5,0.5,4.0},{-0.5,0.5,0.0},
    {0.5,0.5,3.0},{1.5,0.5,4.0}},
  { {-1.5,1.5,-2.0},{-0.5,1.5,-2.0},
    {0.5,1.5,0.0},{1.5,1.5,-1.0}}};
GLfloat texpts[2][2][2] = {
  {{0.0,0.0},{0.0,1.0}},
  {{1.0,0.0},{1.0,1.0}}};

void display(void) {
  glClear(GL_COLOR_BUFFER_BIT|
          GL_DEPTH_BUFFER_BIT);
  glColor3f(1.0,1.0,1.0);
  glEvalMesh2(GL_FILL,0,20,0,20);
  glFlush();
}

void makeImage(void) {
  int i,j;
  float ti,tj;
  for( i = 0 ; i < iw ; i++ ) {
    ti = 2.0*3.14159265*i/iw;
    for( j = 0 ; j < ih ; j++ ) {
      tj = 2.0*3.14159265*j/ih;
      im[3*(ih*i+j)]=127*(1+sin(ti));
      im[3*(ih*i+j)+1]=127*
                       (1+cos(2*tj));
      im[3*(ih*i+j)+2]=127*
                       (1+cos(ti+tj));
    }
  }
}

void myinit(void) {
  glMap2f(GL_MAP2_VERTEX_3,
          0,1,3,4,0,1,12,4,
          &pts[0][0][0]);
  glMap2f(GL_MAP2_TEXTURE_COORD_2,
          0,1,2,2,0,1,4,2,
          &texpts[0][0][0]);
  glEnable(GL_MAP2_TEXTURE_COORD_2);
  glEnable(GL_MAP2_VERTEX_3);
  glMapGrid2f(20,0.0,1.0,20,0.0,1.0);
  makeImage();
  glTexEnvf(GL_TEXTURE_ENV,
            GL_TEXTURE_ENV_MODE,
            GL_DECAL);
  glTexParameterf(GL_TEXTURE_2D,
                  GL_TEXTURE_WRAP_S,
                  GL_REPEAT);
  glTexParameterf(GL_TEXTURE_2D,
                  GL_TEXTURE_WRAP_T,
                  GL_REPEAT);
  glTexParameterf(GL_TEXTURE_2D,
                 GL_TEXTURE_MAG_FILTER,
                 GL_NEAREST);
  glTexParameterf(GL_TEXTURE_2D,
                 GL_TEXTURE_MIN_FILTER,
                 GL_NEAREST);
  glTexImage2D(GL_TEXTURE_2D,0,3,
               iw,ih,0,
               GL_RGB,
               GL_UNSIGNED_BYTE,
               im);
  glEnable(GL_TEXTURE_2D);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_NORMALIZE);
  glShadeModel(GL_FLAT);
}

Programme Aux | Programme GLUt
Exécutable Aux | Exécutable GLUt

L'interface GLU pour les NURBS

Introduction

La bibliothèque GLU propose un support pour les courbes et les surfaces NURBS.

Programme Aux | Programme GLUt
Exécutable Aux | Exécutable GLUt

Pour l'affichage, ces courbes et ces surfaces sont discrétisées en lignes et polygones.

Exemple

#include <GL/gl.h>
#include <GL/glu.h>
#include <stdlib.h>
#include "aux.h"

GLfloat ctlpts[4][4][3];
GLUnurbsObj *theNurb;

void display(void) {
  GLfloat knots[8] = {0.0,0.0,0.0,0.0,
                      1.0,1.0,1.0,1.0};
  glClear(GL_COLOR_BUFFER_BIT|
          GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  glRotatef(330.0,1.,0.,0.);
  glScalef(0.5,0.5,0.5);
  gluBeginSurface(theNurb);
  gluNurbsSurface(theNurb,8,knots,
                  8,knots,4*3,3,
                  &ctlpts[0][0][0],4,4,
                  GL_MAP2_VERTEX_3);
  gluEndSurface(theNurb);
  glPopMatrix();
  glFlush();
}

void init_surface(void) {
  int u,v;
  for( u = 0 ; u < 4 ; u++ ) {
    for( v = 0 ; v < 4 ; v++ ) {
      ctlpts[u][v][0] = 2.*(u-1.5);
      ctlpts[u][v][1] = 2.*(v-1.5);
      if ((u==1 || u==2)&&
          (v==1 || v==2))
        ctlpts[u][v][2] = 3.0;
        else
        ctlpts[u][v][2] = -3.0; } }
  }

void myinit(void) {
  GLfloat dif[] = {0.7,0.7,0.7,1.0};
  GLfloat spe[] = {1.0,1.0,1.0,1.0};
  GLfloat shininess[] = {100.0};
  glClearColor(0.0,0.0,0.0,1.0);
  glMaterialfv(GL_FRONT,GL_DIFFUSE,
               dif;
  glMaterialfv(GL_FRONT,GL_SPECULAR,
               spe);
  glMaterialfv(GL_FRONT,GL_SHININESS,
               shininess);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glDepthFunc(GL_LESS);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_AUTO_NORMAL);
  glEnable(GL_NORMALIZE);
  init_surface();
  theNurb = gluNewNurbsRenderer();
  gluNurbsProperty(theNurb,
                GLU_SAMPLING_TOLERANCE,
                25.0);
  gluNurbsProperty(theNurb,
                   GLU_DISPLAY_MODE,
                   GLU_FILL);
}

void myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(45.0,
                (float)w/(float)h,
                 3.0,8.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef(0.0,0.0,-5.0);
}

int main(int argc,char** argv) {
  auxInitDisplayMode(AUX_SINGLE|
                     AUX_RGB);
  auxInitPosition(0,0,500,500);
  auxInitWindow(argv[0]);
  myinit();
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}

Commandes

  • GLUnurbsObj *gluNewNurbsRenderer(void);

Crée un nouvel objet NURBS repéré par le pointeur GLUnurbsObj* retourné.

  • void gluNurbsProperty(GLUnurbsObj *obj,GLenum attrb, GLfloat v);

Contrôle les attributs de la NURBS obj.

attrb: GLU_SAMPLING_TOLERANCE, GLU_AUTO_LOAD_MATRIX, GLU_CULLING ou GLU_DISPLAY_MODE

v: valeur à affecter à l'attribut

attrb

Rôle

GLU_SAMPLING_TOLERANCE

Valeur maximale de longueur d'un segment de ligne ou de polygone

GLU_AUTO_LOAD_MATRIX

non renseigné

GLU_DISPLAY_MODE

Rendu filaire ou en polygones pleins:

  • GLU_FILL
  • GLU_OUTLINE
    _POLYGONE
  • GLU_OUTLINE
    _PATCH

GLU_CULLING

Amélioration les performances par non affichage des NURBS en dehors du volume visualisé.

  • void gluBeginSurface(GLUnurbsObj *obj);

Commence la création de la surface NURBS obj.

  • void gluNurbsSurface(GLUnurbsObj *obj,GLint ukc, GLfloat *ukn,GLint vkc, GLfloat *vkn,GLint us, GLint vs,GLfloat *ctl,GLint uo,GLint vo,GLenum type);

Décrit les sommets (ou normales ou coordonnées de texture) de la NURBS obj.

ukc et vkc: non renseigné

ukn et vkn: non renseigné

us: nombre de flottants entre deux points de contrôle suivant la direction paramétrique u

vs: nombre de flottants entre deux points de contrôle suivant la direction paramétrique v

ctl: tableau des points de contrôle

uo et vo: ordres de la NURBS suivant u et v (degré + 1) (nombre de sommets)

type: GL_MAP2_VERTEX_3 ou GL_MAP2_VERTEX_4 pour des points de contrôle à trois coordonnées ou à quatre.

  • void gluEndSurface(GLUnurbsObj *obj);

Finit la définition de la surface NURBS obj.

L'interface GLU pour les quadriques

Introduction

La bibliothèque GLU propose un support pour certaines surfaces quadriques.

Programme GLUt

Exécutable GLUt

La sélection d'objets

Introduction

OpenGL propose un mécanisme permettant la sélection d'objets à l'écran.

Cette sélection peut être interactive si la zone de sélection est petite et proche de la souris.

Mode opératoire

- Dessin de la scène

- Entrée en mode "sélection"

- Second dessin de la scène (tant que l'on est en mode sélection, le frame buffer ne change pas)

- Sortie du mode sélection

- OpenGL retourne la liste des primitives qui ont intersectées le volume de visualisation au cours du second dessin.

Gestion de la liste de primitives

Le résultat d'une sélection est un tableau de "noms" entiers et de données associées. Chaque nom correspond à une ou plusieurs primitives de dessin.

Chaque primitive qui coupe la zone de visualisation génère un flag de sélection.

Le tableau de "noms" subit une mise à jour réalisant une sauvegarde de la "pile de noms" dans le tableau.

Le tableau retourné est utilisé pour déterminer quelles primitives ont été sélectionnées.

Commandes

Initialisation

  • void glSelectBuffer(GLsizei taille,GLuint *buff);

Définit le tableau à utiliser pour le retour des données sélectionnées.

taille: nombre maximum de données mémorisées

buff: tableau d'entiers non signés

  • GLint glRenderMode(GLenum mode);

Passe l'application en mode rendu, sélection ou feedback

mode: GL_RENDER, GL_SELECT ou GL_FEEDBACK

Valeur retournée: nombre d'objets sélectionnés si on demande GL_SELECT alors que l'on est déjà en mode sélection.

Création de la pile de noms

  • void glInitNames(void);

Vide la pile de noms.

  • void glPushName(GLuint nom);

Empile nom sur la pile de noms.

  • void glPopName(void);

Dépile un nom de la pile de noms.

  • void glLoadName(GLuint nom);

Remplace la valeur en haut de la pile de noms par nom.

Exploitation de la pile de noms

Le tableau retourné permet l'interprétation des flags de sélection.

Chaque flag a généré dans ce tableau 4 enregistrements:

- le nombre de noms dans la pile quand le flag intervient,

- le minimum et le maximum des coordonnées écran z des sommets des primitives qui ont intersectée le volume de visualisation depuis le dernier flag (valeurs comprises entre 0 et 1 multipliées par 232-1).

- le contenu de la pile de nom au moment du flag.

Exemple

Programme Aux | Programme GLUt
Exécutable Aux | Exécutable GLUt

#include <GL/gl.h>
#include <GL/glu.h>
#include <stdlib.h>
#include "aux.h"

#define BUFSIZE 512

void drawTriangle(GLfloat x1,
                  GLfloat y1,
                  GLfloat x2,
                  GLfloat y2,
                  GLfloat x3,
                  GLfloat y3,
                  GLfloat z) {
  glBegin(GL_TRIANGLES);
  glVertex3f(x1,y1,z);
  glVertex3f(x2,y2,z);
  glVertex3f(x3,y3,z);
  glEnd();
}

void drawViewVol(GLfloat x1,
                 GLfloat x2,
                 GLfloat y1,
                 GLfloat y2,
                 GLfloat z1,
                 GLfloat z2) {
  glColor3f(1.0,1.0,1.0);
  glBegin(GL_LINE_LOOP);
  glVertex3f(x1,y1,-z1);
  glVertex3f(x2,y1,-z1);
  glVertex3f(x2,y2,-z1);
  glVertex3f(x1,y2,-z1);
  glEnd();
  glBegin(GL_LINE_LOOP);
  glVertex3f(x1,y1,-z2);
  glVertex3f(x2,y1,-z2);
  glVertex3f(x2,y2,-z2);
  glVertex3f(x1,y2,-z2);
  glEnd();
  glBegin(GL_LINES);
  glVertex3f(x1,y1,-z1);
  glVertex3f(x1,y1,-z2);
  glVertex3f(x1,y2,-z1);
  glVertex3f(x1,y2,-z2);
  glVertex3f(x2,y1,-z1);
  glVertex3f(x2,y1,-z2);
  glVertex3f(x2,y2,-z1);
  glVertex3f(x2,y2,-z2);
  glEnd();
}

void drawScene(void) {
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(40.0,1.333,
                 0.01,100.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluLookAt(7.5,7.5,12.5,
            2.5,2.5,-5.0,
            0.0,1.0,0.0);
  glColor3f(0.0,1.0,0.0);
  drawTriangle(2.0,2.0,3.0,
               2.0,2.5,3.0,-5.0);
  glColor3f(1.0,0.0,0.0);
  drawTriangle(2.0,7.0,3.0,
               7.0,2.5,8.0,-5.0);
  glColor3f(1.0,1.0,0.0);
  drawTriangle(2.0,2.0,3.0,
               2.0,2.5,3.0,0.0);
  drawTriangle(2.0,2.0,3.0,
               2.0,2.5,3.0,-10.);
  drawViewVol(0.0,5.0,0.0,
              5.0,0.0,10.0);
}

void processHits(GLint hits,
                 GLuint buffer[]) {
  unsigned int i,j;
  GLuint names,*ptr;
  printf("hits = %d\n",hits);
  ptr =(GLuint *) buffer;
  for ( i = 0 ; i < hits ; i++ ) {
    names = *ptr;
    printf("nb of names=%d\n",names);
    ptr++;
    printf(" z1 is %u;",*ptr);
    ptr++;
    printf(" z2 is %u\n",*ptr);
    ptr++;
    printf(" the name is ");
    for( j = 0 ; j < names ; j++ )
      printf("%d ",*ptr);
    ptr++;
    printf("\n"); }
}

void myinit(void) {
  glDepthFunc(GL_LESS);
  glEnable(GL_DEPTH_TEST);
  glShadeModel(GL_FLAT);
}

void selectObjects(void) {
  GLuint selectBuf[BUFSIZE];
  GLint hits,viewport[4];
  glSelectBuffer(BUFSIZE,selectBuf);
  glRenderMode(GL_SELECT);
  glInitNames();
  glPushName(-1);
  glPushMatrix();
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(0.0,5.0,0.0,5.0,0.0,10.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glLoadName(1);
  drawTriangle(2.0,2.0,3.0,
               2.0,2.5,3.0,-5.0);
  glLoadName(2);
  drawTriangle(2.0,7.0,3.0,
               7.0,2.5,8.0,-5.0);
  glLoadName(3);
  drawTriangle(2.0,2.0,3.0,
               2.0,2.5,3.0,0.0);
  drawTriangle(2.0,2.0,3.0,
               2.0,2.5,3.0,-10.);
  glPopMatrix();
  glFlush();
  hits = glRenderMode(GL_RENDER);
  processHits(hits,selectBuf);
}

void display(void) {
  glClearColor(0.0,0.0,0.0,0.0);
  glClear(GL_COLOR_BUFFER_BIT|
          GL_DEPTH_BUFFER_BIT);
  drawScene();
  selectObjects();
  glFlush();
}

int main(int argc,char** argv) {
  auxInitDisplayMode(AUX_SINGLE|
                     AUX_RGB|
                     AUX_DEPTH);
  auxInitPosition(0,0,200,200);
  auxInitWindow(argv[0]);
  myinit();
  auxMainLoop(display);
}

Sélection sur une zone écran

Utilisation du mode de sélection pour déterminer si des objets sont désignés.

Utilisation d'une matrice spéciale en conjonction avec la matrice de projection pour restreindre le dessin à une région de l'écran.

Un clic de souris -> entrée en mode de sélection.

  • void gluPickMatrix(GLdouble x,GLdouble y,GLdouble l, GLdouble h,GLint viewport[4]);

Crée une matrice de projection qui restreint le dessin à une région de l'écran et la multiplie à la matrice courante.

x,y: centre de la région (en coordonnées écran)

l,h: taille de la région (en coordonnées écran)

viewport: bords de l'écran actuel (obtenu par glGetIntegerv (GL_VIEWPORT,GLint *viewport);)

Exemple

#include <GL/gl.h>
#include <GL/glu.h>
#include "glaux.h"

#define BUFSIZE 512
int brd[3][3];

void myinit(void) {
  int i,j;
  for( i = 0 ; i < 3 ; i++ )
    for( j = 0 ; j < 3 ; j++ )
      brd[i][j] = 0;
      glClearColor(0.0,0.0,0.0,0.0);
}

void myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluOrtho2D(0.0,3.0,0.0,3.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void drawSquares(GLenum mode) {
  GLuint i,j;
  for ( i = 0 ; i < 3 ; i++ ) {
  if ( mode == GL_SELECT )
    glLoadName(i);
    for( j = 0 ; j < 3 ; j++ ) {
      if( mode == GL_SELECT )
        glPushName(j);
      glColor3f((float) i/3.0,
                (float) j/3.0,
                (float) brd[i][j]/3.0);
      glRecti(i,j,i+1,j+1);
      if ( mode == GL_SELECT )
        glPopName(); } }
}

void processHits(GLint hits,
                 GLuint buffer[]) {
  unsigned int i,j;
  GLuint ii,jj,names,*ptr;
  printf("hits = %d\n",hits);
  ptr =(GLuint *) buffer;
  for ( i = 0 ; i < hits ; i++ ) {
    names = *ptr;
    printf("nb of names=%d\n",names);
    ptr++;
    printf(" z1 is %u;",*ptr);
    ptr++;
    printf(" z2 is %u\n",*ptr);
    ptr++;
    printf(" names are ");
    for ( j = 0 ; j < names ; j++ ) {
      printf("%d ",*ptr);
    if ( j == 0 )
      ii = *ptr;
      else
      if( j == 1 )
    jj = *ptr;
    ptr++; }
  printf("\n");
  board[ii][jj] =(brd[ii][jj]+1)%3; }
}

void display(void) {
  glClear(GL_COLOR_BUFFER_BIT);
  drawSquares(GL_RENDER);
  glFlush();
}

void pickSquares(AUX_EVENTREC *event) {
  GLuint selectBuf[BUFSIZE];
  GLint hits;
  GLint viewport[4];
  int x,y;
  x = event->data[AUX_MOUSEX];
  y = event->data[AUX_MOUSEY];
  glGetIntegerv(GL_VIEWPORT,viewport);
  glSelectBuffer(BUFSIZE,selectBuf);
  glRenderMode(GL_SELECT);
  glInitNames();
  glPushName(-1);
  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  gluPickMatrix((float) x,
                (float)(viewport[3]-y),
                5.0,5.0,viewport);
  gluOrtho2D(0.0,3.0,0.0,3.0);
  drawSquares(GL_SELECT);
  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
  glFlush();
  hits = glRenderMode(GL_RENDER);
  processHits(hits,selectBuf);
}

int main(int argc,char** argv) {
  auxInitDisplayMode(AUX_SINGLE|
                     AUX_RGB);
  auxInitPosition(0,0,100,100);
  auxInitWindow(argv[0]);
  myinit();
  auxMouseFunc(AUX_LEFTBUTTON,
               AUX_MOUSEDOWN,
               pickSquares);
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}


Tutoriel 9 : indexation de VBO

Téléchargement

http://www.opengl-tutorial.org/fr/intermediate-tutorials/

Le Tutorial 1 explique comment compiler le code source. Lis le s’il te plait !

Tu peux télécharger un ZIP contenant tous les tutoriaux ici (Lis la FAQ pour savoir quelle version utiliser):

  • OpenGL-tutorial_v0015_33 La version normale
  • OpenGL-tutorial_v0015_21 Le port OpenGL 2.1

Sinon tu peux aussi prendre la dernière version du code sur le repo GitHub :

  • Repo GitHub (Les commits qui correspondent aux zips ci-dessus sont taggés OpenGL-tutorial_0015_33 et OpenGL-tutorial_0015_21 respectively)

Tout le code source sur ce site (à l’exception des libraries externes) est licensié avec la WTFPL Public license : ```

       DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
               Version 2, December 2004

Copyright (C) 2004 Sam Hocevar sam@hocevar.net

Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed.

       DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
  1. You just DO WHAT THE FUCK YOU WANT TO. ```

Tout le contenu des tutoriaux (explications etc) est disponible sous la licence Creative-Common: CC-BY-NC-ND disponible ici

  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

FAQ

Envoyer des e-mails …

Le meilleur moyen d’avoir du support est d’envoyer des courriels à contact@opengl-tutorial.org. Toutefois, si tu as un problème, essaye d’inclure autant d’informations que tu peux. Au minimum :

  • Ton système d’exploitation : Gentoo, Windows XP, 7, 10, Mac OS (Lion, Yosemite, El Capitan ?) …
  • 32 bits or 64 bits ?
  • Ta carte graphique : NVIDIA, AMD, Intel, S3, Matrox ? (Souviens-toi : utilise le portage OpenGL 2.1 si tu as un GPU intégré !).

… Et aussi n’importe quelle autre information que tu penses utile, par exemple:

  • La version de ton pilote de carte graphique
  • La call stack (pile d’appel) de ton crash
  • Un (des?) capture(s) d’écran
  • Les log de la console
  • Un minidump …

Et bien sûr, lis la FAQ en premier. Ça s’appelle FAQ (Questions fréquentes en anglais) pour une certaine raison =).

Je n’arrives pas à compiler les tutoriels

  • Assures-toi d’avoir lu le premier tutoriel. S’IL TE PLAIT, utilise CMake au lieu de recréer le projet, ou, au moins, assure-toi d’avoir lu Compiler votre propre application C.
  • Si tu as une erreur liée à la bibliothèque Assimp, cela sera corrigé prochainement ; toutefois, cela n’affecte qu’un SEUL tutoriel, tous les autres pourront être compilés correctement.
  • Si tu as une erreur liée à la bibliothèque AntTweakBar, cela n’affecte qu’un SEUL tutoriel, tous les autres pourront être compilés correctement.
  • S’il y a un autre souci, n’hésite pas à poster à nous envoyer un courriel.

J’ai compilé les tutoriels, mais cela plante au démarrage. Que se passe-t-il ?

Plusieurs raisons possibles :

GPU/Système d’exploitation incompatible

Vérifie STP si tu avez une carte Intel. Tu peux le faire en utilisant glewinfo, GPU Caps Viewer ou n’importe quel autre outil.

Les cartes graphiques Intel, sauf les récentes HD4000, ne supporte pas OpenGL 3.3. Cela veut dire que la plupart supporte seulement OpenGL 2.1. Tu dois télécharger la version OpenGL 2.1 sur la page des téléchargements, à la place.

L’autre raison possible est que tu utilises un Mac, avec une version avant Lion. Dans ce cas télécharge aussi la version 2.1 sur la page des téléchargements.

Mauvais répertoire de travail

Il se peut que tu n’exécutes pas les tutoriels à partir du bon répertoire. Essaye en double-cliquant sur le .exe à partir de l’explorateur de fichiers.

Lis le premier tutoriel pour configurer ton IDE afin que tu puisses déboguer l’exécutable.

Les .exe sont compilés dans le répertoire build, mais ensuite automatiquement copiés dans le répertoire source, pour qu’il puisse trouver les ressources nécessaire (images, modèle 3D, shaders)

Pas de VAO

Si tu as créé un programme à partir de rien, assure-toi d’avoir créé un VAO au moment de l’initialisation :

GLuint VertexArrayID;
 glGenVertexArrays(1, &VertexArrayID);
 glBindVertexArray(VertexArrayID);

Bogue de GLEW

GLEW possède un bogue qui rend impossible l’utilisation d’un contexte core (sauf lorsque tu utilises le code source des tutoriels, qui a été corrigé). Trois solutions :

  • Demande à GLFW de fournir un profil de compatibilité à la place :

    glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);

  • Utilise glewExperimental ; voici la méthode recommandée :

    glewExperimental = true;

  • Corrige GLEW … Voir ce patch.

CMake

T’as pas lu le premier tutoriel, pas vrai ? T’as pas essayé d’écrire ton propre Makefile et de tout compiler toi-même, N’EST-CE PAS ?

Pourquoi devrais-je utiliser OpenGL 3.3 si Intel et Mac ne peuvent pas l’exécuter ?!

… Aussi connu comme :

Quelle version d’OpenGL dois-je utiliser ?

En fait, je ne recommande pas d’utiliser OpenGL 3 ou supérieur pour une application. Je l’utilise dans les tutoriels, car c’est la méthode propre pour apprendre OpenGL, sans tous les trucs dépréciés et, car une fois que vous connaissez OpenGL 3.3, l’utilisation d’OpenGL 2.1 est immédiate.

Ce que je recommande :

  • Apprend OpenGL 3.3 afin de connaître la « bonne façon »
  • Définis un matériel cible pour ton application. Par exemple, elle nécessite les FBO et le GLSL
  • Utilise GLEW pour charger les extensions. Au démarrage, refuse tous les matériels qui n’ont pas le niveau de fonctionnalité requis.
  • A partir de maintenant, tu peux coder presque comme si tu étais avec OpenGL 3.3, avec juste quelques changements mineurs.
  • Si tu veux vraiment gérer les matériels plus anciens/moins chers, tu peux toujours les gérer en désactivant les effets qui nécessite les FBO, par exemple.

Il y a une vrai situation où tu peux utiliser une version très récente, disons OpenGL 4.2 : tu es un étudiant diplômé faisant de la recherche, tu as vraiment besoin d’une fonctionnalité récente et tu ne te soucies pas de la compatibilité, car ton logiciel ne sera jamais exécuté en dehors de ton laboratoire. Dans ce cas, ne perd pas de temps et utilise directement la version la plus récente d’OpenGL que ton matériel supporte.

Où télécharger OpenGL 3 ?

Tu ne le télécharges pas.

Sur Windows, par exemple, tu as seulement opengl32.dll, qui est seulement OpenGL 1.1. MAIS il y a une fonction, wglGetProcAddress(), qui permet de récupérer les fonctions qui ne sont pas implémentées directement dans opengl32.dll, mais qui sont disponibles dans le pilote.

GLEW appelle wglGetProcAddress sur tous les symboles nécessaires et les rend disponibles. (tu peux le faire toi-même, mais c’est extrêmement ennuyeux). GLEW déclare aussi des constantes qui n’existait pas dix ans auparavant, comme, GL_VERTEX_ATTRIB_ARRAY_DIVISOR_ARB.

Donc, assure-toi que ton GPU supporte la dernière version nécessaire, utilise GLEW et tu es prêt.

Pourquoi crée-t-on un VAO dans chaque tutoriel, mais on ne l’utilise jamais ?

Faux. Il est lié, donc en réalité, il est utilisé durant toute l’exécution.

Les VAO sont des abstractions de VBO. Ils se souviennent quel tampon est lié à quel attribut et une multitude d’autres choses. Ils réduisent le nombre d’appels OpenGL avant glDrawArrays/Elements(). Depuis OpenGL 3 Core, ils sont obligatoires, mais tu peux en utiliser seulement un seul et le modifier une fois pour toute (c’est ce qui est fait dans ce tutoriel).

Les VAO peuvent être source de confusion pour ces tutoriels pour débutant et il n’y a pas d’équivalant en OpenGL 2. De plus le gain de performance n’est pas très visible. Si tu es intéressés dans les VAO, jete un œil au wiki d’OpenGL. Cela peut légèrement simplifier ton application et améliorer légèrement les performances, mais ce n’est pas toujours le cas.

J’ai une erreur « Impossible de démarrer le programme ALL_BUILD »

ALL_BUILD n’est qu’un projet générés par CMake pour compiler tout les autres projets ; ce n’est pas un vrai programme.

Comme indiqué dans le premier tutoriel, tu dois sélectionner le projet que tu souhaites exécuter en effectuant un clic droit sur un projet (à partir de Visual Studio) et sélectionner « Définir comme projet de démarrage », comme ceci :

J’obtiens un message à propos du répertoire de travail et le programme plante.

Tu dois démarrer le programme à partir des dossiers tutorial01_first_window/, tutorial02_red_triangle/, etc. Si tu démarres le programme à partir de l’IDE, tu dois le configurer pour qu’il fasse ainsi.

Lis le premier tutoriel pour avoir plus de détails.

  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Billboards

Introduction

Les billboards sont des éléments 2D incrustés dans un monde 3D. Ce n’est pas un menu 2D au-dessus de tout le reste, ni un plan 3D autour duquel tu peux tourner, mais quelque chose entre les deux, comme les barres de vies dans beaucoup de jeux.

Ce qui diffère avec les billboards est qu’ils sont positionnés à un endroit spécifique, mais que leur orientation est automatiquement calculée afin qu’ils soient toujours face à la caméra.

Solution n°1 : la méthode 2D

Cette méthode est super simple.

Calcule simplement où ton point est à l’écran et affiche un texte 2D (voir le onzième tutoriel) à cette position.

// Everything here is explained in Tutorial 3 ! There's nothing new.
glm::vec4 BillboardPos_worldspace(x,y,z, 1.0f);
glm::vec4 BillboardPos_screenspace = ProjectionMatrix * ViewMatrix * BillboardPos_worldspace;
BillboardPos_screenspace /= BillboardPos_screenspace.w;

if (BillboardPos_screenspace.z < 0.0f){
    // Object is behind the camera, don't display it.
}

Voilà !

En plus d’être super facile à implémenter, le billboard aura la même taille, peu importe sa distance avec la caméra. Mais le texte 2D est toujours affiché au-dessus de tout le reste et cela peut désorganiser le rendu et s’afficher au-dessus d’autres objets.

Solution n°2 : la méthode 3D

Celle-ci est habituellement mieux et pas trop compliquée.

Le but est de garder le modèle aligné avec la caméra, même si la caméra se déplace :

Tu peux voir ce problème comme étant un souci de génération de matrice de modèle, même si ce n’est pas aussi simple que cela.

L’idée est que chaque coin du billboard est à la position centrale, déplacé par les vecteurs haut et droit de la caméra ;

Bien sûr, on ne connaît que le centre du billboard dans le repère du monde, donc on a aussi besoin des vecteurs haut/droit de la caméra dans le repère du monde.

Dans l’espace caméra, le vecteur haut de celle-ci est (0, 1, 0). Pour l’obtenir dans l’espace monde, on le multiplie par la matrice qui transforme du repère de la caméra vers le repère du monde, qui est, bien sûr, l’inverse de la matrice de vue.

Une façon plus simple de l’exprimer en mathématique est :

CameraRight_worldspace = {ViewMatrix[0][0], ViewMatrix[1][0], ViewMatrix[2][0]}
CameraUp_worldspace = {ViewMatrix[0][1], ViewMatrix[1][1], ViewMatrix[2][1]}

Une fois que l’on a ça, il est très simple de calculer la position finale du sommet :

vec3 vertexPosition_worldspace =
    particleCenter_wordspace
    + CameraRight_worldspace * squareVertices.x * BillboardSize.x
    + CameraUp_worldspace * squareVertices.y * BillboardSize.y;
  • particleCenter_worldspace est, comme son nom l’indique, la position du centre du billboard : elle est déterminée en utilisant une variable uniforme vec3
  • squareVertices est le modèle original. SquareVertices.x est -0.5 pour les sommets de gauche, qui sont donc déplacé vers la gauche de la caméra (à cause du *CameraRight_worldsspace)
  • BillboardSize est la taille du billboard, en unités monde, envoyée avec une autre variable uniforme.

Et presto, voici le résultat. C’était facile, non ?

Pour information, voici comment squareVertices est définie :

// The VBO containing the 4 vertices of the particles.
 static const GLfloat g_vertex_buffer_data[] = {
 -0.5f, -0.5f, 0.0f,
 0.5f, -0.5f, 0.0f,
 -0.5f, 0.5f, 0.0f,
 0.5f, 0.5f, 0.0f,
 };

Solution n°3 : la méthode 3D avec taille fixe

Comme tu peux le voir ci-dessus, la taille du billboard change suivant la distance de la caméra. Dans certains cas, cela est le résultat attendu, mais dans d’autres, tel que les barres de vies, tu veux probablement une taille fixe à la place.

Comme le déplacement entre le centre et un coin doit être fixe dans l’espace écran, c’est exactement ce que l’on va faire : calculer la position du centre dans l’espace écran et la décaler pour avoir la position des coins.

vertexPosition_worldspace = particleCenter_wordspace;
// Get the screen-space position of the particle's center
gl_Position = VP * vec4(vertexPosition_worldspace, 1.0f);
// Here we have to do the perspective division ourselves.
gl_Position /= gl_Position.w;

// Move the vertex in directly screen space. No need for CameraUp/Right_worlspace here.
gl_Position.xy += squareVertices.xy * vec2(0.2, 0.05);

Souvient-toi qu’à cet endroit du pipeline de rendu, tu es en Normalized Device Coordinates (coordonnées normalisées du périphérique), donc entre -1 et 1 sur les deux axes : ce n’est pas en pixels.

Si tu veux une taille en pixels, c’est facile : utilise simplement (ScreenSizeInPixels / BillboardSizeInPixels) au lieu de BillboardSizeInScreenPercentage.

Solution n°4 : rotation verticale seule

Quelques technique de rendue représentent les arbres lointains et les lampes avec des billboards. Mais tu ne souhaites vraiment, vraiment pas, que votre arbre soit incliné : il DOIT être vertical. Donc tu as besoin d’un système hybride qui tourne seulement autour d’un axe.

Eh bien … ce cas-là est laissé comme exercice pour le lecteur !

  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Faire ta propre application en C

Beaucoup d’efforts ont été effectués pour que ces tutoriels soient faciles à compiler et exécuter. Malheureusement, cela signifie aussi que CMake cache la façon de mettre en place ton propre projet.

Donc, ce tutoriel va expliquer comment compiler ta propre application C à partir de rien. Mais avant tout, tu as besoin de quelques connaissances de base sur ce que ton compilateur fait.

Ne saute pas les deux premières sections. Si tu lis ce tutoriel, tu as probablement besoin de connaître ces choses.

Le modèle des applications C

Le préprocesseur

On parle ici des #define et #include.

L’étape du préprocesseur est très simple : c’est du copier-coller.

Lorsque le préprocesseur voit le code du fichier MyCode.c suivant :

#include "MyHeader.h"

void main(){
    FunctionDefinedInHeader();
}

Il ouvre simplement le fichier MyHeader.h et copie-colle son contenu dans le fichier MyCode.c.

// Begin of MyCode.c
// Begin of MyHeader.h
#ifndef MYHEADER_H
#define MYHEADER_H

void FunctionDefinedInHeader(); // Declare the function

# endif
// End of MyHeader.h

void main(){
    FunctionDefinedInHeader(); // Use it
}

// End of MyCode

De façon similaire, les #define sont copiées-collées, les #if sont analysés et potentiellement retirés, etc.

À la fin de cette étape, vous obtenez un fichier C++, sans #define, #if, #ifdef, #include, prêt à être compilé.

Par exemple, voici le fichier main.cpp du sixième tutoriel, complètement prétraité dans Visual : fichier préprocessé. Attention, c’est un immense fichier ! Mais ça vaut le coup de savoir ce qu’un fichier .cpp simple est réellement pour le compilateur.

Compilation

Le compilateur traduit le code C++ en une représentation que le CPU peut comprendre directement. Par exemple, le code suivant :

int i=3;
int j=4*i+2;

sera traduit en ces opcodes x86 :

mov         dword ptr [i],3
mov         eax,dword ptr [i]
lea         ecx,[eax*4+2]
mov         dword ptr [j],ecx

Chaque fichier .cpp est compilé séparément et le code binaire résultant est écrit dans des fichiers .o/.obj.

Nous n’avons pas encore l’exécutable : il reste une étape à faire.

Linking

Le linker prend tout le code binaire (le tien, mais aussi celui des bibliothèques externes) et génère l’exécutable final. Quelques notes :

  • Une bibliothèque possède l’extension .lib.
  • Certaines bibliothèques sont statiques. Cela signifie que les .lib contiennent tous les opcodes x86 nécessaires.
  • Certaines bibliothèques sont dynamiques (aussi appelées bibliothèques partagées). Cela signifie que le fichier .lib ne contient pas tout le code x86 ; il indique simplement : « Je jure que les fonctions Foo, Bar et WhatsNot seront disponibles à l’exécution ».

Lorsque le linker a été exécuté, on obtient un exécutable (.exe sous Windows, sans extension sous Unix) :

Exécution

Lorsque tu lances l’exécutable, le système d’exploitation va ouvrir le .exe et placer les opcodes x86 en mémoire. Comme indiqué précédemment, certains codes ne sont pas disponibles à ce moment-là : le code des bibliothèques dynamiques. Mais le linker a été assez gentil pour indiquer où les trouver : le .exe indique clairement que la fonction glClearColor est implémentée dans le fichier OpenGL32.dll.

Windows va ouvrir la .dll et trouver glClearColor :

Quelques fois, une .dll ne peut être trouvée, sûrement, car l’installation a échoué et le programme ne peut pas s’exécuter.

Comment faire X avec l’éditeur Y ?

On va expliquer les opérations de base de création d’un projet séparement des opérations specifiques a OpenGL.

  • Premièrement, tu devras faire les opérations de base tout le temps, donc tu as plutot intérêt à les connaître par coeur
  • Deuxièmement, vous allez connaître ce qui est spécifique à OpenGL de ce qui ne l’est pas

Visual Studio

Creating a new project

Fichier (File) -> Nouveau (New) -> Projet (Project) -> Projet vide (Empty Project). N’utilise pas ces étranges assistants. N’utilise aucune option que tu ne connais pas (désactivez MFC, ATL, les entêtes précompilés, stdafx, le fichier main).

Adding a source file in a project

Faites un clic droit sur Fichiers sources (Sources Files) -> Ajouter nouveau (Add new).

Ajouter un fichier source dans un projet

Fait un clic droit sur projet (Project) -> Propriétés du projet (Project Properties) -> C++ -> Général -> Répertoire d’inclusion additionnel (Additional include directories). C’est une liste déroulante, tu peux la modifier comme tu le souhaites.

Lier une bibliothèque

Fait un clic droit sur projet (Project) -> Propriétés du projet (Project Properties) -> Éditeur de liens (Linker) -> Entrée (Input) -> Dépendances additionnelles (Additional dependencies) : tape le nom du fichier .lib. Par exemple : opengl32.lib.

Dans les propriétés du projet (Project Properties) -> Éditeur de liens (Linker) -> Général -> répertoires de bibliothèques additionnels (Additional library directories), assures-toi que le chemin de la bibliothèque ci-dessus est présent.

Compiler, exécuter et déboguer

Pour définir le répertoire de travail (là où sont les textures et les shaders) : Propriétés du projet (Project Properties) -> Débogage (Debugging) -> Répertoire de travail (Working Directory).

Pour exécuter : Maj-F5 ; mais tu ne souhaites probablement jamais faire ça. Pour déboguer : F5.

Voici une courte liste de raccourcis pour le débogage :

  • F9 sur une ligne, ou en faisant un clic gauche sur les numéros de ligne : définit un point d’arrêt. Un point rouge va apparaître
  • F10 : exécuter la ligne actuelle
  • F11 : exécuter la ligne actuelle, mais en s’enfoncer dans les fonctions que cette ligne appelle (« Step into »)
  • Maj-F1 : exécuter jusqu’à la fin de la fonction (« Step out »)

Tu as aussi de nombreuses fenêtres de débogage : les variables observées, la pile d’appels, les threads…

QtCreator

QtCreator est disponible gratuitement ici.

Créer un nouveau projet

Utilise un projet vide C ou C++ ; éviter les templates contenant des trucs de Qt.

Utilise les options par défaut.

Ajouter un fichier source dans un projet

Utilise l’interface, ou ajoute un fichier dans le .pro :

SOURCES += main.cpp \
           other.cpp \
           foo.cpp

Ajouter un répertoire d’inclusion

Dans le fichier .pro :

INCLUDEPATH += [ton chemin] \ [ton autre chemin]

Lier avec une bibliothèque

Clic droit sur le projet -> Ajout une bibliothèque (Add library)

  • Si tu es sous Linux et que tu as installé la bibliothèque à partir de apt-get ou similaire, il y a de fortes chances que la bibliothèque soit enregistrée dans le système. Tu peux sélectionner « Paquet système » (System package) et entrer le nom de la bibliothèque (par exemple : libglfw ou glew).

  • Sinon, utilise « Bibliothèque système » (System library) et navigue jusqu’à atteindre le dossier où tu as compilée ta lib.

Compiler, exécuter et déboguer

Pour compiler : Ctrl-B ou le marteau dans le coin inférieur gauche.

Pour exécuter : la flèche verte. Tu peux définir les arguments du programme et le répertoire de travail dans Projets (Projects) -> Paramètres d’exécution (Run Settings).

Pour déboguer :

  • Définir un point d’arrêt : clique à gauche des numéros de ligne. Un point rouge apparaîtra
  • F10 : exécuter la ligne actuelle
  • F11 : exécuter la ligne actuelle, mais en s’enfonçant dans les fonctions que cette ligne appelle (« Step into »)
  • Maj-F11 : exécuter jusqu’à la fin de la fonction (« Step out »)

Tu as aussi de nombreuses fenêtres de débogage : les variables observées, la pile d’appels, les threads …

XCode

En cours …

CMake

CMake créera les projets et cela pour pratiquement tous les IDE : Visual, Qt Creator, XCode, make, Code::Blocks, Eclipse, etc., sur n’importe quel système d’exploitation. Ça te libère de la maintenance de multiples fichiers de projet.

Créer un nouveau projet

Crée un fichier CMakeLists.txt et écris ceci (adapte au besoin) :

cmake_minimum_required (VERSION 2.6)
project (your_project_name)

find_package(OpenGL REQUIRED)

add_executable(your_exe_name
    tutorial04_colored_cube/tutorial04.cpp
    common/shader.cpp
    common/shader.hpp
)

Lance l’interface graphique de CMake, navigue jusqu’à atteindre ton fichier .txt et sélectionne ton dossier de compilation. Clique sur « Configurer » (Configure) puis « Générer » (Generate). Ta solution va être créée dans le dossier de compilation.

Ajouter un fichier source dans un projet

Ajoute simplement une ligne dans la commande add_executable.

Ajouter des répertoires d’inclusion

include_directories(
    external/AntTweakBar-1.15/include/
    external/glfw-2.7.2/include/
    external/glm-0.9.1/
    external/glew-1.5.8/include/
    .
)

Lier une bibliothèque

set(ALL_LIBS
    ${OPENGL_LIBRARY}
    GLFW_272
    GLEW_158
    ANTTWEAKBAR_151_OGLCORE_GLFW
)

target_link_libraries(tutorial01_first_window
    ${ALL_LIBS}
)

Compiler, exécuter et déboguer

CMake ne fait pas cela. Utilisez votre IDE favori.

make

Vraiment ??

GCC

Cela peut être intéressant de compiler un petit projet « à la main » afin de mieux comprendre ce qui se passe. Ne le fait pas sur un vrai projet …

Tu peux aussi le faire sur Windows avec MinGW.

Compile chaque fichier .cpp séparément :

g++ -c main.cpp
g++ -c tools.cpp

Comme indiqué précédemment, tu vas obtenir des fichiers main.o et tools.o. Link-les :

g++ main.o tools.o

un fichier a.out apparaît ; c’est ton exécutable. Exécute-le :

./a.out

C’est tout !

Compiler votre propre application C

Armé de cette connaissance, on peut commencer à compiler soi-même une application OpenGL.

  1. Télécharge les dépendances : ici on utilise GLFW, GLEW et GLM, mais suivant ton projet, tu peux avoir besoin d’autre chose. Sauvegarde-les préférablement dans des sous-répertoires de ton projet (par exemple : external/).
  2. Elles doivent être précompilées pour votre plateforme. Toutefois, GLM n’a pas besoin d’être compilée.
  3. Créez un nouveau projet avec l’IDE de ton choix.
  4. Ajoute un nouveau fichier .cpp dans le projet.
  5. Copie et colle, par exemple, le code suivant (c’est en fait le fichier playground.cpp) :
#include <stdio.h>
#include <stdlib.h>

#include <GL/glew.h>

#include <GL/glfw.h>

#include <glm/glm.hpp>
using namespace glm;

int main( void )
{
    // Initialise GLFW
    if( !glfwInit() )
    {
        fprintf( stderr, "Failed to initialize GLFW\n" );
        return -1;
    }

    glfwOpenWindowHint(GLFW_FSAA_SAMPLES, 4);
    glfwOpenWindowHint(GLFW_WINDOW_NO_RESIZE,GL_TRUE);
    glfwOpenWindowHint(GLFW_OPENGL_VERSION_MAJOR, 3);
    glfwOpenWindowHint(GLFW_OPENGL_VERSION_MINOR, 3);
    glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    // Open a window and create its OpenGL context
    if( !glfwOpenWindow( 1024, 768, 0,0,0,0, 32,0, GLFW_WINDOW ) )
    {
        fprintf( stderr, "Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n" );
        glfwTerminate();
        return -1;
    }

    // Initialize GLEW
    if (glewInit() != GLEW_OK) {
        fprintf(stderr, "Failed to initialize GLEW\n");
        return -1;
    }

    glfwSetWindowTitle( "Playground" );

    // Ensure we can capture the escape key being pressed below
    glfwEnable( GLFW_STICKY_KEYS );

    // Dark blue background
    glClearColor(0.0f, 0.0f, 0.3f, 0.0f);

    do{
        // Draw nothing, see you in tutorial 2 !

        // Swap buffers
        glfwSwapBuffers();

    } // Check if the ESC key was pressed or the window was closed
    while( glfwGetKey( GLFW_KEY_ESC ) != GLFW_PRESS &&
           glfwGetWindowParam( GLFW_OPENED ) );

    // Close OpenGL window and terminate GLFW
    glfwTerminate();

    return 0;
}
  1. Compile le projet.

Tu vas obtenir de nombreuses erreurs de compilation. On va les analyser une par une.

Dépannage

Les messages d’erreurs ci-dessous sont pour Visual Studio 2010, mais ils sont plus ou moins proches de ceux de GCC.

Visual Studio - fatal error C1083 : impossible d’ouvrir le fichier ‘GL/glew.h’ : Aucun fichier trouvé.

(Ou n’importe quel autre fichier.)

Certains entêtes ont des emplacements bizarres. Par exemple, les fichiers à inclure de GLEW sont situés dans external/glew-x.y.z/include. Le compilateur n’a aucune méthode magique pour le deviner, donc tu dois les lui indiquer. Dans les propriétés du projet, ajoute le chemin approprié dans les options du compilateur (et non pas l’éditeur de liens).

Pour aucune raison tu ne dois copier les fichiers dans les répertoires du compilateur (Program Files/Visual Studio/… ). Techniquement, cela fonctionnera, mais c’est une très mauvaise pratique.

De plus, c’est une bonne chose d’utiliser des chemins relatifs (./external/glew/… au lieu de C:/Users/utilisateur/Downloads/…).

Par exemple, voici ce que contiennent les fichiers de CMake de ces tutoriels :

external/glfw-2.7.2/include
external/glm-0.9.1
external/glew-1.5.8/include

Répéte ça jusqu’à ce que tous les fichiers soient trouvés.

GCC - fatal error : GL/glew.h : aucun fichier trouvé

(ou n’importe quel autre fichier.)

Ça signifie que la bibliothèque n’est pas installée. Si tu es chanceux, la bibliothèque est connue et tu n’a qu’à l’installer. C’est le cas pour GLFW, GLEW et GLM :

sudo apt-get install libglfw-dev libglm-dev libglew1.6-dev

Si ce n’est pas une bibliothèque répandue, regarde la réponse pour Visual Studio ci-dessus.

Visual Studio - error LNK2019 : symbole non résolu glfwGetWindowParam référencé dans la fonction main

(ou n’importe quel autre symbole dans n’importe quelle autre fonction.)

Félicitations ! Tu as une erreur d’édition de liens. C’est une excellente nouvelle : ça signifie que la compilation a réussi. Plus qu’une dernière étape !

Les fonctions GLFW se trouvent dans une bibliothèque externe. Tu dois indiquer cette bibliothèque à l’éditeur de liens. Ajoute-la dans les options de l’éditeur de liens. N’oublie pas d’ajouter le chemin menant à cette bibliothèque.

Par exemple, voici ce que le projet Visual Studio utilise. Les noms sont quelque peu inhabituels, car c’est une compilation personnalisée. De plus, GLM ne nécessite pas d’être compilée ou liée, donc elle n’est pas là.

external\Debug\GLFW_272.lib
external\Debug\GLEW_158.lib

Si tu télécharges ces bibliothèques à partir de SourceForge (GLFW, GLEW) et les compile toi-même, tu devras spécifier le chemin adéquat. Par exemple :

C:\Where\You\Put\The\Library\glfw.lib
C:\Where\You\Put\The\Other\Library\glew32.lib

GCC - main.cpp : référence indéfinie vers ‘glfwInit’

(ou n’importe quel autre symbole.)

Même réponse que pour Visual Studio ci-dessus.

Sur Linux, GLFW et GLEW (et plein d’autres) sont généralement installées avec apt-get ou autre : sudo apt-get install libglew-dev libglfw-dev (cela peut changer). Lorsque tu fais ça, les bibliothèques sont copiées dans les répertoires standards du compilateur, donc tu n’as pas à préciser le chemin. Lie simplement glfw et glew comme présenté dans la première section.

J’ai tout configuré correctement, mais j’ai toujours une erreur « unresolved external error » !

Ça peut être quelque peu difficile à tracer. Il y a plusieurs solutions :

J’ai une erreur durant l’édition des liens avec _imp_glewInit ou d’autres symboles qui commencent par _imp

Ça signifie que la bibliothèque (dans ce cas, glew) a été compilée comme bibliothèque statique, mais tu essayes de l’utiliser comme bibliothèque dynamique. Ajoute simplement la définition pour le préprocesseur suivante dans les options du compilateur (pour ton propre projet, et non celui de glew) :

GLEW_STATIC

J’ai quelques problèmes étranges avec GLFW

Peut-être, car GLFW a été compilée comme bibliothèque dynamique, mais tu essayes de l’utiliser comme bibliothèque statique ?

Essaye d’ajouter la définition pour le préprocesseur suivante :

GLFW_DLL

J’ai un autre problème avec l’éditeur de liens ! Aidez-moi, je suis bloqué !

Envoie nous par mail ton projet complet compressé dans un zip et on te répondra et ajoutera les indications manquantes.

J’aimerais résoudre cela moi-même. Quelles sont les règles générales ?

Admettons que tu es l’auteur de la GLFW. Tu veux fournir la fonction glfwInit().

Lors de la compilation comme DLL, tu dois indiquer au compilateur que glfwInit() n’est pas comme n’importe quelle autre fonction dans la DLL ; elle doit être vue par les autres, contrairement à glfwPrivateImplementationMethodNobodyShouldCareAbout(). On fait ça en déclarant la fonction externe (external en anglais) avec GCC, ou “__declspec(dllexport)” avec Visual Studio.

Lorsque tu veux utiliser GLFW, tu deois indiquer au compilateur que la fonction n’est pas réellement disponible : il doit la lier dynamiquement. On fait ça en déclarant la fonction externe (external) avec GCC ou “__declspec(dllimport)” avec Visual.

Donc tu utilises une définition pratique #define : GLFWAPI et tu l’utilises pour déclarer les fonctions :

GLFWAPI int glfwInit(void) ;

  • Lorsque tu compiles la bibliothèque en DLL, tu définis #define GLFW_BUILD_DLL. GLFWAPI sera alors défini en __declspec(dllexport)
  • Lorsque tu utilises GLFW à travers une DLL, tu définis #define GLFW_DLL. GLFWAPI sera alors défini en __declspec(dllimport)
  • Lorsque tu compiles la bibliothèque statique, GLFWAPI est défini vide
  • Lorsque tu utilises GLFW en bibliothèque statique, GLFWAPI est défini vide.

Donc la règle est : ces indications doivent être consistantes. Si tu compiles une bibliothèque (n’importe quelle bibliothèque, pas que GLFW) en DLL, utilise la bonne définition pour le préprocesseur : GLFW_DLL, GLEW_STATIC.

Mon programme crashe !

Il y a de multiples raisons pour qu’une application C++ OpenGL plante. Voici quelques-unes d’entre elles. Si tu ne sais pas la ligne exacte où ton programme plante, apprend à utiliser un débogueur (voir les raccourcis ci-dessus). NE débogue PAS avec printf().

Je n’atteins même pas le main()

C’est certainement dû à des DLL qui n’ont pas pu être trouvées. Essaye d’ouvrir ton application avec Dependency Walker (Windows) ou ldd (Linux ; essaye aussi ceci).

Mon programme crashe sur glfwOpenWindow(), ou toute autre fonction qui crée un contexte OpenGL

Plusieurs raisons possible :

  • Ton GPU ne supporte pas la version OpenGL demandée. Essaye de voir la version supportée avec GPU Caps Viewer ou un outil similaire. Met à jour tes pilotes si la version semble trop vieille. Les cartes intégrées Intel sur les netbooks sont particulièrement nulles. Utilise une version d’OpenGL plus basse (2.1 par exemple) et utilise les extensions si tu manques de fonctionnalités
  • Ton système d’exploitation ne supporte pas la version OpenGL demandée : Mac OS pre-Lion … même réponse
  • Tu essayes d’utiliser GLEW avec un contexte OpenGL core (c’est-à-dire, sans les choses dépréciées). C’est un bogue de GLEW. Utilise glewExperimental=true avec glewInit() ou utilise le mode de compatibilité (c’est-à-dire utilise GLFW_OPENGL_COMPAT_PROFILE à la place de GLFW_OPENGL_CORE_PROFILE).

Mon programme crash au premier appel OpenGL, ou a la création du premier buffer

Trois raisons possible:

  • Tu n’appels pas glewInit() APRES glfwOpenWindow()
  • Tu utilises le profil OpenGL Core, et tu n’a pas crée de VAO, Ajoute ça après glewInit() :
    GLuint VertexArrayID;
    glGenVertexArrays(1, &VertexArrayID);
    glBindVertexArray(VertexArrayID);
  • Tu utilise la version normales de GLEW, qui a un bug. Tu ne peux pas utiliser un profil OpenGL Corea cause d’un bug. Soit tu utilise: glewExperimental=true avant glewInit(), soit tu demandes a GLFW un Compatibility Profile à la place :
    glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);

Mon programme crashe lorsque j’essaie de charger un fichier

Définis ton répertoire de travail correctement. Lis le premier tutoriel.

Crée un fichier test.txt et essaye le code suivant :

if ( fopen("test.txt", "r" ) == NULL ){
    printf("I'm probably running my program from a wrong folder");
}

UTILISE LE DÉBOGUEUR !!!! Sincèrement ! Ne débogue pas avec printf() ; utilise un bon EDI. http://www.dotnetperls.com/debugging est pour le C# mais c’est aussi valide pour le C++. Cela peut être différent pour XCode ou Qt Creator, mais les concepts restent exactement les mêmes.

Autre chose ne va pas

Envoie nous un courriel !

  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Math Cheatsheet

Trigonométrie

Pi

const float pi = 3.14159265f; // avec une infinité de nombre après

Cosinus & Sinus

(Provient de http://commons.wikimedia.org/wiki/User:Geek3 , sous la licence GNU Free Documentation)

Cercle unitaire

( Modifié depuis http://en.wikipedia.org/wiki/User:Gustavb, licence: Creative Commons 3.0 )t est un angle en radian.

0 radians = 0 degrés

180 degrés = Pi radians

360 degrés (cercle complêt) = 2*Pi radians

90 degrés = Pi/2 radians

Vecteurs

Fait TOUJOURS attention au repère dans lesquels tes vecteur sont. Regarde la section 3 pour plus de détails.

Coordonées homogène

Un vecteur 3D est représenté par : (x, y, z), mais un vecteur 3D en coordonées homogène est représenté par : (x, y, z, w) :

  • w=0 : Le vecteur représente une direction
  • w=1 : Le vecteur représente une position
  • w=qqch d’autre : C’est peut être encore correct, mais t’as plutôt intérêt de savoir ce que tu fais.

On ne peut multiplier une matrice 4*4 qu’avec un vecteur homogène.

Longueur

Comme pour une distance cartésienne : racine carrée(x² + y² + z²). w n’est pas pris en compte.

Produit en croix

( Modifié depuis http://en.wikipedia.org/wiki/User:Acdx , image sous la license: Creative Commons 3.0 )Le X est la notation du produit en croix. longueur( a x b ) == longueur(a) * longueur(b) * sin(θ), donc tu voudra peut être normaliser le résultat

Produit scalaire (Dot product en anglais)

##

( Depuis http://en.wikipedia.org/wiki/File:Dot_Product.svg )A.B = longueur(A)cos(θ) , mais on peut le calculer comme ça: A.xB.x +A.yB.y +A.zB.z

Addition et soustraction

Pour chaque composante (x, y, …) : ```

res.x = A.x + B.x res.y = A.y + B.y … ```

Multiplication

Pour chaque composante (x, y, …) : ```

res.x = A.x * B.x res.y = A.y * B.y … ```

Normalisation

On divise le vecteur par sa longueur ```

normalizedVector = vec * ( 1.0f / vec.length() ) ```

Matrices

Multiplication Matrice à Matrices

Example pour une matrice de translation :

Multiplication matrice à vecteur

Transformations habituelles

… mais dans tes shaders, tu peux aussi représenter tes vecteurs dans le repère tangent. Et aussi dans le repère image quand tu fais des effets après rendu.

res.x = A.x + B.x

  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Outils et liens utiles

Documentation

OpenGL references

  • OpenGL 3.2 Références rapides
  • OpenGL 4.1 Références rapides
  • OpenGL 4.0 Spécification. Pas facile à lire !
  • GLSL Spécifications
  • Liste de toutes les extensions Si ton GPU est assez récent, la pluspart seront disponible via GLEW

Autre tutoriels

  • http://arcsynthesis.org/gltut/ Un tutoriel complêt ; Courbe d’apprentissage élevée
  • ogldev Notre principale compétiteur :)
  • http://openglbook.com/ Quelques autres tutoriels pour débutant
  • NeHe Plein de tutoriel 1.1, avec quelques points intéressant, mais pour la pluspart déprécié

Livres

  • Real-time Rendering 3
  • OpenGL SuperBible
  • More Opengl Programming

Autres

  • Un voyage à travers le pipeline graphique : Des informations en profondeur à propos des drivers et des cartes graphiques. et comment ils fonctionnent réellement. C’est vraiment une très bonne source d’info. Très recommandé !
  • Statistiques d’Unity : Des statistiques sur les cartes graphiques actuellement dans le marché.
  • Plus de statistiques : Plus précises, mais avec des infos plus vieilles.

Outils de débogage

  • glslDevil : un fantastique débogueur GLSL - lorsqu’il fonctionne. Même si le site Web est souvent hors ligne.
  • GLIntercept : génère une page Web avec la liste des commandes OpenGL exécutées. Affiche le code des shaders, le contenu des images, le contenu des buffers d’image, etc.
  • gdebugger : un profileur.
  • NVidia Parallel NSight : un outil merveilleux qui donne accès à des informations très précises sur les performances. Hautement recommandé. Visual Studio + Nvidia seulement.

Bibliothèques

Fenêtrage et divers

  • GLFW , celui que l’on utilise.
  • GLUT, le plus vieux. Pas maintenu, licence horrible. Non recommandé.
  • FreeGlut, même bibliothèque, mais Open Source.
  • SDL, je pense que celle-là est étrange mais certains l’aiment.
  • SFML, inclut une machine à café, une machine à laver et un téléphone portable.

Chargement des extensions

  • GLEW, celle que l’on utilise.
  • GLEE, j’ai entendu dire qu’elle n’était pas mauvaise non plus.
  • gl3w, un script Python qui génère un fichier C++.

Bibliothèques mathématiques

  • GLM, celle que l’on utilise. Très complète. Directement compatible avec OpenGL et CUDA.
  • Vectormath de Bullet, jamais utilisé mais elle semble être SIMD (= plus rapide, même si tu n’effectue que rarement de grands calculs sur le CPU).
  • Boost.ublas , c’est très gros. Je ne la recommande pas pour OpenGL.
  • MathFu Google a fait sa bibliothèque de math optimisé SIMD.

Chargement d’images

  • stbimage Un simple fichier .c à inclure dans ton projet et tu peux charger les fichiers .jpeg, .bmp et .png !!!
  • SOILJe recommande celle-ci. Basée sur stbimage, elle fournit des fonctions pratiques pour l’utiliser avec OpenGL, ainsi qu’un chargeur de fichiers DDS.
  • DevIL Une bibliothèque de chargement d’image tout en un.
  • FreeImage … et encore une autre.
  • Boost.GIL Grosse et encombrante, ne charge pas les .jpg.
  • libjpg La bibliothèque classique pour charger les .jpg. Assez difficile à utiliser, mais de nombreux exemples sur le net.
  • libpng Pareil.

Chargement d’objets statiques

  • ASSIMP

Chargement d’objets articulés et autres

  • N’importe quel chargeur de fichier Quake III et Quake IV.
  • Cal3D

Physique

  • Bullet Open Source, utilisée dans beaucoup de jeux et même des films.
  • Newton Gratuite et aussi très bonne.
  • ODE. Vieille et instable. À éviter.
  • PhysX
  • Havok (chère)
  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Particules/Instanciation

Introduction

Les particules sont très similaires aux billboards 3D. Il y a toutefois quelques différences importantes :

  • Il y a généralement énormément de particules
  • Elles se déplacent
  • Elles apparaissent et meurent
  • Elles sont semi-transparentes

Toutes ces différences entraînent des problèmes. Ce tutoriel présente UNE solution pour les résoudre ; il y a beaucoup d’autres possibilités.

Des particules, plein partout !

La première idée pour dessiner une multitude de particules serait d’utiliser le code du tutoriel précédent et d’appeler glDrawArrays pour chaque particule. C’est une très mauvaise idée, car cela signifie que l’ensemble de tes superbes multiprocesseurs GTX 512+ seront tous utilisés pour ne dessiner qu’UN unique rectangle (évidemment, un seul sera utilisé, perdant 99 % d’efficacité). Puis tu vas dessiner le billboard suivant et cela sera identique.

Clairement, on doit trouver une méthode pour dessiner toutes les particules en même temps.

Il y a plusieurs façons pour le faire ; en voici trois :

  • Générer un seul VBO avec toutes les particules à l’intérieur. Facile, efficace, fonctionne sur toutes les plates-formes
  • Utiliser les geometry shaders. Ce n’est pas exploré dans ce tutoriel, notamment parce que 50 % des ordinateurs ne les supportent pas (en 2012)
  • Utiliser l’instanciation. Ce n’est pas disponible sur TOUTES les plates-formes, mais une grande majorité de celles-ci.

Dans ce tutoriel, nous allons utiliser la troisième option : c’est un bon compromis entre les performances et la disponibilité et par-dessus tout, il est facile d’ajouter le support de la première méthode une fois que celle-ci fonctionne.

Instanciation

« Instanciation » signifie que l’on a un modèle de base (dans ce cas, un simple rectangle fait de deux triangles), mais de nombreuses instances de ce rectangle.

Techniquement, cela s’effectue avec plusieurs buffers (tampons) :

  • Certains pour décrire le modèle de base
  • D’autres pour décrire les particularités de chaque instance du modèle de base

Tu as de nombreuses, très nombreuses options sur ce que tu mets dans chaque buffer. Dans notre cas basique on a :

  • Un buffer pour les sommets du modèle. Pas de buffer d’indices, donc c’est 6 vec3, constituant deux triangles, qui font un rectangle
  • Un buffer pour le centre des particules
  • Un buffer pour la couleur des particules

Ce sont des buffers standards. Ils sont créés de cette façon :

// The VBO containing the 4 vertices of the particles.
// Thanks to instancing, they will be shared by all particles.
static const GLfloat g_vertex_buffer_data[] = {
 -0.5f, -0.5f, 0.0f,
 0.5f, -0.5f, 0.0f,
 -0.5f, 0.5f, 0.0f,
 0.5f, 0.5f, 0.0f,
};
GLuint billboard_vertex_buffer;
glGenBuffers(1, &billboard_vertex_buffer);
glBindBuffer(GL_ARRAY_BUFFER, billboard_vertex_buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);

// The VBO containing the positions and sizes of the particles
GLuint particles_position_buffer;
glGenBuffers(1, &particles_position_buffer);
glBindBuffer(GL_ARRAY_BUFFER, particles_position_buffer);
// Initialize with empty (NULL) buffer : it will be updated later, each frame.
glBufferData(GL_ARRAY_BUFFER, MaxParticles * 4 * sizeof(GLfloat), NULL, GL_STREAM_DRAW);

// The VBO containing the colors of the particles
GLuint particles_color_buffer;
glGenBuffers(1, &particles_color_buffer);
glBindBuffer(GL_ARRAY_BUFFER, particles_color_buffer);
// Initialize with empty (NULL) buffer : it will be updated later, each frame.
glBufferData(GL_ARRAY_BUFFER, MaxParticles * 4 * sizeof(GLubyte), NULL, GL_STREAM_DRAW);

Ce qui est habituel. Ils sont mis à jour de cette façon :

// Update the buffers that OpenGL uses for rendering.
// There are much more sophisticated means to stream data from the CPU to the GPU,
// but this is outside the scope of this tutorial.
// http://www.opengl.org/wiki/Buffer_Object_Streaming

glBindBuffer(GL_ARRAY_BUFFER, particles_position_buffer);
glBufferData(GL_ARRAY_BUFFER, MaxParticles * 4 * sizeof(GLfloat), NULL, GL_STREAM_DRAW); // Buffer orphaning, a common way to improve streaming perf. See above link for details.
glBufferSubData(GL_ARRAY_BUFFER, 0, ParticlesCount * sizeof(GLfloat) * 4, g_particule_position_size_data);

glBindBuffer(GL_ARRAY_BUFFER, particles_color_buffer);
glBufferData(GL_ARRAY_BUFFER, MaxParticles * 4 * sizeof(GLubyte), NULL, GL_STREAM_DRAW); // Buffer orphaning, a common way to improve streaming perf. See above link for details.
glBufferSubData(GL_ARRAY_BUFFER, 0, ParticlesCount * sizeof(GLubyte) * 4, g_particule_color_data);

Ce qui est aussi habituel. Ils sont liés avant le rendu de cette façon :

// 1rst attribute buffer : vertices
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, billboard_vertex_buffer);
glVertexAttribPointer(
 0, // attribute. No particular reason for 0, but must match the layout in the shader.
 3, // size
 GL_FLOAT, // type
 GL_FALSE, // normalized?
 0, // stride
 (void*)0 // array buffer offset
);

// 2nd attribute buffer : positions of particles' centers
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, particles_position_buffer);
glVertexAttribPointer(
 1, // attribute. No particular reason for 1, but must match the layout in the shader.
 4, // size : x + y + z + size => 4
 GL_FLOAT, // type
 GL_FALSE, // normalized?
 0, // stride
 (void*)0 // array buffer offset
);

// 3rd attribute buffer : particles' colors
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, particles_color_buffer);
glVertexAttribPointer(
 2, // attribute. No particular reason for 1, but must match the layout in the shader.
 4, // size : r + g + b + a => 4
 GL_UNSIGNED_BYTE, // type
 GL_TRUE, // normalized? *** YES, this means that the unsigned char[4] will be accessible with a vec4 (floats) in the shader ***
 0, // stride
 (void*)0 // array buffer offset
);

Ce qui est aussi normal. La différence vient lors du rendu. Au lieu d’utiliser glDrawArrays (ou glDrawElements si votre modèle de base possède un tampon d’indices), on utilise glDrawArraysInstanced/glDrawElementsInstanced, qui est équivalent à appeler N fois glDrawArrays (N est le dernier paramètre, dans notre cas ParticlesCount).

glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, ParticlesCount);

Mais il manque quelque chose ici. On n’a pas indiqué à OpenGL quel buffer était pour le modèle de base et lesquels sont pour les différentes instances. Cela se fait avec glVertexAttribDivisor. Voici le code complet commenté :

// These functions are specific to glDrawArrays*Instanced*.
// The first parameter is the attribute buffer we're talking about.
// The second parameter is the "rate at which generic vertex attributes advance when rendering multiple instances"
// http://www.opengl.org/sdk/docs/man/xhtml/glVertexAttribDivisor.xml
glVertexAttribDivisor(0, 0); // particles vertices : always reuse the same 4 vertices -> 0
glVertexAttribDivisor(1, 1); // positions : one per quad (its center) -> 1
glVertexAttribDivisor(2, 1); // color : one per quad -> 1

// Draw the particules !
// This draws many times a small triangle_strip (which looks like a quad).
// This is equivalent to :
// for(i in ParticlesCount) : glDrawArrays(GL_TRIANGLE_STRIP, 0, 4),
// but faster.
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, ParticlesCount);

Comme vous pouvez le voir, l’instanciation est très polyvalente, car tu peux passer n’importe quel entier comme AttribDivisor. Par exemple, avec glVertexAttribDivisor(2, 10), chaque groupe de dix instances aura la même couleur.

Quel est le but ?

Le but est que maintenant, on doit seulement mettre à jour un petit buffer à chaque image (le centre des particules) et non plus un immense modèle. C’est un gain de 75 % pour la bande passante !

Vie et mort

Contrairement à la plupart des objets de la scène, les particules meurent et naissent à un grand débit. On doit trouver une méthode convenablement rapide pour obtenir de nouvelles particules et se débarrasser d’elles, quelque chose de mieux que new Particle().

Créer de nouvelles particules

Pour cela, on doit avoir un grand conteneur de particules :

// CPU representation of a particle
struct Particle{
    glm::vec3 pos, speed;
    unsigned char r,g,b,a; // Color
    float size, angle, weight;
    float life; // Remaining life of the particle. if < 0 : dead and unused.

};

const int MaxParticles = 100000;
Particle ParticlesContainer[MaxParticles];

Maintenant, on a besoin d’une méthode pour en créer. Cette fonction recherche linéairement dans ParticlesContainer, ce qui est une idée horrible, sauf qu’elle démarre au dernier emplacement connu, donc cette fonction retourne généralement immédiatement :

int LastUsedParticle = 0;

// Finds a Particle in ParticlesContainer which isn't used yet.
// (i.e. life < 0);
int FindUnusedParticle(){

    for(int i=LastUsedParticle; i<MaxParticles; i++){
        if (ParticlesContainer[i].life < 0){
            LastUsedParticle = i;
            return i;
        }
    }

    for(int i=0; i<LastUsedParticle; i++){
        if (ParticlesContainer[i].life < 0){
            LastUsedParticle = i;
            return i;
        }
    }

    return 0; // All particles are taken, override the first one
}

On peut maintenant remplir ParticlesContainer[particleIndex] avec des valeurs intéressantes pour life, color, speed et position. Voir le code pour les détails, mais tu peux faire ce que tu veux ici. La seule chose intéressante est, comment plusieurs particules doivent être générées à chaque image ? C’est principalement dépendant de l’application, donc prenons un taux de 10 000 nouvelles particules par seconde (oui, c’est plutot beaucoup) :

int newparticles = (int)(deltaTime*10000.0);

Sauf que on devrait restreindre cela a une nombre fixe :

// Generate 10 new particule each millisecond,
// but limit this to 16 ms (60 fps), or if you have 1 long frame (1sec),
// newparticles will be huge and the next frame even longer.
int newparticles = (int)(deltaTime*10000.0);
if (newparticles > (int)(0.016f*10000.0))
    newparticles = (int)(0.016f*10000.0);

Supprimer les vieilles particules

Il y a une astuce, comme montré ci-dessous =)

La boucle principale de simulation

ParticlesContainer contient aussi bien les particules actives que les « mortes », mais le tampon que l’on envoie au GPU ne doit avoir que les particules vivantes.

Donc, on va parcourir chaque particule, vérifier si elle est vivante, si elle doit mourir, et si tout est correct, ajouter un peu de gravité, puis finalement la copier dans un buffer GPU spécifique.

// Simulate all particles
int ParticlesCount = 0;
for(int i=0; i<MaxParticles; i++){

    Particle& p = ParticlesContainer[i]; // shortcut

    if(p.life > 0.0f){

        // Decrease life
        p.life -= delta;
        if (p.life > 0.0f){

            // Simulate simple physics : gravity only, no collisions
            p.speed += glm::vec3(0.0f,-9.81f, 0.0f) * (float)delta * 0.5f;
            p.pos += p.speed * (float)delta;
            p.cameradistance = glm::length2( p.pos - CameraPosition );
            //ParticlesContainer[i].pos += glm::vec3(0.0f,10.0f, 0.0f) * (float)delta;

            // Fill the GPU buffer
            g_particule_position_size_data[4*ParticlesCount+0] = p.pos.x;
            g_particule_position_size_data[4*ParticlesCount+1] = p.pos.y;
            g_particule_position_size_data[4*ParticlesCount+2] = p.pos.z;

            g_particule_position_size_data[4*ParticlesCount+3] = p.size;

            g_particule_color_data[4*ParticlesCount+0] = p.r;
            g_particule_color_data[4*ParticlesCount+1] = p.g;
            g_particule_color_data[4*ParticlesCount+2] = p.b;
            g_particule_color_data[4*ParticlesCount+3] = p.a;

        }else{
            // Particles that just died will be put at the end of the buffer in SortParticles();
            p.cameradistance = -1.0f;
        }

        ParticlesCount++;

    }
}

Voici ce que t’obtiens. Presque réussi, mais il y a un souci …

Tri

Comme expliqué dans le dixième tutoriel, tu dois trier les objets semi-transparents du plus loin au plus proche pour que le mélange soit correct.

void SortParticles(){
    std::sort(&ParticlesContainer[0], &ParticlesContainer[MaxParticles]);
}

Maintenant, std::sort nécessite une fonction qui peut indiquer si une particule doit être placée avant ou après une autre particule dans le conteneur. Ça peut être fait avec Particle::operator< :

// CPU representation of a particle
struct Particle{

    ...

    bool operator<(Particle& that){
        // Sort in reverse order : far particles drawn first.
        return this->cameradistance > that.cameradistance;
    }
};

Ça permet de trier ParticleContainer et les particules vont maintenant être affichées correctement[^1] :

Aller plus loin

Particules animées

Tu peux animer ta texture de particules avec une texture atlas. Envoie l’âge de chaque particule avec la position et dans les shaders, calcule les coordonnées UV comme on l’a fait dans le tutoriel de police 2D. Une texture atlas ressemble à ça :

Gérer plusieurs systèmes de particules

Si tu as besoin de plus d’un système de particules, tu as deux options : soit utiliser un seul ParticleContainer, soit un par système.

Si vous avez un seul conteneur pour toutes les particules, alors tu vas être capable de parfaitement les trier. Le principal inconvénient est que tu dois utiliser la même texture pour toutes les particules, ce qui est un gros problème. Cela peut être corrigé en utilisant une texture atlas (une grande texture avec toutes les différentes textures sur celle-ci, ayant simplement des coordonnées UV différentes), mais ce n’est pas vraiment pratique pour l’éditer et l’utiliser.

Si tu as un conteneur par système de particules, d’un autre côté, les particules seront seulement triées dans ces conteneurs : si deux systèmes de particules se superposent, des artefacts apparaîtront. Suivant ton application, ça peut ne pas être un souci.

Bien sûr, tu peux aussi utiliser un système hybride avec plusieurs systèmes de particules et chacun avec un (petit et gérable) atlas.

Particules douces

Tu vas remarquer très rapidement un artefact classique : lorsque tes particules croisent une géométrie, la limite devient très visible et moche :

(image provenant de http://www.gamerendering.com/2009/09/16/soft-particles/).

Une technique classique pour corriger cela est de tester si le fragment actuellement dessiné est proche du Z-buffer (buffer de profondeur). Si c’est le cas, le fragment est fondu (= tout de même rendu mais avec plus de transparence).

Par contre, tu vas devoir échantillonner le Z-buffer (buffer de profondeur), ce qui n’est pas possible avec le Z-buffer « normal ». Tu dois afficher ta scène dans une render target. Sinon, tu peux copier le Z-buffer à partir d’un framebuffer vers un autre avec glBlitFramebuffer.

http://developer.download.nvidia.com/whitepapers/2007/SDK10/SoftParticles_hi.pdf

Amélioration du taux de remplissage

L’un des facteurs les plus limitants dans les GPU modernes est le taux de remplissage : le nombre de fragments (pixels) que le GPU peut écrire en 16,6 ms, permis pour obtenir 60 FPS.

C’est un problème, car les particules nécessitent typiquement un GRAND taux de remplissage, comme tu dois redessiner le même fragment dix fois, chaque fois avec une particule spécifique ; et si tu ne le fait pas, tu vas obtenir les mêmes artefacts que précédemment.

Parmi tous les fragments qui sont écrits, la plupart sont complètement inutiles : ceux se trouvant sur la bordure. Vos textures de particules sont souvent complètement transparentes sur les bordures, mais le modèle de particule continuera de les dessiner - et mettre à jour le buffer de couleur avec exactement la même valeur que précédemment.

Ce petit utilitaire calcule un modèle (celui que tu es supposé dessiner avec glDrawArrayInstanced()), qui correspond précisément à ta texture :

http://www.humus.name/index.php?page=Cool&ID=8. Le site de Emil Person contient plein d’autres articles fascinant.

Physique des particules

Au bout d’un moment, tu vas probablement souhaiter que tes particules interagissent un peu plus avec le reste de l’environement. En particulier, les particules pourraient rebondir sur le sol.

Tu pourrais simplement lancer un rayon pour chaque particule, entre la position actuelle et la prochaine ; on apprend à le faire dans les tutoriels de Picking. Mais c’est très coûteux. simplement, tu ne peux pas le faire pour chaque particule, à chaque image, et garder un bon fps.

Suivant ton application, tu peux aussi approximer ta géométrie avec un ensemble de plans et faire un lancer de rayon sur ces plans uniquement ; ou tu peux utiliser un vrai lancer de rayon, mais mettre en cache le résultat et approcher les collisions alentour avec le cache (ou tu peux faire les deux).

Une technique complètement différente est d’utiliser le Z-buffer existant tel une approximation très brute de la géométrie (visible) et faire collisionner les particules avec celle- ci. C’est « assez efficace » et rapide, mais tu vas devoir effectuer toute la simulation sur le GPU, car tu ne peux pas accéder au tampon de profondeur à partir du CPU (du moins, pas rapidement), donc c’est bien plus compliqué.

Voici quelques liens sur ces techniques :

http://www.altdevblogaday.com/2012/06/19/hack-day-report/

http://www.gdcvault.com/search.php#&category=free&[…]

Simulation GPU

Comme indiqué précédemment, tu peux complètement simuler le mouvement des particules sur le GPU. Tu vas toujours devoir gérer le cycle de vie de tes particules sur le CPU - au moins pour les faire apparaître.

Tu as plusieurs options pour ce faire et aucune ne rentre dans le champ de ce tutoriel ; je vais juste donner quelques pointeurs.

  • Utiliser le retour de transformation (Transform Feedback). Cela te permet de stocker les sorties du vertex shader dans un VBO côté GPU. Stocke la nouvelle position dans ce VBO et à la prochaine image, utilise ce VBO comme point de départ et stocke les nouvelles positions dans le premier VBO.
  • Même chose, mais sans le retour de transformation : encode vos positions de particules dans une texture et mettez-la à jour en faisant un rendu dans cette texture (tutoriel 14).
  • Utiliser une bibliothèque GPU généraliste : CUDA ou OpenCL, qui possède des fonctions d’interopérabilité avec OpenGL.
  • Utilisez un compute shader. La solution la plus propre, mais seulement disponible sur les GPU récents (en 2012).

[^1] Pour des raisons de simplicité, dans cette implémentation, ParticleContainer est trié après la mise à jour des tampons GPU. Cela fait que les particules ne sont pas exactement triées (il y a un délai d’une image), mais ce n’est pas vraiment remarquable. Tu peux corriger cela en séparant la boucle principale en deux. Simule, trie puis met à jour.

  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Picking avec un hack OpenGL

Cette technique n’est pas vraiment conseillée, mais c’est une méthode facile et rapide pour ajouter un picking1 simple. Dans tous les cas, évite d’utiliser cela dans les jeux, car cela peut provoquer une chute de FPS. Toutefois, si tu as une simulation et que tu ne te soucie pas des performances liées au picking, cette technique peut être la solution parfaite.

Le code source pour ce tutoriel est disponible dans le fichier au nom lourd de sens : misc05_picking/misc05_picking_slow_easy.cpp.

Idée de base

L’idée derrière cette technique est d’afficher la scène, comme d’habitude, mais au lieu d’utiliser un joli rendu, on dessine chaque objet avec une couleur spécifique et unique.

Ensuite, on récupére la couleur du pixel sous le curseur de la souris et on utilise cette couleur pour identifier l’objet. On obtient ainsi l’objet sur lequel l’utilisateur a cliqué.

Voici un exemple :

Dans cette capture d’écran, chaque singe possède une couleur légèrement différente permettant de les identifier.

Bien sûr, tu ne veux pas voir l’image avec toutes ces couleurs étranges donc tu dois nettoyer l’écran et redessiner comme d’habitude.

Implémentation

Donner un identifiant à chaque objet

Chaque objet de la scène doit avoir un identifiant unique. La façon la plus facile de le faire est de donner à chaque objet un entier et de le convertir en une couleur. Cette couleur ne doit pas avoir de sens ; cette technique est simplement un hack de toute façon.

Dans le code source accompagnant ce tutoriel, 100 objets sont créés et conservés dans un std::vector dont l’identifiant n’est que l’index de l’objet dans ce vecteur. Si tu as une hiérarchie plus complexe, tu dois sans doute ajouter l’identifiant dans ta classe de Mesh et maintenir une sorte de std::map pour associer l’identifiant à l’objet.

Détecter le clic

Dans cet exemple simple, le picking est effectué à chaque image lorsque le bouton gauche de la souris est appuyé :

        if (glfwGetMouseButton(GLFW_MOUSE_BUTTON_LEFT)){

Dans une application réelle, tu veux certainement le faire uniquement lorsque l’utilisateur relâche le bouton et donc tu dois garder un booléen estCeQueLeBoutonGaucheAEteAppuyeLorsDeLImagePrecedente, ou mieux, utiliser glfwSetMouseButtonCallback() (lis le manuel GLFW pour savoir comment l’utiliser).

Convertir l’identifiant en une couleur spécifique

Comme on doit afficher chaque modèle avec une couleur différente, la première étape est de calculer cette couleur. Une méthode simple est de placer les bits de poids faible dans le canal rouge et les bits de poids fort dans le canal bleu :

// Convert "i", the integer mesh ID, into an RGB color
int r = (i & 0x000000FF) >>  0;
int g = (i & 0x0000FF00) >>  8;
int b = (i & 0x00FF0000) >> 16;

Ça peut paraître effrayant, mais c’est un code de manipulation de bits standard. Tu obtiens finalement trois entiers, chacun compris entre 0 et 255. Avec cette méthode, tu peux représenter 255^3 = 16 millions d’objets différents, ce qui est sans doute suffisant.

Afficher la scène avec ces couleurs

On a maintenant besoin d’un shader pour utiliser cette couleur. C’est très simple. Le vertex shader ne fait rien :

#version 330 core

// Input vertex data, different for all executions of this shader.
layout(location = 0) in vec3 vertexPosition_modelspace;

// Values that stay constant for the whole mesh.
uniform mat4 MVP;

void main(){

    // Output position of the vertex, in clip space : MVP * position
    gl_Position =  MVP * vec4(vertexPosition_modelspace,1);

}

Et le fragment shader écrit simplement la couleur voulue dans le framebuffer :

#version 330 core

// Ouput data
out vec4 color;

// Values that stay constant for the whole mesh.
uniform vec4 PickingColor;

void main(){

    color = PickingColor;

}

Facile !

La seule astuce est que tu dois envoyer tes couleurs en nombres flottants (compris dans [0, 1]) alors que tu as des entiers (compris dans [0, 255]), donc tu dois effectuer une petite division lors de l’appel à glUniformXX() :

// OpenGL expects colors to be in [0,1], so divide by 255.
glUniform4f(pickingColorID, r/255.0f, g/255.0f, b/255.0f, 1.0f);

Tu peux maintenant dessiner les modèles comme d’habitude (glBindBuffer, glVertexAttribBuffer, glDrawElements) et tu obtiendra l’étrange image ci-dessus.

Obtenir la couleur sous le curseur de souris

Lorsque tu as dessiné tous les modèles (certainement avec une boucle for()), tu dois appeler glReadPixels(), permettant d’obtenir les pixels rasterizés sur le CPU. Mais pour que cela fonctionne, quelques étapes supplémentaires sont nécessaires.

Premièrement, tu dois appeler glFlush(). Cela indiquera au pilote OpenGL d’envoyer les commandes en attente (incluant votre dernier glDrawXX) au GPU. Cela n’est typiquement pas fait automatiquement, car les commandes sont envoyées en groupe et non immédiatement (cela signifie que lorsque tu appels glDrawElements(), rien n’est dessiné. Cela VA être dessiné quelques millisecondes plus tard). Cette opération est LENTE.

Ensuite, tu dois appeler glFinish(), qui attendra jusqu’à ce que tout soit vraiment affiché. La différence avec glFlush() est que glFlush() envoie simplement les commandes ; glFinish() attend que les commandes soient exécutées. Cette opération est LEEENTE.

Tu dois aussi configurer comment glReadPixels() se comportera par rapport à l’alignement mémoire. C’est quelque peu hors sujet, mais simplement, tu dois appeler glPixelStore(GL_UNPACK_ALIGNMENT, 1).

Finalement, tu peux appeler glReadPixels() ! Voici le code complet :

// Wait until all the pending drawing commands are really done.
// Ultra-mega-over slow !
// There are usually a long time between glDrawElements() and
// all the fragments completely rasterized.
glFlush();
glFinish();

glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

// Read the pixel at the center of the screen.
// You can also use glfwGetMousePos().
// Ultra-mega-over slow too, even for 1 pixel,
// because the framebuffer is on the GPU.
unsigned char data[4];
glReadPixels(1024/2, 768/2,1,1, GL_RGBA, GL_UNSIGNED_BYTE, data);

Ta couleur est maintenant dans le tableau data. Par exemple ci-dessous, tu peux voir que l’identifiant est 19 :

Convertir la couleur en un identifiant

Tu peux maintenant retrouver ton identifiant à partir du tampon data. Le code est exactement l’opposé du code de conversion de l’identifiant en une couleur :

// Convert the color back to an integer ID
int pickedID =
    data[0] +
    data[1] * 256 +
    data[2] * 256*256;

Utiliser cet identifiant

Tu peux maintenant utiliser l’identifiant comme tu veux. Dans l’exemple, le texte de l’interface est mis à jour, mais bien sûr, tu peux faire ce que tu veux.

if (pickedID == 0x00ffffff){ // Full white, must be the background !
    message = "background";
}else{
    std::ostringstream oss; // C++ strings suck
    oss << "mesh " << pickedID;
    message = oss.str();
}

Avantages et inconvénients

Avantages :

  • Facile et rapide à implémenter
  • Pas besoin de bibliothèque externe ni de mathématique complexe

Inconvénients :

  • Utilise glFlush(), glFinish(), glReadPixels() qui sont toutes des fonctions lentes, car elles forcent le CPU à attendre le GPU, ce qui ruine les performances
  • Tu n’as pas une information précise : quel triangle est touché, la normale en ce point, etc

Remarques finales

Bien que cela ne soit pas recommandé, cette technique peut être vraiment utile, mais elle est complètement restreinte au picking. Les méthodes dans les deux prochains tutoriels peuvent être utilisées pour d’autres buts, comme la détection des collisions, faire marcher un avatar sur le sol, effectuer des requêtes de visibilité pour l’intelligence artificielle, etc.

Si finalement tu utilises cette technique et que tu veux obtenir plusieurs points dans une même image, tu dois obtenir ces points en une seule fois. Par exemple, si tu veux gérer cinq points de toucher, ne dessinez pas cinq fois la scène !

  1. Picker c’est détecter quel objet se trouve sous le pointeur de la souris. Le picking c’est donc la détection de l’objet sous le pointeur de la souris ↩

  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Picking avec une bibliothèque physique

Dans ce tutoriel, nous allons voir la méthode « recommandée » pour picker1 dans un moteur de jeu classique - ce qui peut ne pas être ton cas.

L’idée est que le moteur de jeu devra intégrer un moteur physique dans tous les cas et tous les moteurs physiques ont des fonctions pour obtenir l’intersection d’un rayon avec la scène. En plus, ces fonctions sont certainement mieux optimisées que ce que tu peux faire toi-même : tous les moteurs physiques utilisent des structures de partitionnement de l’espace évitant de tester les intersections avec les objets qui ne sont pas dans la même région.

Dans ce tutoriel, nous allons utiliser le moteur Bullet Physics Engine, mais les concepts sont identiques pour n’importe quel autre : PhysX, HavoK, etc.

Intégration de Bullet

De nombreux tutoriels expliquent comment intégrer Bullet ; en particulier, le wiki de Bullet est très bien fait.

// Initialize Bullet. This strictly follows http://bulletphysics.org/mediawiki-1.5.8/index.php/Hello_World,
// even though we won't use most of this stuff.

// Build the broadphase
btBroadphaseInterface* broadphase = new btDbvtBroadphase();

// Set up the collision configuration and dispatcher
btDefaultCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration();
btCollisionDispatcher* dispatcher = new btCollisionDispatcher(collisionConfiguration);

// The actual physics solver
btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver;

// The world.
btDiscreteDynamicsWorld* dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher,broadphase,solver,collisionConfiguration);
dynamicsWorld->setGravity(btVector3(0,-9.81f,0));

Chaque objet doit avoir une forme de collision (Collision Shape). Même si celle-ci peut être le modèle en lui-même, cela est généralement une mauvaise idée pour les performances. À la place, on utilise habituellement des formes plus simples telles que les boîtes, les sphères ou les capsules. Voici quelques formes de collision. De gauche à droite : sphère, cube, convex hull du modèle, modèle d’origine. Les sphères sont moins précises que le modèle, mais bien plus rapides à tester.

Dans cet exemple, tous les modèles utilisent la même boîte :

btCollisionShape* boxCollisionShape = new btBoxShape(btVector3(1.0f, 1.0f, 1.0f));

Les moteurs physiques ne connaissent rien sur OpenGL ; et en réalité, ceux-ci peuvent s’exécuter sans aucune visualisation 3D. Donc on ne peut pas directement donner notre VBO à Bullet. À la place, on doit ajouter un corps rigide (Rigid Body) dans la simulation.

btDefaultMotionState* motionstate = new btDefaultMotionState(btTransform(
    btQuaternion(orientations[i].x, orientations[i].y, orientations[i].z, orientations[i].w),
    btVector3(positions[i].x, positions[i].y, positions[i].z)
));

btRigidBody::btRigidBodyConstructionInfo rigidBodyCI(
    0,                  // mass, in kg. 0 -> Static object, will never move.
    motionstate,
    boxCollisionShape,  // collision shape of body
    btVector3(0,0,0)    // local inertia
);
btRigidBody *rigidBody = new btRigidBody(rigidBodyCI);

dynamicsWorld->addRigidBody(rigidBody);

On peut voir que le corps rigide utilise une forme de collision pour définir sa forme.

On garde aussi une trace du corps rigide, mais comme il est indiqué dans le commentaire, un vrai moteur aura d’une quelconque façon une classe MonGameObject avec la position, l’orientation, le modèle OpenGL et un pointeur vers le corps rigide.

rigidbodies.push_back(rigidBody);

// Small hack : store the mesh's index "i" in Bullet's User Pointer.
// Will be used to know which object is picked.
// A real program would probably pass a "MyGameObjectPointer" instead.
rigidBody->setUserPointer((void*)i);

En d’autres mots : n’utilise pas le code ci-dessus dans la vraie vie ! Ce n’est que pour les besoins de la démonstration.

Lancer de rayon

Trouver la position/direction du rayon

Premièrement, on doit trouver un rayon qui commence à la position de la caméra et va « à travers la souris ». Cela est fait dans la fonction ScreenPosToWorldRay().

D’abord, on trouve la position de départ et de fin du rayon dans les coordonnées normalisées du périphérique (Normalized Device Coordinates). On le fait dans cet espace, car c’est très simple :

// The ray Start and End positions, in Normalized Device Coordinates (Have you read Tutorial 4 ?)
glm::vec4 lRayStart_NDC(
    ((float)mouseX/(float)screenWidth  - 0.5f) * 2.0f, // [0,1024] -> [-1,1]
    ((float)mouseY/(float)screenHeight - 0.5f) * 2.0f, // [0, 768] -> [-1,1]
    -1.0, // The near plane maps to Z=-1 in Normalized Device Coordinates
    1.0f
);
glm::vec4 lRayEnd_NDC(
    ((float)mouseX/(float)screenWidth  - 0.5f) * 2.0f,
    ((float)mouseY/(float)screenHeight - 0.5f) * 2.0f,
    0.0,
    1.0f
);

Pour comprendre ce code, voila une nouvelle fois à cette image du quatrième tutoriel :

L’espace de coordonnées normalisé du périphérique est un cube 222, centré sur l’origine, donc dans cet espace, le rayon « passant par la souris » n’est qu’une ligne droite, perpendiculaire au plan proche ! Cela rend IRayStart_NDC et IEndStart_NDC facile à calculer.

Maintenant, on n’a qu’à appliquer la transformation inverse :

// The Projection matrix goes from Camera Space to NDC.
// So inverse(ProjectionMatrix) goes from NDC to Camera Space.
glm::mat4 InverseProjectionMatrix = glm::inverse(ProjectionMatrix);

// The View Matrix goes from World Space to Camera Space.
// So inverse(ViewMatrix) goes from Camera Space to World Space.
glm::mat4 InverseViewMatrix = glm::inverse(ViewMatrix);

glm::vec4 lRayStart_camera = InverseProjectionMatrix * lRayStart_NDC;    lRayStart_camera/=lRayStart_camera.w;
glm::vec4 lRayStart_world  = InverseViewMatrix       * lRayStart_camera; lRayStart_world /=lRayStart_world .w;
glm::vec4 lRayEnd_camera   = InverseProjectionMatrix * lRayEnd_NDC;      lRayEnd_camera  /=lRayEnd_camera  .w;
glm::vec4 lRayEnd_world    = InverseViewMatrix       * lRayEnd_camera;   lRayEnd_world   /=lRayEnd_world   .w;

// Faster way (just one inverse)
//glm::mat4 M = glm::inverse(ProjectionMatrix * ViewMatrix);
//glm::vec4 lRayStart_world = M * lRayStart_NDC; lRayStart_world/=lRayStart_world.w;
//glm::vec4 lRayEnd_world   = M * lRayEnd_NDC  ; lRayEnd_world  /=lRayEnd_world.w;

Avec IRayStart_worldspace et IRayEnd_worldspace, la direction du rayon (dans l’espace monde) est facile à calculer :

glm::vec3 lRayDir_world(lRayEnd_world - lRayStart_world);
lRayDir_world = glm::normalize(lRayDir_world);

Utiliser RayTest()

Le lancer de rayon est très simple, aucun besoin de commentaire :

glm::vec3 out_end = out_origin + out_direction*1000.0f;

btCollisionWorld::ClosestRayResultCallback RayCallback(
    btVector3(out_origin.x, out_origin.y, out_origin.z),
    btVector3(out_end.x, out_end.y, out_end.z)
);
dynamicsWorld->rayTest(
    btVector3(out_origin.x, out_origin.y, out_origin.z),
    btVector3(out_end.x, out_end.y, out_end.z),
    RayCallback
);

if(RayCallback.hasHit()) {
    std::ostringstream oss;
    oss << "mesh " << (int)RayCallback.m_collisionObject->getUserPointer();
    message = oss.str();
}else{
    message = "background";
}

La seule chose est que pour une raison étrange, tu dois définir la position de départ et de fin du rayon, deux fois.

C’est tout, tu sais comment implémenter le picking1 dans Bullet !

Avantages et inconvénients

Avantages :

  • Très simple lorsque tu as déjà un moteur physique
  • Rapide
  • N’impacte pas les performances OpenGL

Inconvénients :

  • Probablement pas la bonne solution si tu n’as pas besoin de simulation physique ou d’un moteur de collision.

Remarques finales

Tous les moteurs physiques possèdent une vue de débogage. Le code d’exemple montre comment l’activer avec Bullet. Tu obtiendras une représentation de ce que Bullet sait sur votre scène, ce qui est vraiment très utile pour déboguer les problèmes liés à la physique, notamment pour être sûr que le « monde visuel » est consistant avec le « monde physique ».

La boîte verte est la forme de collision, à la même position et orientation que le modèle. La boîte rouge est la boîte englobante alignée sur les axes (AABB), qui est utilisée pour un rapide test de réjection : si le rayon ne touche pas le AABB (très facile à calculer), alors il ne touchera pas la forme de collision. Finalement, tu peux voir les axes de l’objet en bleu et rouge (regarde au nez et à l’oreille). Pratique !

  1. Picker c’est détecter quel objet se trouve sous le pointeur de la souris. Le picking c’est donc la détection de l’objet sous le pointeur de la souris ↩ ↩2

  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Picking with custom Ray-OBB function

La dernière méthode est un sympathique compromis entre l’implémentation OpenGL « hacky » et l’utilisation d’un moteur physique complet pour de simples lancers de rayons.

Ce tutoriel utilise les concepts et les fonctions du tutoriel de Bullet, donc lis le avant celui-ci.

L’idée de base

Au lieu de dépendre de l’intersection d’un rayon contre une forme de collision dans Bullet, on va l’implémenter nous-mêmes.

Comme nous l’avons vu, il y a plusieurs formes de collision possibles. Les sphères sont très simples à intersecter, mais pour de nombreux objets, elles représentent très mal les modèles. D’un autre côté, l’intersection d’un rayon avec tous les triangles du modèle original est très couteuse. Les boîtes englobantes orientées (Oriented Bounding Box (OBB)) sont un bon compromis. Elles sont assez précises (mais cela dépend de tes géométries) et sont plutôt rapides à calculer.

Une OBB est une boîte englobant le modèle et lorsque le modèle est déplacé ou tourné, la même transformation est appliquée à la boîte :

Algorithme d’intersection rayon-OBB

(L’algorithme et les figures s’inspirent grandement du livre Real-Time Rendering Vol. 3. Achete ce livre !)

On a l’OBB ci-dessous. Sur l’axe des X, il est délimité par deux plans verticaux, ici en rouge. Lorsqu’ils sont intersectés par le rayon (une opération très simple), cela donne deux intersections, une « proche » et une « lointaine » :

Lorsque le rayon intersecte les deux autres plans qui délimitent l’axe Y (en vert), cela donne deux intersections supplémentaires. Remarque comment les intersections sont ordonnées : tu entres dans la zone verte -> tu sors de la zone verte -> tu entres dans la zone rouge -> tu sors de la zone rouge. Cela signifie qu’il n’y a pas d’intersection.

Mais si l’ordre change (tu entres dans la zone verte -> tu entres dans la zone rouge), alors tu sais qu’il y a une intersection !

Mettons ça en pratique.

Implémentation de l’algorithme

(Le code complet est disponible dans le fichier Misc05/misc05_picking_custom.cpp)

Notre fonction d’intersection rayon - OBB ressemblera à cela :

bool TestRayOBBIntersection(
    glm::vec3 ray_origin,        // Origine du rayon, dans le repère du monde
    glm::vec3 ray_direction,     // Direction du rayon (PAS la cible !), dans le repère du monde. Doit être normalisé
    glm::vec3 aabb_min,          // Coordonnées minimales X,Y,Z du modèle lorsqu'il n'est pas transformé du tout
    glm::vec3 aabb_max,          // Coordonnées maximales X,Y,Z. Souvent aabb_min*-1 si votre modèle est centré, mais ce n'est pas toujours le cas
    glm::mat4 ModelMatrix,       // Transformation appliquée au modèle (et qui sera donc appliquée à la boîte englobante)
    float& intersection_distance // Résultat : la distance entre ray_origin et l'intersection avec l'OBB
){

On commençe par initialiser quelques variables. tMin est la plus grande intersection « proche » actuellement trouvée ; tMax est la plus petite intersection « lointaine » trouvée jusqu’à présent. Delta est utilisé pour calculer les intersections avec les plans.

float tMin = 0.0f;
float tMax = 100000.0f;

glm::vec3 OBBposition_worldspace(ModelMatrix[3].x, ModelMatrix[3].y, ModelMatrix[3].z);

glm::vec3 delta = OBBposition_worldspace - ray_origin;

Maintenant, calculons l’intersection entre les deux plans qui délimitent l’OBB sur l’axe des X :

glm::vec3 xaxis(ModelMatrix[0].x, ModelMatrix[0].y, ModelMatrix[0].z);
float e = glm::dot(xaxis, delta);
float f = glm::dot(ray_direction, xaxis);

// Beware, don't do the division if f is near 0 ! See full source code for details.
float t1 = (e+aabb_min.x)/f; // Intersection with the "left" plane
float t2 = (e+aabb_max.x)/f; // Intersection with the "right" plane

t1 et t2 contiennent maintenant les distances entre l’origine du rayon et les intersections avec le plan, mais on ne sait pas dans quel ordre, donc on s’assure que t1 représente l’intersection « proche » et t2 l’intersection « lointaine » :

if (t1>t2){ // if wrong order
    float w=t1;t1=t2;t2=w; // swap t1 and t2
}

On peut mettre à jour tMin et tMax :

// tMax est l'intersection « lointaine » la plus proche (parmi les paires de plans X,Y et Z)
if ( t2 < tMax ) tMax = t2;
// tMin est l'intersection « proche » la plus lointaine (parmi les paires de plans X,Y et Z)
if ( t1 > tMin ) tMin = t1;

Et voici une astuce : si l’intersection « lointaine » est plus proche que l’intersection « proche », il n’y a PAS d’intersection.

if (tMax < tMin )
    return false;

Ça c’est pour l’axe des X. C’est exactement la même chose pour les autres axes !

Utiliser l’algorithme

La fonction TestRayOBBIntersections() permet de tester l’intersection avec une seule OBB, donc on doit toutes les tester. Dans ce tutoriel, on test simplement les boîtes les unes après les autres, mais si tu as plein d’objets, tu pourrais avoir besoin d’une structure accélératrice comme un arbre binaire de partitionnement de l’espace (Binary Space Partitionning Tree BSP-Tree) ou une hiérarchie de volume englobant (Bounding Volume Hierarchy BVH).

for(int i=0; i<100; i++){

    float intersection_distance; // Output of TestRayOBBIntersection()
    glm::vec3 aabb_min(-1.0f, -1.0f, -1.0f);
    glm::vec3 aabb_max( 1.0f,  1.0f,  1.0f);

    // The ModelMatrix transforms :
    // - the mesh to its desired position and orientation
    // - but also the AABB (defined with aabb_min and aabb_max) into an OBB
    glm::mat4 RotationMatrix = glm::toMat4(orientations[i]);
    glm::mat4 TranslationMatrix = translate(mat4(), positions[i]);
    glm::mat4 ModelMatrix = TranslationMatrix * RotationMatrix;

    if ( TestRayOBBIntersection(
        ray_origin,
        ray_direction,
        aabb_min,
        aabb_max,
        ModelMatrix,
        intersection_distance)
    ){
        std::ostringstream oss;
        oss << "mesh " << i;
        message = oss.str();
        break;
    }
}

Remarque: cet algorithme a un problème ! il prend la première OBB qu’il trouve. Mais si cette OBB est derrière une autre, cela est faux. Donc tu dois prendre seulement l’OBB la plus proche ! C’est au lecteur de résoudre ce problème …

Avantages et inconvénients

Pros :

  • Facile
  • Nécessite peu de mémoire (seulement le surplus de l’OBB)
  • Ne ralentit pas OpenGL comme le faisait la première version

Inconvénients :

  • Plus lent qu’avec le moteur physique, car il n’y a pas de structure accélératrice
  • Peut ne pas être assez précis

Remarques finales

Il y a de nombreuses autres routines d’intersection disponibles pour toutes sortes de formes de collisions ; lis par exemple http://www.realtimerendering.com/intersections.html.

Si tu as besoin d’une intersection précise, tu dois effectuer un test rayon-triangle. Encore une fois, ce n’est pas une bonne idée de vérifier tous les triangles de tous les modèles linéairement. Une structure accélératrice supplémentaire est nécessaire.

  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Traduction

Tu veux traduire juste une page ? Pas de soucis ! Toute une série de tutoriels ? Encore mieux !

Tu veux complèter une traduction déjà existante :

Clone la branche “gh-pages” sur le dépôt GitHub ici. Trouve la page que tu veux éditer, modifie la, et envoie la nous simplement par courriel ou en faisant une pull request, comme tu préféres.

Trouver la page que tu veux éditer est super facile. Par example si tu veux traduire la page en japonais: For example if you want to translate that page in japanese: https://github.com/opengl-tutorials/ogl/tree/gh-pages/jp/intermediate-tutorials/tutorial-9-vbo-indexing Remarque le chemin de la page: “jp/intermediate-tutorials/tutorial-9-vbo-indexing/” Ensuite va dans le dépôt GitHub, sous la branche gh-pages, et tu va trouver le fichier index.markdown dans le même chemin : https://github.com/opengl-tutorials/ogl/tree/gh-pages/jp/intermediate-tutorials/tutorial-9-vbo-indexing

Tu veux commencer une traduction dans un nouveau langage :

Envoie nous un courriel, on va créer les nouvelles pages (avec le contenu en anglais) au bon endroit sur le site. Ensuite il ne te restera plus qu’a faire comme ci-dessus: Editer les fichiers à traduire, et nous les renvoyer par courriel ou par pull request.

Merci !!

  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Tutoriel 1 : ouvrir une fenêtre

Introduction

Bienvenue dans le premier tutoriel !

Avant de s’attaquer à OpenGL, tu dois d’abord apprendre à compiler le code accompagnant chaque tutoriel, apprendre à l’exécuter, et plus important, apprendre avec le code.

Prérequis

Aucun prérequis n’est nécessaire pour suivre ces tutoriels. Une expérience avec n’importe quel langage de programmation (C, Java, Lisp, JavaScript, ou autre) est préférable pour comprendre le code, mais pas nécessaire ; il sera simplement plus difficile d’apprendre deux choses à la fois.

Tous les tutoriels sont écrits en « C++ simple » : beaucoup d’efforts ont été faits pour rendre le code aussi simple que possible. Pas de templates, pas de classes, pas de pointeurs. De cette façon, tu vas pouvoir tout comprendre même si tu ne connais que le Java.

Oublie tout

Tu n’as rien besoin de connaître, mais tu dois oublier tout ce que tu connais sur OpenGL. Si tu connais quelque chose qui ressemble à glBegin(), oublie-le. Ici tu vas apprendre l’OpenGL moderne (OpenGL 3 et 4), et la plupart des tutoriels en ligne apprennent le vieux OpenGL (OpenGL 1 et 2). Donc oublie tout ce que tu pourrais savoir, avant que ton cerveau ne fonde face à ce mélange.

Compiler les tutoriels

Tous les tutoriels peuvent être compilés sur Windows, Linux et Mac. Pour ces plates-formes, la procédure est grossièrement la même :

  • Mets à jour tes pilotes !! Fais-le. Tu as été prévenus
  • Télécharge un compilateur, si tu n’en as pas encore
  • Installe CMake
  • Télécharge le code source des tutoriels
  • Génére un projet avec CMake
  • Compile le projet
  • Joue avec les exemples

Compilation sous Windows

  • La mise à jour de tes pilotes devrait être simple. Va sur le site de NVIDIA ou AMD et télécharge les pilotes. Si tu n’es pas sûr de connaître ton modèle de carte graphique : Panneau de configuration → Système et Sécurité → Système → Gestionnaire de périphériques → Cartes graphiques. Si tu as un GPU intégré Intel, les pilotes sont généralement fournis par votre OEM (Dell, HP…).
  • On suggère d’utiliser Visual Studio 2010 Express comme compilateur. Tu peux le télécharger gratuitement ici. Si tu préféres utiliser MinGW, on recommande d’utiliser Qt Creator. Installe celui que tu veux. Les étapes suivantes seront expliquées pour Visual Studio mais devraient être similaires pour tout autre EDI.
  • Télécharge CMake et installe-le.
  • Télécharge le code source et décompresse-le, par exemple dans C:\Users\XYZ\Projects\OpenGLTutorials .

  • Lance CMake. Dans la première ligne, navigue jusqu’au dossier décompressé. Si tu n’es pas sûr, choisis le dossier contenant le fichier CMakeLists.txt. Dans la seconde ligne, entre le chemin où tu veux que les fichiers spécifiques au compilateur soient généré. Par exemple, tu peux choisir C:\Users\XYZ\Projects\OpenGLTutorials-build-Visual2010-32bits\, ou C:\Users\XYZ\Projects\OpenGLTutorials\build\Visual2010-32bits . Cela peut être n’importe où et pas nécessairement dans le même dossier.

  • Clique sur le bouton « Configurer » (« Configure »). Comme c’est la première fois que tu configure le projet, CMake te demandera quel compilateur tu veux utiliser. Choisisse-le sagement suivant la première étape. Si tu as un Windows 64 bits, tu peux choisir 64 bits ; si tu n’es pas sur, choisis 32 bits.
  • Clique sur « Configurer » (« Configure ») jusqu’à ce que les lignes rouges disparaissent. Clique sur « Générer » (« Generate »). Ton projet Visual Studio est maintenant créé. Tu peux oublier CMake ou même le désinstaller si tu as envie.
  • Ouvre C:\Users\XYZ\Projects\OpenGLTutorials-build-Visual2010-32bits . Tu vas voir un fichier Tutorials.sln : ouvre-le avec Visual Studio. Dans le menu Générer (Build), clique sur Tout générer (Build All). Tous les tutoriels et les dépendances vont être compilés. Chaque exécutable sera aussi copié dans C:\Users\XYZ\Projects\OpenGLTutorials. Avec un peu de chance, aucune erreur n’apparaît.
  • Ouvre C:\Users\XYZ\Projects\OpenGLTutorials\playground et lance playground.exe. Une fenêtre noire devrait apparaître.

Tu peux aussi démarrer n’importe quel tutoriel à partir de Visual Studio. Fait un clic droit sur Playground et choisis Définir comme projet de démarrage (Choose as startup project). Tu peux maintenant déboguer le code en appuyant sur F5.

Compilation sous Linux

Il y a tellement de variantes disponibles de Linux qu’il est impossible de lister toutes les plates-formes possibles. Adapte les instructions si nécessaire et n’hésite pas à lire la documentation de ta distribution.

  • Installe les derniers pilotes. On recommande chaudement les pilotes binaires propriétaires. Ce n’est pas contre GNU ou autre, mais au moins ils fonctionnent. Si ta distribution ne fournit pas d’installation automatique, essaye le guide d’Ubuntu.
  • Installe tous les compilateurs, outils et bibliothèques nécessaires. La liste complète est : cmake make g++ libx11-dev libgl1-mesa-dev libglu1-mesa-dev libxrandr-dev libxext-dev. Utilise sudo apt-get install *** ou su && yum install ****.
  • Télécharge le code source et décompresse-le, par exemple dans ~/Projects/OpenGLTutorials/.
  • cd dans ~/Projects/OpenGLTutorials/ et entre les commandes suivantes :
  • mkdir build
  • cd build
  • cmake ..
  • Un makefile a été créé dans le répertoire build/.
  • Tape « make all ». Chaque tutoriel et dépendance sera compilé. Chaque exécutable sera aussi copié dans ~/Projects/OpenGLTutorials/. Avec un peu de chance, aucune erreur n’apparaît.
  • Ouvre ~/Projects/OpenGLTutorials/playground et lance ./playground. Une fenêtre noire devrait apparaître.

Tu devrais vraiment utiliser un IDE comme Qt Creator. En particulier, celui-ci supporte les projets CMake nativement et fournira une meilleure expérience au débogage. Voici les instructions pour Qt Creator :

  • Dans Qt Creator, va dans « Fichier » (« File ») → « Outils » (« Tools ») → « Options » → « Compilation et exécution » (« Compile&Execute) → CMake
  • Définis le chemin vers CMake. C’est probablement /usr/bin/cmake
  • Fichier→Ouvrir projet ; Sélectionne tutorials/CMakeLists.txt
  • Sélectionne un répertoire de compilation, si possible en dehors du répertoire des tutoriels
  • Optionel: Définis -DCMAKE_BUILD_TYPE=Debug dans la boite des paramètres et valide
  • Clique sur le marteau en bas. Les tutoriels peuvent maintenant être lancés à partir du répertoire tutorials/
  • Pour exécuter les tutoriels dans Qt Creator, clique sur Projets→Paramètres d’exécution→Répertoire de travail et sélectionne le répertoire où les shaders, textures et modèles sont. Par exemple, pour le second tutoriel : ~/opengl-tutorial/tutorial02_red_triangle/.

Compilation sur Mac

La procédure est très similaire à celle de Windows (les Makefiles sont aussi supportés, mais on ne l’expliquera pas ici) :

  • Installe XCode à partir du Mac App Store
  • Télécharge CMake et installe le fichier .dmg. Vous n’avez pas besoin d’installer les outils en ligne de commande
  • Télécharge le code source et décompressez-le, par exemple dans ~/Projects/OpenGLTutorials/ .
  • Lance CMake (Applications → CMake). Dans la première ligne, navigue jusqu’au dossier décompressé. Si tu n’es pas sûr, choisis le dossier contenant le fichier CMakeLists.txt. Dans la seconde ligne, entre l’emplacement où tu souhaite mettre tous les fichiers spécifiques au compilateur. Par exemple, tu peux choisir ~/Projects/OpenGLTutorials_bin_XCode/. Note que cela peut être n’importe où et pas nécessairement dans le même dossier.
  • Clique sur le bouton Configurer. Comme c’est la première fois que tu configures le projet, CMake te demandera quel est le compilateur à utiliser. Choisis XCode.
  • Clique sur Configurer jusqu’à ce que les lignes rouges disparaissent. Clique sur Générer. Ton projet XCode est maintenant créé. Tu peux oublier CMake et même le désinstaller si tu veux.
  • Ouvre ~/Projects/OpenGLTutorials_bin_XCode/. Tu vas voir un fichier Tutorials.xcodeproj : Ouvre-le.
  • Sélectionne le tutoriel dans le panneau de projet que tu souhaites lancer et utilise le bouton Lancer pour compiler et exécuter :

Notes pour Code::Blocks

À cause de deux bogues (un dans Code::Blocks, un dans CMake), tu dois éditer la ligne de commande dans Projet→Options de compilation→Commandes Make, comme suit :

Tu devras peut être aussi configurer le répertoire de travail toi-même : Projet→Propriétés→Cibles de compilation→tutorial N→Répertoire d’exécution (c’est src_dir/tutorial_N/).

Exécuter les tutoriels

Tu dois exécuter les tutoriels directement à partir du bon répertoire : simplement en double cliquant sur l’exécutable. Si tu préféres la ligne de commande, cd vers le bon répertoire.

Si tu souhaites exécuter les tutoriels à partir de l’IDE, n’oublie pas de lire les instructions ci-dessus pour définir le bon répertoire de travail.

Comment suivre ces tutoriels

Chaque tutoriel est fourni avec son code source et ses données, qui se trouvent dans tutorialXX/. Par contre, tu ne dois jamais modifier ces projets : ils ne servent que de référence. Ouvre playground/playground.cpp et modifie ce fichier à la place. Torture-le comme tu le souhaites. Si tu es perdu, copie n’importe quel tutoriel dans celui-ci et tout devrait revenir à la normale.

On va fournir des morceaux de code tout au long des tutoriels. N’hésite pas à les copier/coller dans ton bac à sable durant la lecture : l’expérimentation est une bonne chose. Évite de te limiter à lire le code fini : tu ne vas pas apprendre grand-chose comme cela. Même avec un simple copier/coller, tu vas avoir ton lot de problèmes.

Ouvrir une fenêtre

Finalement ! Du code OpenGL ! Enfin, pas vraiment. Tous les tutoriels te montre la façon bas niveau de faire les choses afin que tu puisses voir qu’il n’y a pas de magie. Mais cette partie est très ennuyeuse et inutile, donc on va utiliser GLFW, une bibliothèque externe qui fera cela à notre place. Si tu le souhaite vraiment, tu peux utiliser l’api Win32 sous Windows, l’api X11 sous Linux et Cocoa sous Mac ; ou utiliser une autre bibliothèque comme SFML, FreeGLUT, SDL, … regarde la page des liens.

Ok, allons-y. Premièrement, on doit gérer les dépendances : on a besoin de choses basiques pour afficher les messages dans la console :

// Include standard headers
#include <stdio.h>
#include <stdlib.h>

Premièrement, GLEW. Celle-ci est en faite un peu magique, alors on la laisse de côté pour plus tard.

// Include GLEW. Always include it before gl.h and glfw.h, since it's a bit magic.
#include <GL/glew.h>

On a décidé de laisser GLFW gérer la fenêtre et le clavier, donc on l’inclue aussi :

// Include GLFW
#include <GL/glfw3.h>

Pour l’instant, on n’a pas besoin du prochain include, mais c’est la bibliothèque pour les opérations mathématiques liées à la 3D. Elle va se montrer rapidement très utile. Il n’y a pas de magie dans GLM, tu peux écrire ta propre bibliothèque si tu veux, c’est juste pratique de pas réinventer la roue. Le using namespace permet d’éviter de taper glm::vec3, au lieu de vec3.

// Include GLM
#include <glm/glm.hpp>
using namespace glm;

Si tu copies/colles tout ces includes dans playground.cpp, le compilateur se plaindra, car il n’y a pas de fonction main(). Donc, créons-la :

int main(){

La première chose à faire est d’initialiser GLFW :

// Initialise GLFW
if( !glfwInit() )
{
    fprintf( stderr, "Failed to initialize GLFW\n" );
    return -1;
}

On peut maintenant créer notre premier fenêtre OpenGL !

glfwWindowHint(GLFW_SAMPLES, 4); // 4x antialiasing
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // We want OpenGL 3.3
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make MacOS happy; should not be needed
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //We don't want the old OpenGL

// Open a window and create its OpenGL context
GLFWwindow* window; // (In the accompanying source code, this variable is global)
window = glfwCreateWindow( 1024, 768, "Tutorial 01", NULL, NULL);
if( window == NULL ){
    fprintf( stderr, "Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n" );
    glfwTerminate();
    return -1;
}
glfwMakeContextCurrent(window); // Initialize GLEW
glewExperimental=true; // Needed in core profile
if (glewInit() != GLEW_OK) {
    fprintf(stderr, "Failed to initialize GLEW\n");
    return -1;
}

Compile et exécute ce code. Une fenêtre devrait apparaître et se fermer immédiatement. Évidemment ! On doit attendre que l’utilisateur appuie sur la touche Échap :

// Ensure we can capture the escape key being pressed below
glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE);

do{
    // Draw nothing, see you in tutorial 2 !

    // Swap buffers
    glfwSwapBuffers(window);
    glfwPollEvents();

} // Check if the ESC key was pressed or the window was closed
while( glfwGetKey(window, GLFW_KEY_ESCAPE ) != GLFW_PRESS &&
glfwWindowShouldClose(window) == 0 );

Et cela conclut notre premier tutoriel ! Dans le deuxième tutoriel, tu vas apprendre comment afficher un triangle.

  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Tutoriel 2 : le premier triangle

Cela va être un autre long tutoriel.

OpenGL 3 facilite l’écriture des choses compliquées, mais possède l’inconvénient de rendre l’affichage d’un simple triangle relativement difficile.

N’oublie pas de copier/coller le code régulièrement.

Si le programme crashe au démarrage, tu l’exécute certainement à partir du mauvais répertoire. Lis PRÉCAUTIONNEUSEMENT le premier tutoriel sur comment configurer Visual Studio.

Le VAO

On ne va pas s’enfoncer dans les détails maintenant, mais on doit créer un Vertex Array Object et le définir comment objet courant.

GLuint VertexArrayID;
glGenVertexArrays(1, &VertexArrayID);
glBindVertexArray(VertexArrayID);

Fait-le une fois que ta fenêtre est créée (= après la création du contexte OpenGL) et avant tout autre appel OpenGL.

Si tu souhaite vraiment en apprendre plus sur les VAO, il y a quelques autres tutoriels sur le Web, mais ce n’est pas très important pour l’instant.

Coordonnées écran

Un triangle est défini par trois points. Lorsque l’on parle de « points » en graphismes 3D, on utilise habituellement le terme de sommet (en anglais vertex, vertices au pluriel). Un sommet possède trois coordonnées : X, Y et Z. Tu peux imaginer ces trois coordonnées de la manière suivante

  • X est sur ta droite
  • Y est vers le haut
  • Z est derrière toi (oui, derrière et non devant).

Mais voici une meilleure méthode pour les visualiser : utilise la règle de la main droite :

  • X est ton pouce
  • Y est ton index
  • Z est ton majeur. Si tu place ton pouce sur ta droite et ton index vers le ciel, ton majeur pointera aussi derrière ton dos.

Il est étrange d’avoir l’axe Z dans cette direction. Pourquoi est-ce ainsi ? Pour faire court : car 100 années de « règle de la main droite » te donneront des outils pratiques. La seule contrepartie est un axe Z contre-intuitif.

Mis à part cela, remarque aussi que tu peux bouger ta main librement : ton X, Y et Z suivront. On reviendra sur ce point.

Donc, on a besoin de trois points 3D afin de faire un triangle ; les voici :

// An array of 3 vectors which represents 3 vertices
static const GLfloat g_vertex_buffer_data[] = {
   -1.0f, -1.0f, 0.0f,
   1.0f, -1.0f, 0.0f,
   0.0f,  1.0f, 0.0f,
};

Le premier sommet est (-1, -1, 0). Cela signifie que, sauf si nous le transformons d’une quelconque manière, il sera affiché à la position (-1, -1) à l’écran. Qu’est-ce que cela donne ? L’origine de l’écran est au centre, l’axe X va vers la droite, comme toujours et l’axe Y vers le haut. Voici ce que cela donne sur un écran large :

C’est une chose que tu ne peux pas modifier, c’est intégré dans ta carte graphique. Donc (-1, -1) est le coin inférieur gauche de votre écran. (1, -1) est le coin inférieur droit et (0, 1), le milieu haut. Donc ce triangle devrait couvrir la majorité de l’écran.

Dessiner notre triangle

La prochaine étape est de fournir ce triangle à OpenGL. Pour cela il faut créer un buffer (Tampon en Français) :

// This will identify our vertex buffer
GLuint vertexbuffer;
// Generate 1 buffer, put the resulting identifier in vertexbuffer
glGenBuffers(1, &vertexbuffer);
// The following commands will talk about our 'vertexbuffer' buffer
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
// Give our vertices to OpenGL.
glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);

C’est nécessaire qu’une seule fois au lancement du programme.

Maintenant, la boucle principale, où on ne dessiner rien dans le premier tuto. On peut maintenant dessiner un fantastique triangle :

// 1rst attribute buffer : vertices
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glVertexAttribPointer(
   0,                  // attribute 0. No particular reason for 0, but must match the layout in the shader.
   3,                  // size
   GL_FLOAT,           // type
   GL_FALSE,           // normalized?
   0,                  // stride
   (void*)0            // array buffer offset
);
// Draw the triangle !
glDrawArrays(GL_TRIANGLES, 0, 3); // Starting from vertex 0; 3 vertices total -> 1 triangle
glDisableVertexAttribArray(0);

Si tu es chanceux, tu peux voir ce résultat (Si tu vois rien, panique pas et continue !)

C’est juste du blanc ennuyeux. Voyons voir comment l’améliorer en l’affichant en rouge. Cela peut être fait avec quelque chose appelé shader.

Shaders

Compilation de shader

Dans la configuration la plus simple, tu as besoin de deux shaders : un appelé Vertex Shader, qui sera exécuté pour chaque sommet et l’autre appelé Fragment Shader, qui sera exécuté pour chaque fragment. Comme on utilise un antialiasing 4x, on a quatre échantillons pour chaque pixel.

Les shaders se programment avec un langage appelé GLSL : GL Shader Language, qui est intégré à OpenGL. Contrairement au C ou au Java, le GLSL est compilé durant l’exécution du programme, ce qui signifie que chaque fois que tu lance ton application, tous tes shaders sont recompilés.

Les deux shaders sont généralement dans des fichiers distincts. Dans cet exemple, nous avons SimpleFragmentShader.fragmentshader et SimpleVertexShader.vertexshader. L’extension importe peu, cela aurait pu être .txt ou .glsl.

Voici enfin le code pour charger des shaders. Il n’est pas très important de le comprendre entièrement, car on ne l’utilise qu’une seule fois dans un programme, les commentaires suffiront. Comme cette fonction va être utilisée dans tous les autres tutoriels, elle est placée dans un fichier à part : common/loadShader.cpp. On peut remarquer que, comme les buffer, les shaders ne sont pas directement accessibles : on n’a qu’un identifiant. L’implémentation actuelle est cachée par le pilote.

GLuint LoadShaders(const char * vertex_file_path,const char * fragment_file_path){

    // Create the shaders
    GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
    GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);

    // Read the Vertex Shader code from the file
    std::string VertexShaderCode;
    std::ifstream VertexShaderStream(vertex_file_path, std::ios::in);
    if(VertexShaderStream.is_open()){
        std::string Line = "";
        while(getline(VertexShaderStream, Line))
            VertexShaderCode += "\n" + Line;
        VertexShaderStream.close();
    }else{
        printf("Impossible to open %s. Are you in the right directory ? Don't forget to read the FAQ !\n", vertex_file_path);
        getchar();
        return 0;
    }

    // Read the Fragment Shader code from the file
    std::string FragmentShaderCode;
    std::ifstream FragmentShaderStream(fragment_file_path, std::ios::in);
    if(FragmentShaderStream.is_open()){
        std::string Line = "";
        while(getline(FragmentShaderStream, Line))
            FragmentShaderCode += "\n" + Line;
        FragmentShaderStream.close();
    }

    GLint Result = GL_FALSE;
    int InfoLogLength;

    // Compile Vertex Shader
    printf("Compiling shader : %s\n", vertex_file_path);
    char const * VertexSourcePointer = VertexShaderCode.c_str();
    glShaderSource(VertexShaderID, 1, &VertexSourcePointer , NULL);
    glCompileShader(VertexShaderID);

    // Check Vertex Shader
    glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
    glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    if ( InfoLogLength > 0 ){
        std::vector<char> VertexShaderErrorMessage(InfoLogLength+1);
        glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
        printf("%s\n", &VertexShaderErrorMessage[0]);
    }

    // Compile Fragment Shader
    printf("Compiling shader : %s\n", fragment_file_path);
    char const * FragmentSourcePointer = FragmentShaderCode.c_str();
    glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , NULL);
    glCompileShader(FragmentShaderID);

    // Check Fragment Shader
    glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
    glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    if ( InfoLogLength > 0 ){
        std::vector<char> FragmentShaderErrorMessage(InfoLogLength+1);
        glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]);
        printf("%s\n", &FragmentShaderErrorMessage[0]);
    }

    // Link the program
    printf("Linking program\n");
    GLuint ProgramID = glCreateProgram();
    glAttachShader(ProgramID, VertexShaderID);
    glAttachShader(ProgramID, FragmentShaderID);
    glLinkProgram(ProgramID);

    // Check the program
    glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
    glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
    if ( InfoLogLength > 0 ){
        std::vector<char> ProgramErrorMessage(InfoLogLength+1);
        glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
        printf("%s\n", &ProgramErrorMessage[0]);
    }

    glDetachShader(ProgramID, VertexShaderID);
    glDetachShader(ProgramID, FragmentShaderID);

    glDeleteShader(VertexShaderID);
    glDeleteShader(FragmentShaderID);

    return ProgramID;
}

Notre vertex shader

Écrivons notre premier vertex shader.

La première ligne indique au compilateur que l’on va utiliser la syntaxe de OpenGL 3.

#version 330 core

La seconde ligne déclare les données d’entrées :

layout(location = 0) in vec3 vertexPosition_modelspace;

Expliquons-la en détail :

  • vec3 est un vecteur de trois composantes dans le GLSL. Il est similaire (mais différent) au glm::vec3 que nous avons utilisé pour définir notre triangle. Le point important est que si on utilise trois composantes en C++, nous utilisons aussi trois composantes dans le GLSL
  • layout(location = 0) se réfère au buffer (tampon) que l’on fournit à l’attribut vertexPosition_modelspace. Chaque sommet peut avoir de nombreux attributs : une position, une ou plusieurs couleurs, une ou plusieurs coordonnées de texture et plein d’autres choses. OpenGL ne sait pas ce qu’est une couleur : il ne voit qu’un vec3. Donc on doit lui dire quel tampon correspond à quelle entrée. Nous le faisons en définissant le layout à la même valeur que le premier paramètre de la fonction glVertexAttribPointer. La valeur 0 n’est pas importante, cela aurait pu être 12 (mais, elle ne peut être supérieure à glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &v)), la chose importante est qu’elle soit la même des deux côtés
  • vertexPosition_modelspace aurait pu être n’importe quoi. Il contiendra la position des sommets pour chaque exécution du vertex shader
  • in signifie que ce sont des données d’entrée. Bientôt on va voir le mot clé out

La fonction qui est appelée pour chaque sommet est appelée main, tout comme en C :

void main(){

Notre fonction principale va simplement définir la position du vertex à ce qui est dans le buffer (tampon). Donc, si on donne (1, 1), le triangle aura l’un de ces sommets au coin supérieur droit de l’écran. On verra dans le prochain tutoriel comment effectuer des calculs plus intéressants sur les positions passées au shader.

  gl_Position.xyz = vertexPosition_modelspace;
  gl_Position.w = 1.0;
}

gl_Position est l’une des rares variables codé en dur dans le langage : vous devez assigner une valeur à celle-ci. Tout le reste est optionnel ; on verra « tout le reste » dans le quatrième tutoriel.

Notre fragment shader

Pour notre premier fragment shader, on va faire quelque chose de très simple : définir la couleur de chaque fragment à rouge. (Rappel-toi, il y a quatre fragments dans un pixel, car nous utilisons l’Anti-Aliasing 4x.)

#version 330 core
out vec3 color;
void main(){
  color = vec3(1,0,0);
}

Donc voilà, vec3(1,0,0) signifie rouge. Cela est dû aux écrans d’ordinateur. La couleur est représentée par un triplet rouge, vert, bleu, dans cet ordre. Donc (1, 0, 0) indique complètement rouge, pas de vert et pas de bleu.

Mettre tout ensemble

Avant la boucle principale, on appelle la fonction LoadShaders :

// Create and compile our GLSL program from the shaders
GLuint programID = LoadShaders( "SimpleVertexShader.vertexshader", "SimpleFragmentShader.fragmentshader" );

En premier, dans la boucle principale, on nettoie l’écran. Cela changera la couleur de fond en bleu foncé à cause de l’appel glClearColor(0.0f, 0.0f, 0.4f, 0.0f) au-dessus de la boucle principale.

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

Puis on indique à OpenGL que l’on souhaite les shaders vus précédemment :

// Use our shader
glUseProgram(programID);
// Draw triangle...

… et voilà ton triangle rouge !

Dans le prochain tutoriel, on étudiera les transformations : comment définir une caméra, déplacer les objets, etc.

  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Tutoriel 3 : matrices

Les moteurs ne déplacent pas du tout le vaisseau. Le vaisseau reste où il est et les moteurs déplacent l’univers autour de lui.

Futurama

C’est le tutoriel le plus important de tous. Tu devrais le garder en livre de chevet, et le lire et relire au moins 8 fois !

Coordonnées homogènes

Jusqu’à présent, on a considéré les sommets 3D comme des triplets (x, y, z). On va introduire w, ce qui donnera des vecteurs (x, y, z, w).

Cela deviendra bientôt clair, mais pour le moment, retient ça :

  • Si w == 1, alors le vecteur (x, y, z, 1) est une position dans l’espace.
  • Si w == 0, alors le vecteur (x, y, z, 0) est une direction.

(En fait, retiens ça à vie.)

Quelle différence cela fait-il ? Eh bien, pour une rotation, ça change rien. Lorsque tu tournes un point ou une direction, tu obtiens le même résultat. Par contre, pour une translation (lorsque tu déplaces un point dans une certaine direction), les choses sont différentes. Que signifie « déplacer une direction » ? Pas grand-chose.

Les coordonnées homogènes permettent d’utiliser une simple formule mathématique pour gérer ces deux cas.

Matrices de transformation

Une introduction aux matrices

De façon simple, une matrice est un tableau de nombres avec un nombre prédéfini de lignes et colonnes. Par exemple, une matrice 2x3 ressemble à ceci :

En programmation 3D, on utilise uniquement des matrices 4x4. Elles vont permettre de transformer les vecteurs (x, y, z, w). Cela s’effectue en multipliant le vertex (sommet) par la matrice :

Matrix x Sommet (dans cet ordre !!) = SommetTransformé

Ce n’est pas aussi effrayant qu’il y paraît. Place un doigt de ta main gauche sur le ‘a’ et un doigt de ta main droite sur le ‘x’. C’est ax. Déplace ta main gauche sur le prochain nombre (b) et ta main droite sur le prochain nombre (y). Tu obtiens by. Encore une fois : cz. Une nouvelle fois : dw. ax + by + cz + dw. Tu as ton nouveau ‘x’. Fait de même pour chaque ligne et tu obtiendras ton nouveau vecteur (x, y, z, w).

Comme c’est vraiment ennuyeux à calculer et qu’on va le faire souvent, on va laisser l’ordinateur le faire pour nous.

En C++, avec GLM :

glm::mat4 myMatrix;
glm::vec4 myVector;
// fill myMatrix and myVector somehow
glm::vec4 transformedVector = myMatrix * myVector; // Again, in this order ! this is important.

En GLSL :

mat4 myMatrix;
vec4 myVector;
// fill myMatrix and myVector somehow
vec4 transformedVector = myMatrix * myVector; // Yeah, it's pretty much the same than GLM

(as-tu copié/collé ça dans ton code ? Vas-y ! Essaye ! )

Matrices de translation

Ce sont les plus simples matrices de transformation à comprendre. Une matrice de translation ressemble à ça :

où X, Y, Z sont les valeurs que l’on souhaite ajouter à notre position.

Donc, si l’on veut déplacer le vecteur (10, 10, 10, 1) de dix unités sur l’axe des X, on obtient :

( Fais le calcul ! C’est vraiment important ! )

… et on obtient un vecteur homogène (20, 10, 10, 1) ! Rappel-toi, le 1 signifie que c’est une position et non pas une direction. Donc notre transformation n’a pas modifié le fait que nous utilisons une position, ce qui est une bonne chose.

Voyons voir maintenant ce qui se passe pour un vecteur qui représente une direction vers l’axe -Z : (0, 0, -1, 0)

… soit, notre direction originale (0, 0, -1, 0), ce qui est juste, car comme on l’a dit précédemment, le déplacement d’une direction n’a aucun sens.

Donc, qu’est ce que ca donne dans le code ?

En C++, avec GLM :

#include <glm/gtx/transform.hpp> // after <glm/glm.hpp>

glm::mat4 myMatrix = glm::translate(10.0f, 0.0f, 0.0f);
glm::vec4 myVector(10.0f, 10.0f, 10.0f, 0.0f);
glm::vec4 transformedVector = myMatrix * myVector; // guess the result

En GLSL :Bon, en fait, vous ne le ferez presque jamais. La plupart du temps, vous utilisez glm::translate() en C++ pour calculer votre matrice et vous l’envoyez au GLSL et vous la multipliez simplement par votre vecteur :

vec4 transformedVector = myMatrix * myVector;

La matrice d’identité

Celle-ci est spéciale. Elle ne fait rien. Mais on la mentionne car c’est important de pas oublier que A fois 1.0 donne A.

En C++ :

glm::mat4 myIdentityMatrix = glm::mat4(1.0f);

Matrices de mise à l’échelle

Les matrices de mise à l’échelle sont aussi assez simples :

Donc si tu veux redimensionner un vecteur (une position ou une direction, peu importe) par 2.0 dans toutes les directions :

et la valeur de w ne change pas. Tu peux te demander : quel est le sens d’une mise à l’échelle d’une direction ? Eh bien, souvent, pas grand-chose. Généralement, on ne le fait pas sauf dans quelques (rares) cas où cela peut être pratique.

La matrice d’identité n’est qu’un cas spécifique des matrices de redimensionnement, avec (X, Y, Z) = (1, 1, 1). C’est aussi un cas spécifique des matrices de translation avec (X, Y, Z) = (0, 0, 0).

En C++ :

// Use #include <glm/gtc/matrix_transform.hpp> and #include <glm/gtx/transform.hpp>
glm::mat4 myScalingMatrix = glm::scale(2.0f, 2.0f ,2.0f);

Matrices de rotation

Elles sont assez compliquées. Je vais passer les détails ici, sachant qu’il n’est pas important de connaître leur fonctionnement pour un usage quotidien. Pour plus d’informations, jete un oeil à cette populaire FAQ sur les matrices et quaternions (en anglais). Tu peux aussi regarder le tutoriel sur les rotations.

En C++ :

// Use #include <glm/gtc/matrix_transform.hpp> and #include <glm/gtx/transform.hpp>
glm::vec3 myRotationAxis( ??, ??, ??);
glm::rotate( angle_in_degrees, myRotationAxis );

Combiner les transformations

Voilà, on sait comment tourner, déplacer et redimensionner nos vecteurs. Cela serait bien si on pouvait combiner ces transformations. C’est possible en multipliant les matrices ensemble, par exemple :

TransformedVector = TranslationMatrix * RotationMatrix * ScaleMatrix * OriginalVector;

!! ATTENTION !! Cette ligne effectue la mise à l’échelle en premier, puis la rotation, et finalement la translation. C’est ainsi que la multiplication de matrice fonctionne.

Le résultat serait différent en écrivant ces opérations dans un autre ordre. Test le toi-même :

  • fait un pas en avant (attention à ton ordi) et tourner vers la gauche
  • tourne vers la gauche et fait un pas en avant.

En fait, l’ordre utilisé ci-dessus est celui que l’on va utiliser pour les personnages et objets de ton jeu (ou application) : la mise à l’échelle en premier (si besoin) ; puis sa rotation ; finalement la translation. Par exemple, pour un vaisseau (pour simplifier, les rotations ont été retirées) :

  • La mauvaise façon :
    • Tu déplaces le vaisseau de (10, 0, 0). Son centre est maintenant à dix unités de l’origine ;
    • Tu redimensionnes la taille de ton vaisseau d’un facteur 2. Toutes les coordonnées sont multipliées par 2 par rapport à l’origine, qui est très loin… finalement tu as un gros vaisseau, mais centré à 2*10 = 20. Chose que tu ne veux pas.
  • La bonne façon :
    • Tu redimensionnes la taille de ton vaisseau d’un facteur 2. Tu obtiens un gros vaisseau, centré sur l’origine ;
    • Tu déplaces ton vaisseau. Il conserve la même taille et il est au bon endroit.

Les multiplications de matrices * matrices sont très proches des multiplications matrices * vecteur, donc encore une fois, je vais passer quelques détails et te rediriger vers la FAQ des matrices si nécessaire. Pour le moment, on demande simplement à l’ordinateur de le faire :

En C++, avec GLM :

glm::mat4 myModelMatrix = myTranslationMatrix * myRotationMatrix * myScaleMatrix;
glm::vec4 myTransformedVector = myModelMatrix * myOriginalVector;

En GLSL :

mat4 transform = mat2 * mat1;
vec4 out_vec = transform * in_vec;

Les matrices de modèle, de vue et de projection

Pour la suite du tutoriel, on supposera savoir comment dessiner le modèle 3D favori de Blender : le singe Suzanne. Même si c’est pas encore le cas

Les matrices de modèle, de vue et de projection sont des outils pratiques pour différencier proprement les transformations. Tu peux ne pas les utiliser (après tout, c’est ce que l’on a fait dans les deux premiers tutoriels). Mais tu devrais. C’est la façon que tout le monde utilise, car c’est une approche propre, comme on va le voir.

La matrice de modèle

Le modèle, tout comme notre triangle rouge adoré, est défini par un ensemble de sommets. Les coordonnées X, Y, Z de ces sommets sont définies par rapport au centre de l’objet : ce qui veut dire que si un vertex est en (0, 0, 0), il est au centre de l’objet.

On aimerait pouvoir déplacer cet objet, peut-être car le joueur le contrôle avec le clavier et la souris. Facile, on vient juste d’apprendre à le faire : déplacement * rotation * redimensionnement, et c’est fini ! On applique cette matrice sur tous nos sommets, à chaque fois qu’on dessine l’écran (en GLSL, pas en C++) et tout le modèle bouge. Quelque chose qui ne bouge pas sera au centre du monde.

Nos sommets sont maintenant dans le repère du monde. C’est la signification de la flèche noire dans l’image ci-dessous : on s’est déplacé de l’espace du modèle (tous les sommets sont définis par rapport au centre du modèle) à l’espace monde (tous les sommets sont définis par rapport au centre du monde).

On peut résumer cela avec le diagramme suivant :

La matrice de vue

Voici une nouvelle fois la citation de Futurama :

Les moteurs ne déplacent pas du tout le vaisseau. Le vaisseau reste où il est et les moteurs déplacent l’univers autour de lui.

Lorsqu’on y pense, la même chose s’applique aux caméras. Si on souhaite voir une montagne à partir d’un autre angle, on peut déplacer soit la caméra… soit la montagne. Bien que cela ne soit pas possible dans la vraie vie, c’est très simple et pratique dans le monde de l’infographie.

Au début, notre caméra est à l’origine dans le _repère du monde. Afin de déplacer le monde, vous introduisez une nouvelle matrice, tout simplement. Imagine que tu veux déplacer la caméra de trois unités vers la droite (+X). C’est équivalent à déplacer l’ensemble du monde (les modèles inclus) trois unités sur la GAUCHE ! (-X). Pendant que ton cerveau fond, voici comment faire :

// Use #include <glm/gtc/matrix_transform.hpp> and #include <glm/gtx/transform.hpp>
glm::mat4 ViewMatrix = glm::translate(-3.0f, 0.0f ,0.0f);

L’image ci-dessous illustre ce phénomène : on s’est déplacé du repère du monde (tous les sommets sont définis par rapport au centre du monde, comme on l’avait fait dans la section précédente) vers le repère de la caméra (tous les sommets sont définis par rapport à la caméra).

Avant que votre tête n’explose, admire cette superbe fonction glm::LookAt de GLM :

glm::mat4 CameraMatrix = glm::lookAt(
    cameraPosition, // the position of your camera, in world space
    cameraTarget,   // where you want to look at, in world space
    upVector        // probably glm::vec3(0,1,0), but (0,-1,0) would make you looking upside-down, which can be great too
);

Voici le diagramme obligatoire :

Ce n’est pas encore fini. C’est vrai que c’est beaucoup d’un coup, mais accroche toi. t’y es presque !

La matrice de projection

On est maintenant dans le repère de la caméra. Cela signifie qu’après toutes ces transformations, un sommet ayant les coordonnées x == 0 et y == 0 devrait être affiché au centre de l’écran. Mais on ne peut pas utiliser uniquement les coordonnées x et y pour déterminer où un objet devrait être placé à l’écran : sa distance par rapport à la caméra (z) est aussi importante ! Pour deux sommets avec les même coordonnées x et y, le sommet avec la plus grande coordonnée z sera plus au centre de l’écran que l’autre.

Cela s’appelle une projection de perspective :

Et heureusement pour nous, cette projection peut être représentée par une matrice 4x41 :

// Generates a really hard-to-read matrix, but a normal, standard 4x4 matrix nonetheless
glm::mat4 projectionMatrix = glm::perspective(
    FoV,         // The horizontal Field of View, in degrees : the amount of "zoom". Think "camera lens". Usually between 90° (extra wide) and 30° (quite zoomed in)
    4.0f / 3.0f, // Aspect Ratio. Depends on the size of your window. Notice that 4/3 == 800/600 == 1280/960, sounds familiar ?
    0.1f,        // Near clipping plane. Keep as big as possible, or you'll get precision issues.
    100.0f       // Far clipping plane. Keep as little as possible.
);

Une dernière fois :

On s’est déplacé du repère de la caméra (tous les sommets sont définis par rapport à la caméra) vers le repère homogène (tous les sommets sont définis dans un petit cube). Tout ce qui est dans le cube apparaîtra à l’écran.

Et voici le diagramme final :

Voici un nouveau schéma afin que tu comprenes mieux ce qui se passe avec la projection. Avant la projection, on a les objets en bleu, dans le repère caméra, et la forme rouge représente le champ de la caméra : la partie de la scène que la caméra est capable de voir.

Et voici ce que l’on obtient en multipliant tout par la matrice de projection :

Dans cette image, le champ de la caméra (frustum en anglais) est devenu un cube parfait (entre -1 et 1 sur tous les axes, même si c’est un peu dur à voir) et tous les objets en bleu ont été déformés de la même façon. Donc, les objets proches de la caméra (= proche de la face du cube que nous ne pouvons pas voir) sont gros, les autres sont plus petits. Cela ressemble à la réalité !

Voici à quoi cela ressemble lorsque l’on est à « l’intérieur » du champ :

C’est l’image que vous obtenez ! C’est simplement un peu trop carré, donc une autre transformation mathématique est appliquée pour correspondre à la taille de la fenêtre :

Et c’est cette image qui est affichée !

Combiner des transformations : la matrice ModelViewProjection

… est une simple multiplication de matrices comme tu les aimes déjà :

// C++ : compute the matrix
glm::mat4 MVPmatrix = projection * view * model; // Remember : inverted !
// GLSL : apply it
transformed_vertex = MVP * in_vertex;

Mettre tout ensemble

  • Première étape : générer la matrice ModelViewProjection (MVP). Cela doit être fait pour chaque modèle que vous affichez.
// Projection matrix : 45° Field of View, 4:3 ratio, display range : 0.1 unit <-> 100 units
glm::mat4 Projection = glm::perspective(glm::radians(45.0f), (float) width / (float)height, 0.1f, 100.0f);

// Or, for an ortho camera :
//glm::mat4 Projection = glm::ortho(-10.0f,10.0f,-10.0f,10.0f,0.0f,100.0f); // In world coordinates

// Camera matrix
glm::mat4 View = glm::lookAt(
               glm::vec3(4,3,3), // Camera is at (4,3,3), in World Space
               glm::vec3(0,0,0), // and looks at the origin
               glm::vec3(0,1,0)  // Head is up (set to 0,-1,0 to look upside-down)
               );

// Model matrix : an identity matrix (model will be at the origin)
glm::mat4 Model = glm::mat4(1.0f);
// Our ModelViewProjection : multiplication of our 3 matrices
glm::mat4 mvp = Projection * View * Model; // Remember, matrix multiplication is the other way around
  • Seconde étape : passez les matrices au GLSL
// Get a handle for our "MVP" uniform
// Only during the initialisation
GLuint MatrixID = glGetUniformLocation(program_id, "MVP");

// Send our transformation to the currently bound shader, in the "MVP" uniform
// This is done in the main loop since each model will have a different MVP matrix (At least for the M part)
glUniformMatrix4fv(mvp_handle, 1, GL_FALSE, &mvp[0][0]);
  • Third step : use it in GLSL to transform our vertices
// Input vertex data, different for all executions of this shader.
layout(location = 0) in vec3 vertexPosition_modelspace;

// Values that stay constant for the whole mesh.
uniform mat4 MVP;

void main(){

    // Output position of the vertex, in clip space : MVP * position
    gl_Position =  MVP * vec4(vertexPosition_modelspace,1);

}
  • Fini ! Voici le triangle du second tutoriel, toujours à l’origine (0, 0, 0), mais vu en perspective à partir du point (4, 3, 3), la tête en haut (0, 1, 0), avec un champ de vision de 45°.

Dans le sixième tutoriel tu vas apprendre à modifier ces valeurs dynamiquement à l’aide du clavier et de la souris pour créer une caméra comme dans les jeux, mais avant, tu dois apprendre comment donner quelques couleurs à nos modèles 3D (tutoriel 4) et des textures (tutoriel 5).

Exercices

  • Essaye de changer la glm::perspective.
  • Au lieu d’utiliser une projection en perspective, utilise une projection orthogonale (glm::ortho).
  • Modifie la ModelMatrix pour déplacer, tourner et redimensionner le triangle.
  • Faites la même chose, mais dans un ordre différent. Que remarques-tu ? Quel est le « meilleur » ordre que t’utiliserais pour un personnage ?

Addendum

  1. […]heureusement pour nous, cette projection peut être représentée par une matrice 4x4 : mais en réalité, ce n’est pas correct. Une perspective n’est pas affine, et donc, ne peut être complètement représentée par une matrice. Après avoir été multipliées par la matrice de projection, les coordonnées homogènes sont divisées par la composante ‘w’. Cette composante ‘w’ se trouve être -Z (car la matrice de projection a été construite ainsi). Ainsi, les points qui sont très loin de l’origine sont divisés par un grand Z ; leurs coordonnées X et Y deviennent petites, les points se rapprocheront les uns des autres, les objets semblent plus petits ; et c’est cela qui donne la perspective. ↩

  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Tutoriel 4 : un cube coloré

Bienvenue dans le quatrième tutoriel ! Dans celui-ci on va :

  • Afficher un cube au lieu d’un ennuyeux triangle
  • Ajouter de jolies couleur
  • Apprendre ce qu’est le Z-buffer (tampon de profondeur)

Afficher un cube

Un cube possède six faces carrées. Comme OpenGL ne connaît que les triangles, on doit dessiner douze triangles : deux pour chaque face. On définit les sommets de la même façon que pour le triangle.

// Our vertices. Three consecutive floats give a 3D vertex; Three consecutive vertices give a triangle.
// A cube has 6 faces with 2 triangles each, so this makes 6*2=12 triangles, and 12*3 vertices
static const GLfloat g_vertex_buffer_data[] = {
    -1.0f,-1.0f,-1.0f, // triangle 1 : begin
    -1.0f,-1.0f, 1.0f,
    -1.0f, 1.0f, 1.0f, // triangle 1 : end
    1.0f, 1.0f,-1.0f, // triangle 2 : begin
    -1.0f,-1.0f,-1.0f,
    -1.0f, 1.0f,-1.0f, // triangle 2 : end
    1.0f,-1.0f, 1.0f,
    -1.0f,-1.0f,-1.0f,
    1.0f,-1.0f,-1.0f,
    1.0f, 1.0f,-1.0f,
    1.0f,-1.0f,-1.0f,
    -1.0f,-1.0f,-1.0f,
    -1.0f,-1.0f,-1.0f,
    -1.0f, 1.0f, 1.0f,
    -1.0f, 1.0f,-1.0f,
    1.0f,-1.0f, 1.0f,
    -1.0f,-1.0f, 1.0f,
    -1.0f,-1.0f,-1.0f,
    -1.0f, 1.0f, 1.0f,
    -1.0f,-1.0f, 1.0f,
    1.0f,-1.0f, 1.0f,
    1.0f, 1.0f, 1.0f,
    1.0f,-1.0f,-1.0f,
    1.0f, 1.0f,-1.0f,
    1.0f,-1.0f,-1.0f,
    1.0f, 1.0f, 1.0f,
    1.0f,-1.0f, 1.0f,
    1.0f, 1.0f, 1.0f,
    1.0f, 1.0f,-1.0f,
    -1.0f, 1.0f,-1.0f,
    1.0f, 1.0f, 1.0f,
    -1.0f, 1.0f,-1.0f,
    -1.0f, 1.0f, 1.0f,
    1.0f, 1.0f, 1.0f,
    -1.0f, 1.0f, 1.0f,
    1.0f,-1.0f, 1.0f
};

Le tampon OpenGL est créé, lié, rempli et configuré avec les fonctions de base (glGenBuffers, glBindBuffer, glBufferData, glVertexAttribPointer) ; lisez le deuxième tutoriel pour un rappel rapide. L’appel pour l’affichage ne change pas non plus, vous avez simplement à donner le bon nombre de sommets à dessiner :

// Draw the triangle !
glDrawArrays(GL_TRIANGLES, 0, 12*3); // 12*3 indices starting at 0 -> 12 triangles -> 6 squares

Quelques remarques sur ce code :

  • Pour l’instant, nos modèles 3D sont fixes : afin de les modifier, on doit changer le code source, recompiler l’application et prier que tout aille bien. On apprendra comment charger dynamiquement les modèles dans le septième tutoriel
  • Chaque sommet est écrit au moins trois fois (cherchez la ligne « -1.0f,-1.0f,-1.0f » dans le code ci-dessus). C’est une incroyable perte de mémoire. On apprendra comment gérer cela dans le neuvième tutoriel.

Tu as maintenant tous les morceaux pour dessiner le cube en blanc. Fait fonctionner les shaders ! Allez va y, ou au moins, essaye :)

Ajouter des couleurs

Une couleur est, conceptuellement, exactement identique à une position : ce ne sont que des données. Avec les mots d’OpenGL, ce sont des « attributs ». En fait, on les a déjà utilisés avec glEnableVertexAttribArray() et glVertexAttribPointer(). On ajoute un autre attribut à chaque sommet. Le code va ressembler à celui pour les positions.

D’abord, déclare des couleurs : un triplet RGB par sommet. Ici, j’en ai généré quelques-uns aléatoirement, donc le résultat ne sera pas beau, mais tu peux faire mieux, par exemple en copiant la position des sommets pour définir sa couleur.

// One color for each vertex. They were generated randomly.
static const GLfloat g_color_buffer_data[] = {
    0.583f,  0.771f,  0.014f,
    0.609f,  0.115f,  0.436f,
    0.327f,  0.483f,  0.844f,
    0.822f,  0.569f,  0.201f,
    0.435f,  0.602f,  0.223f,
    0.310f,  0.747f,  0.185f,
    0.597f,  0.770f,  0.761f,
    0.559f,  0.436f,  0.730f,
    0.359f,  0.583f,  0.152f,
    0.483f,  0.596f,  0.789f,
    0.559f,  0.861f,  0.639f,
    0.195f,  0.548f,  0.859f,
    0.014f,  0.184f,  0.576f,
    0.771f,  0.328f,  0.970f,
    0.406f,  0.615f,  0.116f,
    0.676f,  0.977f,  0.133f,
    0.971f,  0.572f,  0.833f,
    0.140f,  0.616f,  0.489f,
    0.997f,  0.513f,  0.064f,
    0.945f,  0.719f,  0.592f,
    0.543f,  0.021f,  0.978f,
    0.279f,  0.317f,  0.505f,
    0.167f,  0.620f,  0.077f,
    0.347f,  0.857f,  0.137f,
    0.055f,  0.953f,  0.042f,
    0.714f,  0.505f,  0.345f,
    0.783f,  0.290f,  0.734f,
    0.722f,  0.645f,  0.174f,
    0.302f,  0.455f,  0.848f,
    0.225f,  0.587f,  0.040f,
    0.517f,  0.713f,  0.338f,
    0.053f,  0.959f,  0.120f,
    0.393f,  0.621f,  0.362f,
    0.673f,  0.211f,  0.457f,
    0.820f,  0.883f,  0.371f,
    0.982f,  0.099f,  0.879f
};

Le buffer (tampon) est créé, lié et rempli de la même façon que le précédent :

GLuint colorbuffer;
glGenBuffers(1, &colorbuffer);
glBindBuffer(GL_ARRAY_BUFFER, colorbuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(g_color_buffer_data), g_color_buffer_data, GL_STATIC_DRAW);

La configuration est identique, elle aussi :

// 2nd attribute buffer : colors
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, colorbuffer);
glVertexAttribPointer(
    1,                                // attribute. No particular reason for 1, but must match the layout in the shader.
    3,                                // size
    GL_FLOAT,                         // type
    GL_FALSE,                         // normalized?
    0,                                // stride
    (void*)0                          // array buffer offset
);

Maintenant, dans le vertex shader, on accède au nouveau buffer (tampon) :

// Notice that the "1" here equals the "1" in glVertexAttribPointer
layout(location = 1) in vec3 vertexColor;

Dans notre cas, on ne fait rien de fantaisiste dans le vertex shader. On envoie simplement la couleur au fragment shader :

// Output data ; will be interpolated for each fragment.
out vec3 fragmentColor;

void main(){

    [...]

    // The color of each vertex will be interpolated
    // to produce the color of each fragment
    fragmentColor = vertexColor;
}

Dans le fragment shader, on déclare encore une fois fragmentColor :

// Interpolated values from the vertex shaders
in vec3 fragmentColor;

… et on la copie dans la couleur finale de sortie :

// Ouput data
out vec3 color;

void main(){
    // Output color = color specified in the vertex shader,
    // interpolated between all 3 surrounding vertices
    color = fragmentColor;
}

Et voici ce que l’on obtient :

Argh. Horrible. Pour comprendre ce qui se passe, voici un schéma de ce qui se produit lorsque vous dessinez un triangle « au loin » et ensuite un triangle « proche » :

Cela semble ok. Maintenant si on dessine le triangle « au loin » en deuxième :

Il recouvre le triangle « proche » même s’il est supposé être derrière ! C’est ce qui se passe avec notre cube : certaines faces sont supposées être cachées, mais comme elles sont dessinées après, elles sont visibles. Le tampon de profondeur (Z-buffer) va venir à la rescousse !

Si tu ne voit pas le problème, change la position de ta caméra à (4,3,-3)

Si « la couleur est comme une position, c’est un attribut », pourquoi doit-on déclarer out vec3 fragmentColor ; et in vec3 fragmentColor ; pour la couleur et pas pour la position ? Car la position est un peu spéciale : c’est la seule qui est obligatoire (sinon OpenGL ne saurait pas où dessiner le triangle !). Donc, dans le vertex shader, gl_Position est une variable intégrée du langage.

Le Z-Buffer (tampon de profondeur)

La solution à ce problème est de conserver la profondeur (‘Z’) de chaque fragment dans un tampon et pour toutes les fois où on veut écrire un fragment, on vérifiez d’abord si on peut le faire (si le nouveau fragment est plus proche que l’ancien).

On pourrait le faire nous-même, mais c’est beaucoup plus simple de demander au matériel de le faire :

// Enable depth test
glEnable(GL_DEPTH_TEST);
// Accept fragment if it closer to the camera than the former one
glDepthFunc(GL_LESS);

On doit aussi nettoyer le tampon de profondeur à chaque image, au lieu de ne le faire que pour la couleur :

// Clear the screen
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

Et ça suffit pour régler tous nos problèmes.

Exercices

  • Dessine le cube ET le triangle à deux emplacements différents. Tu auras besoin de générer deux matrices MVP, de faire deux appels pour l’affichage dans la boucle principale, mais seulement un shader est nécessaire.

  • Génére les valeurs des couleurs toi-même. Quelques idées : aléatoirement, de manière à ce que les couleurs changent à chaque exécution ; dépendantes de la position des sommets ; un mélange des deux ; quelques idées créatives :). Au cas où tu ne connais pas la syntaxe en C++, la voici :

static GLfloat g_color_buffer_data[12*3*3];
for (int v = 0; v < 12*3 ; v++){
    g_color_buffer_data[3*v+0] = your red color here;
    g_color_buffer_data[3*v+1] = your green color here;
    g_color_buffer_data[3*v+2] = your blue color here;
}
  • Une fois que tu as fait ça, modifie les couleurs à chaque affichage. Tu dois appeler glBufferData à chaque image. Assure-toi que le tampon approprié est lié (glBindBuffer).
  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Tutoriel 5 : un cube texturé

Dans ce tutoriel, tu vas apprendre :

  • Ce que sont les coordonnées UV
  • Comment charger une texture par toi-même
  • Comment les utiliser dans OpenGL
  • Ce que sont le filtrage et les MIP maps et comment les utiliser
  • comment charger une texture plus facilement avec GLFW.
  • La signification du “channel alpha”

À propos des coordonnées UV

Lorsque l’on applique une texture sur un modèle 3D, on a besoin d’une méthode pour indiquer à OpenGL quelle partie de l’image doit être utilisée pour chaque triangle. Cela se fait grâce aux coordonnées UV.

Chaque sommet possède, en plus de sa position, une paire de floats, U et V. Ces coordonnées sont utilisées pour accéder à la texture de la manière suivante :

Remarque comment la texture est déformée sur le triangle.

Charger des images .BMP à la main

Connaître le format de fichier BMP n’est pas important : nombreuses sont les bibliothèques pouvant le faire pour toi. Mais il est très simple et il peut t’aider à comprendre comment les choses fonctionnent en interne. Donc, on va écrire un chargeur de fichier BMP à partir de rien, afin que tu saches comment ça fonctionne, et ensuite on l’utilisera plus jamais !

Voici la déclaration de la fonction de chargement :

GLuint loadBMP_custom(const char * imagepath);

Et elle s’utilise comme ça :

GLuint image = loadBMP_custom("./my_texture.bmp");

Maintenant, comment lire un fichier BMP ?

Premièrement, on a besoin de quelques données. Ces variables seront définies lors de la lecture du fichier.

// Data read from the header of the BMP file
unsigned char header[54]; // Each BMP file begins by a 54-bytes header
unsigned int dataPos;     // Position in the file where the actual data begins
unsigned int width, height;
unsigned int imageSize;   // = width*height*3
// Actual RGB data
unsigned char * data;

On doit maintenant ouvrir le fichier :

// Open the file
FILE * file = fopen(imagepath,"rb");
if (!file){printf("Image could not be opened\n"); return 0;}

La première chose dans le fichier est un en-tête de 54 octets. Il contient les informations telles que « est-ce vraiment un fichier BMP ? », la taille de l’image, le nombre de bits par pixel, etc. Donc, on lit l’en-tête :

if ( fread(header, 1, 54, file)!=54 ){ // If not 54 bytes read : problem
    printf("Not a correct BMP file\n");
    return false;
}

L’en-tête démarre toujours avec BM. En fait, voici ce que tu obtiens lorsque tu ouvres un fichier .BMP dans un éditeur hexadécimal :

Donc, on vérifier si les deux premiers octets valent vraiment ‘B’ et ‘M’ :

if ( header[0]!='B' || header[1]!='M' ){
    printf("Not a correct BMP file\n");
    return 0;
}

Maintenant, on peut lire la taille de l’image, l’emplacement des données dans le fichier, etc :

// Read ints from the byte array
dataPos    = *(int*)&(header[0x0A]);
imageSize  = *(int*)&(header[0x22]);
width      = *(int*)&(header[0x12]);
height     = *(int*)&(header[0x16]);

On doit compléter les informations pour les cas où elles sont manquantes :

// Some BMP files are misformatted, guess missing information
if (imageSize==0)    imageSize=width*height*3; // 3 : one byte for each Red, Green and Blue component
if (dataPos==0)      dataPos=54; // The BMP header is done that way

Maintenant que la taille de l’image est connue, on peut allouer de la mémoire pour la remplir avec l’image lue :

// Create a buffer
data = new unsigned char [imageSize];

// Read the actual data from the file into the buffer
fread(data,1,imageSize,file);

//Everything is in memory now, the file can be closed
fclose(file);

On arrive à la vrai section OpenGL. La création de textures est très similaire à la création de tampons : créer une texture, la lier, la remplir et la configurer.

Dans glTexImage2D, GL_RGB indique que l’on utilise une couleur ayant trois composantes et GL_BGR indique comment les données sont réellement disposées en mémoire. En fait, le fichier BMP ne stocke pas les pixels en Rouge->Vert->Bleu mais Bleu->Vert->Rouge, donc on doit en informer OpenGL.

// Create one OpenGL texture
GLuint textureID;
glGenTextures(1, &textureID);

// "Bind" the newly created texture : all future texture functions will modify this texture
glBindTexture(GL_TEXTURE_2D, textureID);

// Give the image to OpenGL
glTexImage2D(GL_TEXTURE_2D, 0,GL_RGB, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, data);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

On va expliquer les deux dernières lignes plus tard. Dorénavant, du côté C++, vous pouvez utiliser la nouvelle fonction pour charger une texture :

GLuint Texture = loadBMP_custom("uvtemplate.bmp");

Il est important d’utiliser des textures ayant des tailles en puissances de deux :

  • correct : 128*128, 256*256, 1024*1024, 2*2 …
  • mauvais : 127*128, 3*5 …
  • correct mais étrange : 128*256, 1024*128, …

Utiliser une texture dans OpenGL

On commence par le fragment shader. Il est globalement simple :

#version 330 core

// Interpolated values from the vertex shaders
in vec2 UV;

// Ouput data
out vec3 color;

// Values that stay constant for the whole mesh.
uniform sampler2D myTextureSampler;

void main(){

    // Output color = color of the texture at the specified UV
    color = texture( myTextureSampler, UV ).rgb;
}

Trois choses :

  • Le fragment shader a besoin des coordonnées UV. Cela semble logique
  • Il a aussi besoin d’un « sampler2D » pour savoir à quelle texture accéder (vous pouvez accéder à plusieurs textures dans le même shader) ;
  • Finalement, l’accès à la texture est effectué avec texture(), renvoyant un vec4 contenant (R,G,B,A). On va voir ce qu’est le A, prochainement.

Le vertex shader est simple aussi, vous devez juste passer les coordonnées UV au fragment shader :

#version 330 core

// Input vertex data, different for all executions of this shader.
layout(location = 0) in vec3 vertexPosition_modelspace;
layout(location = 1) in vec2 vertexUV;

// Output data ; will be interpolated for each fragment.
out vec2 UV;

// Values that stay constant for the whole mesh.
uniform mat4 MVP;

void main(){

    // Output position of the vertex, in clip space : MVP * position
    gl_Position =  MVP * vec4(vertexPosition_modelspace,1);

    // UV of the vertex. No special space for this one.
    UV = vertexUV;
}

Te souviens-tu du « layout(location = 1) in vec2 vertexUV » du quatrième tutoriel ? Eh bien, on doit faire exactement la même chose ici, mais au lieu de donner un tampon de triplets (R,G,B), on va donner une paire (U,V).

// Two UV coordinatesfor each vertex. They were created with Blender. You'll learn shortly how to do this yourself.
static const GLfloat g_uv_buffer_data[] = {
    0.000059f, 1.0f-0.000004f,
    0.000103f, 1.0f-0.336048f,
    0.335973f, 1.0f-0.335903f,
    1.000023f, 1.0f-0.000013f,
    0.667979f, 1.0f-0.335851f,
    0.999958f, 1.0f-0.336064f,
    0.667979f, 1.0f-0.335851f,
    0.336024f, 1.0f-0.671877f,
    0.667969f, 1.0f-0.671889f,
    1.000023f, 1.0f-0.000013f,
    0.668104f, 1.0f-0.000013f,
    0.667979f, 1.0f-0.335851f,
    0.000059f, 1.0f-0.000004f,
    0.335973f, 1.0f-0.335903f,
    0.336098f, 1.0f-0.000071f,
    0.667979f, 1.0f-0.335851f,
    0.335973f, 1.0f-0.335903f,
    0.336024f, 1.0f-0.671877f,
    1.000004f, 1.0f-0.671847f,
    0.999958f, 1.0f-0.336064f,
    0.667979f, 1.0f-0.335851f,
    0.668104f, 1.0f-0.000013f,
    0.335973f, 1.0f-0.335903f,
    0.667979f, 1.0f-0.335851f,
    0.335973f, 1.0f-0.335903f,
    0.668104f, 1.0f-0.000013f,
    0.336098f, 1.0f-0.000071f,
    0.000103f, 1.0f-0.336048f,
    0.000004f, 1.0f-0.671870f,
    0.336024f, 1.0f-0.671877f,
    0.000103f, 1.0f-0.336048f,
    0.336024f, 1.0f-0.671877f,
    0.335973f, 1.0f-0.335903f,
    0.667969f, 1.0f-0.671889f,
    1.000004f, 1.0f-0.671847f,
    0.667979f, 1.0f-0.335851f
};

Les coordonnées UV ci-dessus correspondent à ce modèle :

Le reste est évident. Générer le tampon, le lier, le remplir, le configurer et dessiner le tampon de sommet comme d’habitude. Fait attention et utilise 2 comme second paramètre (la taille) de la fonction glVertexAttribPointer au lieu de 3, car on a que 2 coordonées par sommet (U et V).

Voici le résultat :

et en zoomant un peu :

Que sont le filtrage et les MIP maps et comment les utiliser

Comme tu peux le voir dans la capture ci-dessus, la qualité de la texture n’est pas superbe. C’est dû à ce que l’on a écrit dans la fonction loadBMP_custom :

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

Ça signifie que dans le fragment shader, la fonction texture() prend le texel qui est aux coordonnées (U, V) et continue joyeusement avec :

Il y a de plusieurs choses que l’on peut faire pour améliorer cela.

Filtrage linéaire

Avec le filtrage linéaire, la fonction texture() regarde aussi les texels autour et mélange les couleurs suivant la distance de chaque centre. Cela évite les bordures nettes vues précédemment.

C’est beaucoup mieux et c’est beaucoup utilisé, mais si tu veux une meilleure qualité, tu peux aussi utiliser le filtrage anisotrope, qui est un peu plus lent mais plus joli.

Filtrage anisotrope

Celui-ci se rapproche de la partie de l’image qui est réellement vue dans le fragment. Par exemple, si la texture suivante est vue sur le côté et légèrement tournée, le filtrage anisotrope calculera la couleur contenue dans le rectangle bleu en prenant un nombre fixe d’échantillons (le niveau d’anisotropie) suivant sa direction principale :

Mipmaps

Les filtrages linéaire et anisotrope ont tous les deux un souci. Si la texture est vue de très loin, le mélange de quatre texels ne suffira pas. En fait, si votre modèle 3D est très loin et qu’il ne prend qu’un fragment sur l’écran, TOUS les texels de l’image vont être pris en compte pour calculer la moyenne afin de produire la couleur finale. Évidemment, cela n’est pas fait pour préserver les performances. À la place, on introduit les MIP maps :

  • Au moment de l’initialisation, on divise les dimensions de notre image d’un facteur 2, successivement, jusqu’à atteindre une image 1 x 1 (qui correspond à la moyenne de tous les texels de l’image)
  • Lorsque l’on dessine un modèle, on sélectionnez la mipmap la plus appropriée à utiliser suivant la taille à laquelle devrait être le texel
  • On échantillonnez cette MIP map avec l’un des filtrages vus précédemment (anisantropique ou linéaire)
  • Pour une qualité supérieure, on peut aussi échantillonner deux niveau de mipmaps et mélanger le résultat.

Heureusement, tout cela est très simple à faire, OpenGL sait le faire pour toi si tu lui demandez gentiment :

// When MAGnifying the image (no bigger mipmap available), use LINEAR filtering
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// When MINifying the image, use a LINEAR blend of two mipmaps, each filtered LINEARLY too
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
// Generate mipmaps, by the way.
glGenerateMipmap(GL_TEXTURE_2D);

Charger les images avec GLFW

La fonction loadBMP_custom est bien, car on l’a faite à partir de rien. Mais l’utilisation d’une bibliothèque dédiée est conseillé. GLFW peut aussi faire cela (mais uniquement pour les fichiers TGA).

Le chargement des fichiers TGA dans GLFW est dépréciée depuis la version 3.

GLuint loadTGA_glfw(const char * imagepath){

    // Create one OpenGL texture
    GLuint textureID;
    glGenTextures(1, &textureID);

    // "Bind" the newly created texture : all future texture functions will modify this texture
    glBindTexture(GL_TEXTURE_2D, textureID);

    // Read the file, call glTexImage2D with the right parameters
    glfwLoadTexture2D(imagepath, 0);

    // Nice trilinear filtering.
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glGenerateMipmap(GL_TEXTURE_2D);

    // Return the ID of the texture we just created
    return textureID;
}

Les textures compressées

À ce point, tu te demande probablement comment charger les fichiers JPEG à la place des TGA.

Réponse courte : ne le fait pas. Ta carte graphique (GPU) ne sais pas ce qu’est un JPEG ou un PNG. Tu devras décompresser ton image en JPEG et la donner à ta carte graphique: Tu va perdre du temps a la décompresser, tu va perdre en qualité, et aussi en performance car tu vas transmettre à ta carte graphique une texture décompressée.

Il y a une meilleures options : les textures compressées.

Créer une texture compressée

  • Télécharge l’outil d’ATI : Le Compressonator
  • Charge une texture en puissance de deux avec.
  • Compresse-la en DXT1, DXT3 ou DXT5 (plus d’informations sur les différences des formats sur Wikipedia)
  • Génére les MIP maps afin de ne pas avoir à le faire à l’exécution
  • Exporte le résultat dans un fichier .DDS

À ce moment, ton image est compressée dans un format qui est directement compatible avec le GPU. Pour n’importe quel appel à texture() dans un shader, le GPU décompressera la texture à la volée. Cela peut sembler lent, mais comme cela prend TELLEMENT moins de mémoire, moins de données ont besoin d’être transférées, sachant que les transferts mémoire sont lents, et que la décompression de texture est gratuite (il y a des puces dédiées à cela). Généralement, l’utilisation de la compression de texture augmente les performances de 20 %.

Utiliser la texture compressée

Voici comment charger l’image. C’est très proche du code pour le BMP, sauf que l’en-tête est organisé différemment :

GLuint loadDDS(const char * imagepath){

    unsigned char header[124];

    FILE *fp;

    /* try to open the file */
    fp = fopen(imagepath, "rb");
    if (fp == NULL)
        return 0;

    /* verify the type of file */
    char filecode[4];
    fread(filecode, 1, 4, fp);
    if (strncmp(filecode, "DDS ", 4) != 0) {
        fclose(fp);
        return 0;
    }

    /* get the surface desc */
    fread(&header, 124, 1, fp);

    unsigned int height      = *(unsigned int*)&(header[8 ]);
    unsigned int width         = *(unsigned int*)&(header[12]);
    unsigned int linearSize     = *(unsigned int*)&(header[16]);
    unsigned int mipMapCount = *(unsigned int*)&(header[24]);
    unsigned int fourCC      = *(unsigned int*)&(header[80]);

Les données de l’image se trouvent après l’en-tête : tous les niveaux de MIP maps, les uns après les autres. On peut les lire en une fois :

    unsigned char * buffer;
    unsigned int bufsize;
    /* how big is it going to be including all mipmaps? */
    bufsize = mipMapCount > 1 ? linearSize * 2 : linearSize;
    buffer = (unsigned char*)malloc(bufsize * sizeof(unsigned char));
    fread(buffer, 1, bufsize, fp);
    /* close the file pointer */
    fclose(fp);

Ici, on doit gérer trois formats différents : DXT1, DXT3 et DXT5. On doit convertir l’indicateur « fourCC » en une valeur que comprend OpenGL.

    unsigned int components  = (fourCC == FOURCC_DXT1) ? 3 : 4;
    unsigned int format;
    switch(fourCC)
    {
    case FOURCC_DXT1:
        format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
        break;
    case FOURCC_DXT3:
        format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
        break;
    case FOURCC_DXT5:
        format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
        break;
    default:
        free(buffer);
        return 0;
    }

La création de texture est effectuée comme d’habitude :

    // Create one OpenGL texture
    GLuint textureID;
    glGenTextures(1, &textureID);

    // "Bind" the newly created texture : all future texture functions will modify this texture
    glBindTexture(GL_TEXTURE_2D, textureID);

Et maintenant, on peut remplir chaque MIP map l’une après l’autre :

    unsigned int blockSize = (format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) ? 8 : 16;
    unsigned int offset = 0;

    /* load the mipmaps */
    for (unsigned int level = 0; level < mipMapCount && (width || height); ++level)
    {
        unsigned int size = ((width+3)/4)*((height+3)/4)*blockSize;
        glCompressedTexImage2D(GL_TEXTURE_2D, level, format, width, height,
            0, size, buffer + offset);

        offset += size;
        width  /= 2;
        height /= 2;
    }
    free(buffer);

    return textureID;

Inversing the UVs

La compression DXT vient du monde DirectX, où les coordonnées de texture UV sont inversées par rapport à OpenGL. Donc, si tu utilises les textures compressées, tu dois utiliser (coord.u, 1.0-coord.v) pour récupérer le texel adéquat. Tu peux le faire quand tu le souhaite : dans ton script d’exportation, dans ton chargeur, dans ton shader …

Conclusion

Tu viens d’apprendre à créer, charger et utiliser les textures avec OpenGL.

En général, tu dois utiliser uniquement les textures compressées, car elles sont plus petites à stocker, chargées presque instantanément et sont plus rapides à utiliser ; le principal inconvénient est que tu dois convertir tes images avec le Compressonator ou un outils similaire.

Exercices

  • Le chargeur DDS est implémenté dans le code source, mais pas la modification des coordonnées de texture. Change le code à l’emplacement adéquat pour afficher le cube correctement.
  • Expérimente avec les différents formats DDS. Est-ce qu’ils donnent le même résultat ? Des ratios de compression différents ?
  • Essaye de ne pas générer les MIP maps avec The Compressonator. Quel est le résultat ? Donne trois méthodes différentes pour corriger cela.

Références

  • Using texture compression in OpenGL , Sébastien Domine, NVIDIA
  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Tutoriel 6 : clavier et souris

Bienvenue dans le sixième tutoriel !

Tu vas apprendre à utiliser le clavier et la souris pour déplacer la caméra exactement comme dans un FPS.

L’interface

Comme le code va être réutilisé dans les autres tutoriels, on va placer le code dans un fichier à part : common/controls.cpp et déclarer les fonctions dans common/controls.hpp afin que le fichier tutorial06.cpp puisse les utiliser.

Le code du fichier tutoriel06.cpp ne change pas beaucoup par rapport au tutoriel précédent. La plus grande modification est le calcul de la matrice MVP à chaque image au lieu de la calculer une unique fois. On déplace donc ce code dans la boucle principale :

do{

    // ...

    // Compute the MVP matrix from keyboard and mouse input
    computeMatricesFromInputs();
    glm::mat4 ProjectionMatrix = getProjectionMatrix();
    glm::mat4 ViewMatrix = getViewMatrix();
    glm::mat4 ModelMatrix = glm::mat4(1.0);
    glm::mat4 MVP = ProjectionMatrix * ViewMatrix * ModelMatrix;

    // ...
}

Ce code nécessite trois nouvelles fonctions :

  • computeMatricesFromInputs() lit le clavier et la souris et calcule les matrices de projection et de vue. C’est ici que la magie opère
  • getProjectionMatrix() retourne simplement la matrice de projection calculée par computeMatricesFromInputs()
  • getViewMatrix() retourne simplement la matrice de vue calculée par computeMatricesFromInputs()

C’est une façon de faire. Bien sûr, si tu n’aimes pas ces fonctions, vas-y et change-les.

Voyons voir maintenant ce qu’il y a dans controls.cpp.

Le vrai code

On a besoin de quelques variables :

// position
glm::vec3 position = glm::vec3( 0, 0, 5 );
// horizontal angle : toward -Z
float horizontalAngle = 3.14f;
// vertical angle : 0, look at the horizon
float verticalAngle = 0.0f;
// Initial Field of View
float initialFoV = 45.0f;

float speed = 3.0f; // 3 units / second
float mouseSpeed = 0.005f;

Le champ de vision (FoV en anglais) est le niveau de zoom. 80° = un grand angle donc de grosses déformations. 60° - 45° : standard. 20° : gros zoom.

Premièrement, on va recalculer la position, l’angle horizontal, l’angle vertical et le champ de vision selon les entrées, puis on va calculer les matrices de vue et de projection à partir de la position, de l’angle horizontal, de l’angle vertical et du champ de vision.

Orientation

Lire les informations de la souris est facile :

// Get mouse position
int xpos, ypos;
glfwGetMousePos(&xpos, &ypos);

Mais on doit prendre garde à replacer le curseur au centre de l’écran ou il ira en dehors de la fenêtre et on ne sera plus capable de bouger.

// Reset mouse position for next frame
glfwSetMousePos(1024/2, 768/2);

Ce code fait l’hypothèse que la fenêtre est de 1024 * 768, ce qui n’est évidemment pas toujours le cas. Si tu veux tu peux utiliser glfwGetWindowSize pour récupérer la taille de la fenêtre.

Maintenant, on calcule nos angles de vision :

// Compute new orientation
horizontalAngle += mouseSpeed * deltaTime * float(1024/2 - xpos );
verticalAngle   += mouseSpeed * deltaTime * float( 768/2 - ypos );

Lisons ces lignes de droite à gauche :

  • 1024/2 - xpos correspond à la distance de la souris par rapport au centre de la fenêtre. Plus grande est la valeur, plus nous voulons tourner
  • float(…) convertit la position en un float (nombre à virgule flottante) afin que la multiplication soit exacte
  • mouseSpeed n’est là que pour ralentir ou accélérer les rotations. Modifie cette valeur à volonté ou laisse l’utilisateur la choisir
  • += : si tu n’as pas déplacé la souris, 1024/2 - xpos vaudra 0 et horizontalAngle += 0 ne modifie pas la variable. Si tu avais un ‘=’ à la place, tu serais retourné de force à l’orientation d’origine et cela à chaque image, ce qui n’est pas ce que l’on veut.

Maintenant, on peut calculer un vecteur qui représente, dans l’espace monde, la direction vers laquelle on regarde.

// Direction : Spherical coordinates to Cartesian coordinates conversion
glm::vec3 direction(
    cos(verticalAngle) * sin(horizontalAngle),
    sin(verticalAngle),
    cos(verticalAngle) * cos(horizontalAngle)
);

C’est un calcul standard, mais si tu ne connais pas sinus et cosinus, voici une courte explication en 2D :

La formule ci-dessus n’est qu’une généralisation pour la 3D.

Maintenant, on souhaite calculer le vecteur « haut » efficacement. Le « haut » n’est pas toujours en direction de +Y : si tu regardes vers le bas, par exemple, le vecteur « haut » sera en réalité horizontale. Voici un exemple avec deux caméras ayant la même position, la même direction, mais de vecteurs « haut » différents:

Dans ce cas, la seule constante est que le vecteur allant sur la droite est toujours horizontal. Vous pouvez vérifier cela en plaçant votre bras à l’horizontal et en regardant vers le haut, le bas et dans toutes les directions. Voici comment définir le « bon » vecteur « vers la droite » : sa coordonnée Y est 0, car le vecteur est horizontal et ses coordonnées X et Z sont exactement comme dans la figure ci-dessus, mais avec des angles tournés de 90°, ou Pi/2 radians.

// Right vector
glm::vec3 right = glm::vec3(
    sin(horizontalAngle - 3.14f/2.0f),
    0,
    cos(horizontalAngle - 3.14f/2.0f)
);

On a le vecteur « vers la droite » et une « direction » ( = vecteur vers « l’avant »). Le vecteur vers « le haut » est le vecteur qui est perpendiculaire à ceux-ci. Il y a un outil mathématique pratique qui rend cela très simple : le produit vectoriel.

// Up vector : perpendicular to both direction and right
glm::vec3 up = glm::cross( right, direction );

Pour se souvenir de ce que fait le produit vectoriel, c’est très simple. Rappele-toi la règle de la main droite du troisième tutoriel. Le premier vecteur est le pouce ; le second l’index et le résultat est le majeur. C’est très pratique.

Position

Le code est facile à comprendre. D’ailleurs, j’ai utilisé les touches fléchées à la place de aswd, car je suis sur un clavier azerty et aswd correspond à zqsd. C’est aussi différent avec les claviers qwerZ et passons les claviers coréens. Je ne sais même pas quelle disposition les coréens ont, mais je suppose que c’est différent.

// Move forward
if (glfwGetKey( GLFW_KEY_UP ) == GLFW_PRESS){
    position += direction * deltaTime * speed;
}
// Move backward
if (glfwGetKey( GLFW_KEY_DOWN ) == GLFW_PRESS){
    position -= direction * deltaTime * speed;
}
// Strafe right
if (glfwGetKey( GLFW_KEY_RIGHT ) == GLFW_PRESS){
    position += right * deltaTime * speed;
}
// Strafe left
if (glfwGetKey( GLFW_KEY_LEFT ) == GLFW_PRESS){
    position -= right * deltaTime * speed;
}

La seule chose particulière est deltaTime. Tu ne veux pas te déplacer d’une unité chaque image pour la simple raison que :

  • Si tu as un ordinateur rapide et que tu exécutes le programme à 60 FPS, tu te déplacera de 60*speed en une seconde
  • Si tu as un ordinateur lent et que tu exécutes le programme à 20 FPS, tu te déplacera de 20*speed en une seconde.

Comme le fait d’avoir un ordinateur plus rapide n’est pas une raison pour se déplacer plus vite, on doit faire dépendre la distance du « temps depuis la dernière image », ou « deltaTime ».

  • Si tu as un ordinateur rapide et que tu exécutes le programme à 60 FPS, tu te déplacera de 1/60speed en une image, donc 1speed en une seconde.
  • Si tu as un ordinateur lent et que tu exécutes le programme à 20 FPS, tu te déplacera de 1/20speed en une image, donc 1speed en une seconde.

Ce qui est bien mieux ! Le deltaTime est très facile à calculer :

double currentTime = glfwGetTime();
float deltaTime = float(currentTime - lastTime);

Champ de vision

Pour s’amuser, on peut aussi lier la roulette de la souris au champ de vision pour faire rapidement un zoom :

float FoV = initialFoV - 5 * glfwGetMouseWheel();

Calculer les matrices

Le calcul des matrices est maintenant très simple. On utilise les mêmes fonctions que précédemment mais avec les nouveaux paramètres.

// Projection matrix : 45&deg; Field of View, 4:3 ratio, display range : 0.1 unit <-> 100 units
ProjectionMatrix = glm::perspective(FoV, 4.0f / 3.0f, 0.1f, 100.0f);
// Camera matrix
ViewMatrix       = glm::lookAt(
    position,           // Camera is here
    position+direction, // and looks here : at the same position, plus "direction"
    up                  // Head is up (set to 0,-1,0 to look upside-down)
);

Résultat

Backface Culling

Maintenant que tu peux librement te déplacer, si tu vas dans le cube, tu vas remarquer que les polygones sont toujours affichés. Cela peut sembler évident, mais cette remarque ouvre en réalité une possibilité d’optimisation. En effet, normalement dans les applications, tu n’es jamais dans un object 3D.

L’idée consiste à dire au GPU de vérifier si la caméra est derrière ou devant le triangle. Si elle est devant, afficher le triangle ; si elle est derrière, et que le modèle est fermé et que l’on n’est pas dans le modèle, alors il y aura un autre triangle devant et personne ne le remarquera, sauf que tout sera plus rapide : deux fois moins de triangles en moyenne !

Le mieux, c’est qu’il est très facile à calculer. Le GPU calcule la normale du triangle (en utilisant le produit scalaire, tu te rappels ?) et vérifie si la normale est orientée vers la caméra ou non.

Cela possède un coût malheureusement : l’orientation du triangle est implicite. Cela signifie que si tu inverses deux sommets dans ton buffer(tampon) de sommets, tu vas probablement avoir un trou. Mais cela vaut généralement le petit travail supplémentaire. Souvent, tu n’as qu’à cliquer sur « inverser les normales » dans ton logiciel de modélisation 3D (qui va, en réalité, inverser les sommets et donc les normales) et tout sera parfait.

Voici comment on active le backface culling :

// Cull triangles which normal is not towards the camera
glEnable(GL_CULL_FACE);

Exercices

  • Restreins verticalAngle afin de ne pas pouvoir retourner la caméra de haut en bas (mais seulement de gauche à droite)
  • Crée une caméra qui tourne autour de l’objet (position = ObjectCenter + ( radius * cos(time), height, radius * sin(time) ) ) ; lie le rayon/hauteur/temps au clavier/souris, ou autre
  • Amuse-toi !
  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Tutoriel 7 : Charger un modèle 3D

Jusqu’à maintenant, on avait manuellement codé le cube dans le code source. Je suis sûr que vous êtes d’accord pour dire que c’est pénible et pas pratique.

Dans ce tutoriel, on apprendra à charger un modèle 3D à partir d’un fichier. On fera exactement comme on a fait pour les textures : on va écrire un simple et très limité chargeur de modèle 3D et je vais vous donner quelques pistes vers des bibliothèques qui le font mieux que nous.

Pour garder ce tutoriel aussi simple que possible, on utilisera le format de fichier OBJ, qui est à la fois simple et très commun. Une fois encore, pour garder les choses simples, on gérera les fichiers OBJ n’ayant qu’une coordonnée UV et qu’une normale par sommet (tu n’as pas à savoir ce qu’est une normale pour le moment).

Charger le fichier OBJ

La fonction, située dans common/objloader.cpp, et déclarée dans common/objloader.hpp, aura la signature suivante :

bool loadOBJ(
    const char * path,
    std::vector < glm::vec3 > & out_vertices,
    std::vector < glm::vec2 > & out_uvs,
    std::vector < glm::vec3 > & out_normals
)

On souhaite que loadOBJ lise le fichier « path », écrive les données dans out_vertices/out_uvs/out_normals et retourne false si quelque chose s’est mal passé. *std::vector* est la façon de déclarer un tableau de glm::vec3 en C++ dont la taille peut être modifiée à volonté : cela n'a rien à voir avec un vecteur mathématique. Sincèrement, ce n'est qu'un tableau. Finalement, le & signifie que la fonction sera capable de modifier les std::vector.

Exemple de fichier OBJ

Un fichier OBJ ressemble plus ou moins à ceci : ```

Blender3D v249 OBJ File: untitled.blend

# www.blender3d.org mtllib cube.mtl v 1.000000 -1.000000 -1.000000 v 1.000000 -1.000000 1.000000 v -1.000000 -1.000000 1.000000 v -1.000000 -1.000000 -1.000000 v 1.000000 1.000000 -1.000000 v 0.999999 1.000000 1.000001 v -1.000000 1.000000 1.000000 v -1.000000 1.000000 -1.000000 vt 0.748573 0.750412 vt 0.749279 0.501284 vt 0.999110 0.501077 vt 0.999455 0.750380 vt 0.250471 0.500702 vt 0.249682 0.749677 vt 0.001085 0.750380 vt 0.001517 0.499994 vt 0.499422 0.500239 vt 0.500149 0.750166 vt 0.748355 0.998230 vt 0.500193 0.998728 vt 0.498993 0.250415 vt 0.748953 0.250920 vn 0.000000 0.000000 -1.000000 vn -1.000000 -0.000000 -0.000000 vn -0.000000 -0.000000 1.000000 vn -0.000001 0.000000 1.000000 vn 1.000000 -0.000000 0.000000 vn 1.000000 0.000000 0.000001 vn 0.000000 1.000000 -0.000000 vn -0.000000 -1.000000 0.000000 usemtl Material_ray.png s off f 5/1/1 1/2/1 4/3/1 f 5/1/1 4/3/1 8/4/1 f 3/5/2 7/6/2 8/7/2 f 3/5/2 8/7/2 4/8/2 f 2/9/3 6/10/3 3/5/3 f 6/10/4 7/6/4 3/5/4 f 1/2/5 5/1/5 2/9/5 f 5/1/6 6/10/6 2/9/6 f 5/1/7 8/11/7 6/10/7 f 8/11/7 7/12/7 6/10/7 f 1/2/8 2/9/8 3/13/8 f 1/2/8 3/13/8 4/14/8 ```

Soit :

  • ’#’’ est un commentaire, tout comme ‘//’’ en C++
  • usemtl et mtllib décrivent l’aspect du modèle. On ne les utilise pas de ce tutoriel
  • v est un sommet
  • vt est une coordonnée de texture (UV) pour un sommet
  • vn est la normale pour un sommet
  • f est une face

v, vt et vn sont simples à comprendre. f est un peu plus compliqué. Pour f 8/11/7 7/12/7 6/10/7 :

  • 8/11/7 décrit le premier sommet du triangle
  • 7/12/7 décrit le second sommet du triangle
  • 6/10/7 décrit le troisième sommet du triangle (doh)
  • Pour le premier sommet, 8 indique quel sommet utiliser. Dans ce cas, ce sera donc -1.0000000 1.0000000 -1.0000000 (les indices commencent à partir de 1 et non pas de 0 comme en C++)
  • 11 indique quelle coordonnée de texture utiliser. Dans ce cas, ce sera donc 0.748355 0.998230
  • 7 indique quelle normale utiliser. Dans ce cas, ce sera donc 0.0000000 1.0000000 -0.0000000

Ces nombres sont appelés des indices. Ils sont pratiques, car si plusieurs sommets partagent la même position, vous n’avez qu’à écrire une seule fois « v » dans le fichier et l’utiliser plusieurs fois. Cela économise de la mémoire.

La mauvaise nouvelle est que l’on ne peut pas indiquer à OpenGL d’utiliser un indice pour la position, un autre pour la texture et un dernier pour la normale. Donc l’approche que j’ai choisie est de faire un modèle classique, non indexé et de gérer l’indexage plus tard, dans le neuvième tutoriel qui expliquera comment contourner cela.

Créer un fichier OBJ avec Blender

Comme notre chargeur va être très limité, on doit être très précautionneux et définir les bonnes options lors de l’exportation du fichier. Voici comment cela devrait être dans Blender :

Lecture du fichier

Ok, voilà le code. On doit définir des variables temporaires dans lesquelles on stocke le contenu du .obj :

std::vector< unsigned int > vertexIndices, uvIndices, normalIndices;
std::vector< glm::vec3 > temp_vertices;
std::vector< glm::vec2 > temp_uvs;
std::vector< glm::vec3 > temp_normals;

Depuis le cinquième tutoriel : un cube coloré, on sais comment ouvrir un fichier :

FILE * file = fopen(path, "r");
if( file == NULL ){
    printf("Impossible to open the file !\n");
    return false;
}

On le lit jusqu’à la fin :

while( 1 ){

    char lineHeader[128];
    // read the first word of the line
    int res = fscanf(file, "%s", lineHeader);
    if (res == EOF)
        break; // EOF = End Of File. Quit the loop.

    // else : parse lineHeader

On fait l’hypothèse que le premier mot d’une ligne ne fait pas plus de 128 caractères, ce qui est une hypothèse très idiote. Mais c’est un chargeur pour s’amuser, donc c’est ok.

Premièrement, on s’occupe les sommets :

if ( strcmp( lineHeader, "v" ) == 0 ){
    glm::vec3 vertex;
    fscanf(file, "%f %f %f\n", &vertex.x, &vertex.y, &vertex.z );
    temp_vertices.push_back(vertex);

Si le premier mot de la ligne est ‘v’, alors le reste doit être trois nombres flottants, donc on crée un glm::vec3 à partir de ceux-ci et on l’ajoute au vecteur.

}else if ( strcmp( lineHeader, "vt" ) == 0 ){
    glm::vec2 uv;
    fscanf(file, "%f %f\n", &uv.x, &uv.y );
    temp_uvs.push_back(uv);

Si ce n’est pas ‘v’ mais un ‘vt’, alors le reste doit être deux flottants, donc on crée un glm::vec2 et on l’ajoute au vecteur.

Même chose pour les normales :

}else if ( strcmp( lineHeader, "vn" ) == 0 ){
    glm::vec3 normal;
    fscanf(file, "%f %f %f\n", &normal.x, &normal.y, &normal.z );
    temp_normals.push_back(normal);

Et maintenant le ‘f’, qui est plus difficile :

}else if ( strcmp( lineHeader, "f" ) == 0 ){
    std::string vertex1, vertex2, vertex3;
    unsigned int vertexIndex[3], uvIndex[3], normalIndex[3];
    int matches = fscanf(file, "%d/%d/%d %d/%d/%d %d/%d/%d\n", &vertexIndex[0], &uvIndex[0], &normalIndex[0], &vertexIndex[1], &uvIndex[1], &normalIndex[1], &vertexIndex[2], &uvIndex[2], &normalIndex[2] );
    if (matches != 9){
        printf("File can't be read by our simple parser : ( Try exporting with other options\n");
        return false;
    }
    vertexIndices.push_back(vertexIndex[0]);
    vertexIndices.push_back(vertexIndex[1]);
    vertexIndices.push_back(vertexIndex[2]);
    uvIndices    .push_back(uvIndex[0]);
    uvIndices    .push_back(uvIndex[1]);
    uvIndices    .push_back(uvIndex[2]);
    normalIndices.push_back(normalIndex[0]);
    normalIndices.push_back(normalIndex[1]);
    normalIndices.push_back(normalIndex[2]);

Le code est en réalité très similaire au précédent, sauf qu’il y a plus de données à lire.

Traiter les données

Donc, ce que l’on a fait c’est simplement changer la « forme » des données. On avait une chaîne de caractères, on a maintenant un ensemble de std::vector. Mais ce n’est pas suffisant, nous avons à mettre ceux-ci dans une forme que OpenGL aime. Plus précisément en retirant les indices et en ayant que des glm::vec3 à la place. Cette opération est appelée indexation.

On parcourt chaque sommet (chaque v/vt/vn) pour chaque triangle (chaque ligne avec un « f ») :

    // For each vertex of each triangle
    for( unsigned int i=0; i < vertexIndices.size(); i++ ){

L’indice de la position du sommet est vertexIndices[i] :

unsigned int vertexIndex = vertexIndices[i];

Donc la position est temp_vertices[ vertexIndex-1 ] (il y a un -1 car le C++ commence à compter à partir de 0 alors que l’indice du fichier OBJ débute à 1, tu te rappelez ?) :

glm::vec3 vertex = temp_vertices[ vertexIndex-1 ];

Et cela donne la position du nouveau sommet :

out_vertices.push_back(vertex);

La même chose est appliquée pour les UV et les normales et c’est fini !

Utiliser les données chargées

Une fois que l’on a cela, il n’y a presque plus rien à faire. À la place de déclarer l’habituel static const GLfloat g_vertex_buffer_data[] = {…}, on déclare un std::vector de sommets (même chose pour les UV et les normales). Ensuite on appel la fonction loadOBJ avec les bons paramètres :

// Read our .obj file
std::vector< glm::vec3 > vertices;
std::vector< glm::vec2 > uvs;
std::vector< glm::vec3 > normals; // Won't be used at the moment.
bool res = loadOBJ("cube.obj", vertices, uvs, normals);

Et envoie tes vecteurs à OpenGL à la place de tes tableaux :

glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(glm::vec3), &vertices[0], GL_STATIC_DRAW);

And that’s it !

Résultat

Désolé pour la texture naze, je NE suis PAS un bon artiste :( Toute contribution est bienvenue !

Autres formats/chargeurs

Ce petit chargeur devrait te suffire pour démarrer, mais ne sera d’aucune utilité dans la vraie vie. Regarde la page des outils et liens utiles pour une sélection d’outils que tu peux utiliser. Note, par contre, qu’il est préférable d’attendre le neuvième tutoriel avant d’essayer d’utiliser l’un d’entre eux.

  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Tutoriel 8 : shaders de base

Dans ce huitième tutoriel, on va voir comment créer quelques shaders de base. Cela inclut :

  • Obtenir un effet plus brillant lorsque l’on est plus proche de la source de lumière
  • Obtenir une surbrillance lorsque l’on regarde le reflet d’une lumière (lumière spéculaire)
  • Obtenir un effet plus sombre lorsque la lumière n’est pas directement devant le modèle (lumière diffuse) ;
  • Enormément tricher (lumière ambiante).

Cela n’inclut pas :

  • Les ombres : c’est un immense sujet nécessitant son(ses) propre(s) tutoriel(s)
  • Les reflets similaires au miroir (incluant les reflets d’eau)
  • N’importe quelle interaction sophistiquée avec la matière lumineuse comme le « subsurface scattering » (comme la cire)
  • Les matériaux anisotropes (comme le métal brossé)
  • Les shaders basés sur la physique, qui essaient d’imiter au mieux la réalité
  • L’occlusion ambiante (il fait plus sombre dans une cave)
  • Le mélange des couleurs (un tapis rouge fera qu’un plafond blanc sera un peu rouge)
  • La transparence
  • Tout genre d’illumination globale quelle qu’elle soit (c’est le nom regroupant toutes les techniques précédentes)

En un mot : simple.

Les normales

Au cours des tutoriels précédents tu as utilisé des normales sans vraiment savoir ce que c’est.

Les normales de triangle

La normale d’un plan est un vecteur de longueur 1 perpendiculaire à ce plan.

La normale d’un triangle est un vecteur de longueur 1 qui est perpendiculaire à ce triangle. Il est facilement calculé en utilisant le produit vectoriel de deux de ses côtés (le produit vectoriel de a et b produit un vecteur qui est perpendiculaire aux deux vecteurs a et b, tu te souviens ?), normalisé : sa longueur est ramenée à 1. En pseudo code :

triangle ( v1, v2, v3 )
edge1 = v2-v1
edge2 = v3-v1
triangle.normale = cross(edge1, edge2).normalize()

Ne mélange pas la normale et normalize(). normalize() divise un vecteur (n’importe quel vecteur et pas seulement une normale) par sa longueur afin que sa nouvelle longueur soit de 1. normale n’est qu’un nom pour quelques vecteurs qui représentent, eh bien, la normale.

Les normales de sommet

Par extension, on appelle la normale d’un sommet la combinaison des normales des triangles alentour. Cela est pratique car dans un vertex shader, on gère des sommets, donc c’est mieux d’avoir l’information sur le sommet. Et en aucun cas, on ne peut avoir d’informations sur les triangles en OpenGL. En pseudo code :

vertex v1, v2, v3, ....
triangle tr1, tr2, tr3 // all share vertex v1
v1.normal = normalize( tr1.normal + tr2.normal + tr3.normal )

Utiliser les normales de sommet en OpenGL

Pour utiliser les normales en OpenGL, c’est très simple. Une normale est un attribut de sommet, tout comme sa position, sa couleur, ses coordonnées UV… donc, on fait comme d’habitude. La fonction loadOBJ du septième tutoriel lit déjà les normales à partir du fichier OBJ.

GLuint normalbuffer;
 glGenBuffers(1, &normalbuffer);
 glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
 glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(glm::vec3), &normals[0], GL_STATIC_DRAW);

et

 // 3rd attribute buffer : normals
 glEnableVertexAttribArray(2);
 glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
 glVertexAttribPointer(
     2,                                // attribute
     3,                                // size
     GL_FLOAT,                         // type
     GL_FALSE,                         // normalized?
     0,                                // stride
     (void*)0                          // array buffer offset
 );

et c’est suffisant pour démarrer.

La partie diffuse

L’importance de la normale de la surface

Lorsque la lumière touche un objet, une importante partie de celle-ci est reflétée dans toutes les directions. C’est la composante « diffuse ». (On verra bientôt ce qui se passe avec l’autre partie.)

Lorsqu’un certain flux de lumière arrive sur la surface, la surface est illuminée différemment selon l’angle avec lequel la lumière arrive.

Si la lumière est perpendiculaire à la surface, elle est concentrée sur une petite surface. Si elle arrive de biais, la même quantité de lumière s’étale sur une plus grande surface :

Cela signifie que chaque point de la surface sera plus sombre avec une lumière de biais (mais rappelez-vous, plus nombreux sont les points étant illuminés, donc la quantité de lumière totale reste la même).

Cela signifie que lorsque vous calculez la couleur d’un pixel, l’angle entre le rayon de lumière et la normale de la surface entre en jeu. Donc, on obtient :

// Cosine of the angle between the normal and the light direction,
// clamped above 0
//  - light is at the vertical of the triangle -> 1
//  - light is perpendicular to the triangle -> 0
float cosTheta = dot( n,l );

color = LightColor * cosTheta;

Dans ce code, n est la normale de la surface et l est le vecteur unitaire qui va de la surface vers la lumière (et non le contraire, même si ce n’est pas intuitif, cela rend les mathématiques plus simples).

Méfie toi du signe

Quelque chose manque dans la formule du cosTheta. Si la lumière est derrière le triangle, n et l seront opposés, donc n.l sera négatif. Cela signifie que la couleur aura une valeur négative, ce qui ne veut rien dire. Donc nous devons limiter cosTheta à 0 :

// Cosine of the angle between the normal and the light direction,
// clamped above 0
//  - light is at the vertical of the triangle -> 1
//  - light is perpendicular to the triangle -> 0
//  - light is behind the triangle -> 0
float cosTheta = clamp( dot( n,l ), 0,1 );

color = LightColor * cosTheta;

Couleur de matériel

Bien sûr, la couleur de sortie dépend aussi de la couleur du matériel. Dans cette image, la lumière blanche est composée de lumière verte, rouge et bleue. Lors de la collision avec le matériel rouge, les lumières verte et bleue sont absorbées et seule la lumière rouge reste.

On peut modéliser cela par une simple multiplication :

color = MaterialDiffuseColor * LightColor * cosTheta;

Modéliser la lumière

Premièrement, on fera l’hypothèse que l’on a une lumière ponctuelle qui émet dans toutes les directions de l’espace, comme une bougie.

Avec une telle lumière, le flux lumineux que recevra la surface dépendra de sa distance avec la source de lumière : plus loin elle est, moins elle est illuminée. En fait, la lumière diminuera avec le carré de la distance :

color = MaterialDiffuseColor * LightColor * cosTheta / (distance*distance);

Enfin, on a besoin d’un autre paramètre pour contrôler la puissance de la lumière. Cela peut être ajouté à LightColor (et on le fera dans un prochain tutoriel), mais pour le moment utilisez deux variables : la couleur (par exemple, blanche) et la puissance (par exemple 60 watts).

color = MaterialDiffuseColor * LightColor * LightPower * cosTheta / (distance*distance);

Combiner le tout

Pour que ce code fonctionne, on a besoin de plusieurs paramètres (les différentes couleurs et puissances) et d’un peu plus de code.

MaterialDiffuseColor est simplement récupéré à partir de la texture.

LightColor et LightPower sont définies dans le shader avec les variables uniformes GLSL.

cosTheta dépend de n et l. On peut les exprimer dans n’importe quel espace de coordonnées tant qu’il est le même pour les deux. On choisit l’espace caméra car c’est facile de calculer la position de la lumière dans cet espace :

// Normal of the computed fragment, in camera space
 vec3 n = normalize( Normal_cameraspace );
 // Direction of the light (from the fragment to the light)
 vec3 l = normalize( LightDirection_cameraspace );

avec les variables Normal_cameraspace et LightDirection_cameraspace calculées dans le vertex shader et passées au fragment shader :

// Output position of the vertex, in clip space : MVP * position
gl_Position =  MVP * vec4(vertexPosition_modelspace,1);

// Position of the vertex, in worldspace : M * position
Position_worldspace = (M * vec4(vertexPosition_modelspace,1)).xyz;

// Vector that goes from the vertex to the camera, in camera space.
// In camera space, the camera is at the origin (0,0,0).
vec3 vertexPosition_cameraspace = ( V * M * vec4(vertexPosition_modelspace,1)).xyz;
EyeDirection_cameraspace = vec3(0,0,0) - vertexPosition_cameraspace;

// Vector that goes from the vertex to the light, in camera space. M is ommited because it's identity.
vec3 LightPosition_cameraspace = ( V * vec4(LightPosition_worldspace,1)).xyz;
LightDirection_cameraspace = LightPosition_cameraspace + EyeDirection_cameraspace;

// Normal of the the vertex, in camera space
Normal_cameraspace = ( V * M * vec4(vertexNormal_modelspace,0)).xyz; // Only correct if ModelMatrix does not scale the model ! Use its inverse transpose if not.

Ce code peut sembler impressionnant mais il n’y a rien que l’on n’ait pas vu dans le troisième tutoriel : les matrices. J’ai fait attention d’écrire le nom de chaque espace dans le nom des vecteurs pour qu’il soit plus simple de garder trace de ce qui se passe. Tu devrais aussi faire ça.

M et V sont les matrices de Modèle et de Vue, qui sont passées aux shaders de la même façon que MVP.

C’est l’heure de bosser

Tu as tout ce qu’il faut pour coder la lumière diffuse. Vas-y et apprend à la dure :)

Résultat

Avec seulement la composante diffuse, on obtient le résultat suivant (désolé pour la texture moche encore une fois) :

C’est mieux qu’avant, mais c’est encore bien incomplet. En particulier, l’arrière de Suzanne est complètement noir car nous avons utilisé clamp().

La composante ambiante

La composante ambiante est la plus grande triche qui existe !

On s’attend à ce que le dos de Suzanne reçoive plus de lumière car, dans la vraie vie, la lampe éclairerait le mur derrière, ce qui éclairerait (pas mal moins) l’arrière de l’objet.

Ca serai affreusement coûteux à calculer.

Donc, l’astuce habituelle est de simplement imiter cette lumière. En fait, le modèle 3D va émettre de la lumière afin de ne pas apparaître complètement noir.

Cela peut être fait de cette façon :

vec3 MaterialAmbientColor = vec3(0.1,0.1,0.1) * MaterialDiffuseColor;

color =
 // Ambient : simulates indirect lighting
 MaterialAmbientColor +
 // Diffuse : "color" of the object
 MaterialDiffuseColor * LightColor * LightPower * cosTheta / (distance*distance) ;

Voyons ce que ça donne.

Résultat

Ok, donc c’est un petit peu mieux. Tu peux ajuster le (0.1, 0.1, 0.1) si tu souhaites un meilleur résultat.

La composante spéculaire

L’autre partie de la lumière qui est réfléchie l’est principalement dans la direction qui est le reflet de la source de lumière sur la surface. C’est la composante spéculaire.

Comme tu peux le voir dans cette image, cela forme une sorte de lobe. Dans les cas extrêmes, la composante diffuse peut être nulle, le lobe peut être très très étroit (toute la lumière est réfléchie dans une seule direction) et on obtiens un miroir.

(on peut effectivement ajuster les paramètres pour obtenir un miroir mais, dans notre cas, la seule chose que l’on prend en compte dans ce miroir est la lampe. Donc, cela ferait un miroir très étrange.)

// Eye vector (towards the camera)
vec3 E = normalize(EyeDirection_cameraspace);
// Direction in which the triangle reflects the light
vec3 R = reflect(-l,n);
// Cosine of the angle between the Eye vector and the Reflect vector,
// clamped to 0
//  - Looking into the reflection -> 1
//  - Looking elsewhere -> < 1
float cosAlpha = clamp( dot( E,R ), 0,1 );

color =
    // Ambient : simulates indirect lighting
    MaterialAmbientColor +
    // Diffuse : "color" of the object
    MaterialDiffuseColor * LightColor * LightPower * cosTheta / (distance*distance) ;
    // Specular : reflective highlight, like a mirror
    MaterialSpecularColor * LightColor * LightPower * pow(cosAlpha,5) / (distance*distance);

R est la direction vers laquelle la lumière reflète. E la direction inverse de la vue (tout comme nous l’avons fait pour l) ; si l’angle entre ces deux vecteurs est petit, cela signifie que l’on regarde directement dans le reflet.

pow(cosAlpha,5) est utilisé pour contrôler la largeur du lobe de lumière spéculaire. Augmentez le 5 pour obtenir un lobe plus mince.

Résultat final

Remarque la surbrillance spéculaire sur le nez et les sourcils.

Ce modèle de shaders a été utilisé pendant des années de par sa simplicité. Il possède un grand nombre de problèmes, donc il est remplacé par des modèles basés sur la physique comme le BRDF à microfacettes, mais on verra cela plus tard.

Dans le prochain tutoriel, on apprendra à améliorer les performances de votre VBO. Cela sera le premier tutoriel intermédiaire !

  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Le principe de l’indexation

Jusqu’à présent, lors de la construction du VBO, on avait dupliqué les sommets à chaque fois que deux triangles partageaient un côté.

Dans ce tutoriel, on va s’initier à l’indexation, qui permet de réutiliser le même sommet encore et encore. Cela est possible grâce au buffer (tampon) d’indices.

Le tampon d’indices contient des entiers, trois pour chaque triangle dans le modèle, faisant référence aux différents tampons d’attributs (position, couleur, coordonnées UV, autres coordonnées UV, normales…). C’est un peu comme dans le fichier .OBJ, avec une grande différence : il n’y a qu’un SEUL tampon d’indices. Cela signifie que pour qu’un sommet soit partagé entre deux triangles, tous les attributs doivent être les mêmes.

Partagé ou séparé ?

On va prendre en exemple les normales. Dans ce schéma, l’artiste qui a créé ces deux triangles voulait certainement qu’ils représentent une surface lisse. On peut donc fusionner les normales des deux triangles en une normale d’un seul sommet. Pour des soucis de visualisation, j’ai ajouté une ligne rouge qui représente l’aspect de la surface lisse.

Dans le second schéma, par contre, l’artiste a visiblement voulu une « couture », un côté pointu. Mais si on fusionne les normales, cela signifie que le shader va effectuer un adoucissement, comme d’habitude, et créer un aspect plus doux, comme dans le cas précédent :

Donc, dans ce cas il est préférable d’avoir deux normales distinctes, une pour chaque sommet. La seule façon de faire cela en OpenGL est de dupliquer entièrement le sommet, avec son ensemble d’attributs :

VBO indexé en OpenGL

C’est très simple d’utiliser l’indexation. Premièrement, tu vas créer un tampon supplémentaire, que tu vas remplir avec les bons indices. Le code est le même qu’avant, mais maintenant c’est un ELEMENT_ARRAY_BUFFER, et non un ARRAY_BUFFER.

std::vector<unsigned int> indices;
// fill "indices" as needed

// Generate a buffer for the indices
 GLuint elementbuffer;
 glGenBuffers(1, &elementbuffer);
 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);
 glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW);

Et pour dessiner le modèle, remplaces simplement glDrawArrays par :

// Index buffer
 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);
 // Draw the triangles !
 glDrawElements(
     GL_TRIANGLES,      // mode
     indices.size(),    // count
     GL_UNSIGNED_INT,   // type
     (void*)0           // element array buffer offset
 );

Il est préférable d’utiliser des « unsigned short » que des « unsigned int », car cela utilise moins de mémoire et du coup accélère le programme.

Remplir le buffer (tampon) d’indices

On a maintenant un problème. Comme je l’ai dit avant, OpenGL ne peut utiliser qu’un seul tampon d’indices, alors que le format OBJ (ainsi que d’autres formats de fichier 3D populaires comme Collada) utilise un tampon d’indices par attribut. Cela signifie que d’une façon ou d’une autre, on doit convertir N tampons d’indices en un.

L’algorithme pour ce faire est le suivant :

Pour chaque sommet en entrée
    Essayer de trouver un sommet similaire ( = identique pour tous les attributs) parmi ceux déjà en sortie
    Si trouvé :
        Un sommet similaire est déjà dans le VBO, on l'utilise !
    Si non trouvé :
        Aucun sommet similaire trouvé, ajouter le au VBO

Le code C++ peut être trouvé dans le fichier common/vboindexer.cpp. Il est intensément commenté, donc si tu comprends l’algorithme ci-dessus, ça devrait aller.

Le critère pour la similarité des sommets est que la position, la normale et les coordonnées UV doivent êtres identiques. Tu devras l’adapter si tu ajoutes plus d’attributs.

La recherche d’un sommet similaire est effectuée d’une manière linéaire pour garder l’algorithme simple. Une std::map serait plus appropriée.

Extra : le compteur de FPS

Ce n’est pas directement lié à l’indexation, mais c’est une bonne occasion de jeter un œil au compteur de FPS car on peut possiblement voir une amélioration de la vitesse grâce à l’indexation. D’autres outils pour les performances sont disponibles dans la page Outils - débogueurs.

  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Tutoriel 10 : la transparence

La canal alpha

Le concept du canal alpha est très simple. À la place d’écrire un résultat en RGB, on écris un résultat en RGBA :

// Données de sorties : c'est maintenant un vec4
out vec4 color;

Les trois premières composantes sont toujours accessibles avec .xyz (ou .rgb), tandis que la dernière est accessible avec .a :

color.a = 0.3;

Contre toute logique, alpha = opacité, donc alpha = 1 signifie complètement opaque alors que alpha = 0 signifie complètement transparent.

Ici, on a simplement codé en dur le canal alpha à 0.3, mais tu préféreras sûrement utiliser une variable uniforme ou la lire à partir d’une texture RGBA (le format DDS supporte le canal alpha).

Voici le résultat. Assures-toi de désactiver le « backface culling » (suppression des faces arrières) (glDisable(GL_CULL_FACE)) car comme on peut voir à travers le modèle, on pourrait voir si on ne dessine pas la face « arrière ».

L’ordre est important

La capture d’écran précédente paraît correcte, mais c’est uniquement car on est chanceux.

Le problème

Ici, j’ai dessiné deux carrés ayant 50 % d’alpha, un vert et un rouge. Tu peux voir que l’ordre est important, la couleur finale donne une importante piste visuelle sur la perception de la profondeur.

Ce phénomène se produit aussi sur la scène. Si tu change un peu ton point de vue :

On voit tout de suite le problème apparaitre. En fait, c’est un problème très complexe. Tu ne vois jamais énormément de transparence dans les jeux vidéo, n’est-ce pas ?

Solution classique

La solution classique est de trier tous les triangles transparents. Oui, TOUS les triangles transparents.

  • Dessine la partie du monde opaque afin que le tampon de profondeur puisse déjà rejeter les triangles transparents cachés
  • Trie les triangles transparents, du plus loin aux plus proches
  • Dessine les triangles transparents

Tu peux trier ce que tu veux avec qsort (en C) ou std::sort (en C++). Je n’entrerai pas dans les détails, car…

Mise en garde

Faire comme cela fonctionne (plus de détails dans la section suivante), mais :

  • Tu vas être limité par la bande passante. En effet, chaque fragment sera écrit 10, 20 fois ou même plus. Cela est beaucoup trop pour le pauvre bus mémoire. Habituellement, le tampon de profondeur permet de rejeter assez de fragments « lointains », mais là, tu les tries explicitement, faisant que le tampon de profondeur devient totalement inutile
  • Tu vas faire cela quatre fois par pixel (on utilise le 4xMSAA), sauf si tu utilises une optimisation plus intelligente
  • Le tri des triangles prend du temps
  • Si tu dois changer de texture, ou pire, de shader, de triangle en triangle, tu vas avoir de sérieux problèmes de performance. Ne le fait juste pas.

Une solution assez bonne est souvent de :

  • Limiter à un maximum le nombre de polygones transparents
  • Utiliser le même shader et la même texture pour tous les polygones transparents
  • S’ils sont sensés être très différents, utilise ta texture (atlas) !
  • Si tu peux éviter le tri et que ça ne soit pas trop moche, tu peux t’estimer chanceux :)

Transparence indépendante de l’ordre

De nombreuses autres techniques sont intéressantes si ton moteur a vraiment, vraiment besoin de l’état de l’art de la transparence :

  • Le papier original 2001 Depth Peeling : résultats parfait au pixel près, pas très rapide
  • Dual Depth Peeling : une petite amélioration
  • De nombreux papiers sur le bucket sort utilisent un tableau de fragments ; et les tris par profondeur dans le shader
  • Démonstration d’ATI Mecha : bon et rapide, mais compliqué à implémenter. Utilise une liste chaînées de fragments
  • Variation de la technique d’ATI par Cyril Crassin : implémentation encore plus compliquée.

Même un jeu récent comme Little Big Planet, s’exécutant sur une console puissante, utilise une seul couche de transparence.

La fonction de mélange

Afin que le code précédent fonctionne, on doit initialiser la fonction de mélange :

// Enable blending
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

Ce qui signifie :

nouvelle couleur dans le buffer d'écran =
    alpha actuel dans le buffer d'écran * couleur actuelle dans le buffer d'écran +
    (1 - alpha actuel dans le tampon d'écran) * la couleur de sortie du shader

Exemple de l’image ci-dessus, avec le rouge au-dessus :

new color = 0.5*(0,1,0) + (1-0.5)*(1,0.5,0.5); // (the red was already blended with the white background)
new color = (1, 0.75, 0.25) = the same orange
  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Tutoriel 11 : texte 2D

Dans ce tutoriel, on va apprendre à afficher un texte en 2D par-dessus le contenu 3D. Ici, ce sera un simple chronomètre :

L’API

On va implémenter cette simple interface (dans common/text2D.h) :

void initText2D(const char * texturePath);
void printText2D(const char * text, int x, int y, int size);
void cleanupText2D();

Afin que le code fonctionne, que ce soit en 640 x 480 ou 1080p, x et y seront des coordonnées dans l’espace [0-800][0-600]. Le vertex shader adaptera les coordonnées pour correspondre à la taille de l’écran.

Regarde common/text2D.cpp pour voir l’implémentation complète.

La texture

initText2D lit simplement une texture et un couple de shaders. Il n’y a rien de spécial là-dedans, mais regarde à quoi ressemble la texture :

Cette texture a été générée avec CBFG, un des nombreux outils pouvant générer une texture à partir d’une police de caractères. Puis la texture a été importée dans Paint.NET où j’ai ajouté un fond rouge (seulement pour des raisons de visualisation : là où c’est rouge, cela devrait être transparent).

Le but de printText2D sera donc de générer des rectangles avec les positions écran appropriées et les coordonnées de texture qui vont bien.

L’affichage

On doit remplir ces tampons :

std::vector<glm::vec2> vertices;
std::vector<glm::vec2> UVs;

Pour chaque caractère, on calcule les coordonnées des quatre sommets définissant le rectangle puis on ajoute les deux triangles qui le compose :

for ( unsigned int i=0 ; i<length ; i++ ){

    glm::vec2 vertex_up_left    = glm::vec2( x+i*size     , y+size );
    glm::vec2 vertex_up_right   = glm::vec2( x+i*size+size, y+size );
    glm::vec2 vertex_down_right = glm::vec2( x+i*size+size, y      );
    glm::vec2 vertex_down_left  = glm::vec2( x+i*size     , y      );

    vertices.push_back(vertex_up_left   );
    vertices.push_back(vertex_down_left );
    vertices.push_back(vertex_up_right  );

    vertices.push_back(vertex_down_right);
    vertices.push_back(vertex_up_right);
    vertices.push_back(vertex_down_left);

Maintenant les coordonnées UV. La coordonnée du coin supérieur gauche est calculée comme suit :

    char character = text[i];
    float uv_x = (character%16)/16.0f;
    float uv_y = (character/16)/16.0f;

Cela fonctionne (en quelque sorte - voyez ci-dessous) car le code ASCII pour A est 65.

65 % 16 = 1, donc A est sur la colonne numéro 1 (on commence à 0 !).

65 / 16 = 4, donc A est sur la ligne numéro 4 (c’est une division entière, donc ce n’est pas 4.0625 comme cela devrait l’être).

Les deux sont divisés par 16.0 pour rentrer dans l’échelle [0.0 - 1.0] nécessaire aux textures OpenGL.

Et maintenant, on doit faire une chose très proche de ce que nous faisions pour les sommets, mais pour les coordonnées de texture :

    glm::vec2 uv_up_left    = glm::vec2( uv_x           , 1.0f - uv_y );
    glm::vec2 uv_up_right   = glm::vec2( uv_x+1.0f/16.0f, 1.0f - uv_y );
    glm::vec2 uv_down_right = glm::vec2( uv_x+1.0f/16.0f, 1.0f - (uv_y + 1.0f/16.0f) );
    glm::vec2 uv_down_left  = glm::vec2( uv_x           , 1.0f - (uv_y + 1.0f/16.0f) );

    UVs.push_back(uv_up_left   );
    UVs.push_back(uv_down_left );
    UVs.push_back(uv_up_right  );

    UVs.push_back(uv_down_right);
    UVs.push_back(uv_up_right);
    UVs.push_back(uv_down_left);
 }

Le reste est habituel : lier les buffers (tampons), les remplir, sélectionner le bon shader, lier la texture, activer/lier/configurer les attributs de sommets, activer le mélange (pour la transparence) et appeller la fonction glDrawArrays. Hourra ! Tu as fini.

Très important ! Les coordonnées sont générées sur l’échelle [0-800][0-600]. En d’autres mots, il n’y a PAS BESOIN de matrices ici. Le vertex shader doit juste les passer à l’échelle [-1,1][-1,1] avec une simple opération mathématique (cela aurait aussi pu être fait en C++).

void main(){

    // Output position of the vertex, in clip space
    // map [0..800][0..600] to [-1..1][-1..1]
    vec2 vertexPosition_homoneneousspace = vertexPosition_screenspace - vec2(400,300); // [0..800][0..600] -> [-400..400][-300..300]
    vertexPosition_homoneneousspace /= vec2(400,300);
    gl_Position =  vec4(vertexPosition_homoneneousspace,0,1);

    // UV of the vertex. No special space for this one.
    UV = vertexUV;
}

Le fragment shader ne fait que très peu de choses aussi :

void main(){
    color = texture( myTextureSampler, UV );
}

D’ailleurs, n’utilise pas ce code en production, car il ne gère que l’alphabet latin. Ou alors ne vend rien en Inde, Chine, Japon (ou même Allemagne, car il n’y a pas de ß dans cette image). Cette texture marchera principalement en France (remarque la présence des é, à, ç, etc.) car elle a été générée avec ma locale. Et fait attention lors de l’adaptation du code d’autres tutoriels provenant d’autres sites, ou encore lorsque tu as recours à des bibliothèques, la plupart d’entre eux/elles utilisent OpenGL 2, qui n’est pas compatible avec l’UTF8. Malheureusement, je ne connais pas de bibliothèque qui gère UTF-8.

À propos de cela, tu dois lire The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!) (le minimum absolu que chaque développeur de logiciels devrait absolument connaître sur l’Unicode et les ensembles de caractères (pas d’excuse !) de Joel Spolsky.

Regarde aussi cet article de Valve si tu as besoin de grand texte.

  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Tutoriel 12 :Extensions

Avec chaque nouvelle génération, les performances des GPU augmentent, permettant ainsi d’afficher plus de triangles et plus de pixels. Néanmoins, la performance brute n’est pas le seul intérêt. NVIDIA, AMD et Intel améliorent aussi leurs cartes graphiques en y ajoutant des nouvelles fonctionnalités. En voici quelques exemples.

ARB_fragment_program

En 2002, les GPU n’avaient pas de vertex shader ni de fragment shader : tout était codé en dur dans la puce. C’était ce que l’on appelait le pipeline à fonctionnalités fixes (Fixed-Function Pipeline (FFP)). La plus récente version de l’API l’OpenGL, qui était alors OpenGL 1.3, ne proposait aucun moyen de créer, manipuler et utiliser les « shaders », car ils n’existaient même pas. Mais NVIDIA a décidé que cela pourrait être pratique de décrire le processus de rendu avec du code, à la place de la centaine d’indicateurs et de variables d’états. C’est ainsi qu’a été créé ARB_fragment_program : il n’y avait pas de GLSL, mais à la place, vous pouviez écrire des trucs comme :

!!ARBfp1.0 MOV result.color, fragment.color; END

Évidemment, pour indiquer à OpenGL d’utiliser un tel code, on avait besoin de fonctions spéciales, qui n’étaient pas encore dans OpenGL. Avant de plonger dans les explications, un autre exemple.

ARB_debug_output

Ok, tu te dis que ce ARB_fragment_program est trop vieux, ou que je suis un dinosaure, et que tu n’as certainement plus besoin de ce truc ? En fait, il y a de nouvelles extensions qui sont très pratiques. L’une d’entre elles est ARB_debug_output, qui expose une fonctionnalité qui n’existait pas dans OpenGL 3.3 mais que tu peux/dois tout de même utiliser. Elle définit des symboles comme GL_DEBUG_SYNCHRONOUS_ARB ou GL_DEBUG_SEVERITY_MEDIUM_ARB et des fonctions comme DebugMessageCallbackARB. Le bon point de cette extension est qu’à chaque fois que tu écris un code incorrect, comme :

glEnable(GL_TEXTURE); // Incorrect ! You probably meant GL_TEXTURE_2D !

Tu peux avoir une erreur et l’emplacement exact du problème. Tu viens donc d’apprendre que :

  • Les extensions c’est très utiles, même en OpenGL 3.3 moderne
  • Tu dois utiliser ARB_debug_output ! Voir les liens ci-dessous.

Récupérer une extension - la méthode difficile

La méthode « manuelle » pour vérifier si une extension est disponible est d’utiliser ce morceau de code (provenant du wiki OpenGL.org) :

int NumberOfExtensions;
glGetIntegerv(GL_NUM_EXTENSIONS, &NumberOfExtensions);
for(i=0; i<NumberOfExtensions; i++) {
  const GLubyte *ccc=glGetStringi(GL_EXTENSIONS, i);
  if ( strcmp(ccc, (const GLubyte *)"GL_ARB_debug_output") == 0 ){
    // The extension is supported by our hardware and driver
    // Try to get the "glDebugMessageCallbackARB" function :
    glDebugMessageCallbackARB  = (PFNGLDEBUGMESSAGECALLBACKARBPROC) wglGetProcAddress("glDebugMessageCallbackARB");
  }
}

Récupérer toutes les extension - la méthode facile

Somme toute, c’est très compliqué. Les bibliothèques comme GLEW, Glee, gl3w, etc. rendent cela plus facile. Par exemple, avec GLEW, tu n’as qu’à appeler glewInit() après avoir créé la fenêtre, et hop ! Tu as des variables pratiques qui sont créées pour toi comme :

if (GLEW_ARB_debug_output){ // Ta-Dah ! }

debug_output est un peu spéciale, car tu dois l’activer à la création du contexte. Dans GLFW, cela est fait avec glfwOpenWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, 1);

ARB vs EXT vs …

Le nom de chaque extension contient des informations sur sa disponibilité :

  • GL_ : toutes les plates-formes
  • GLX_ : Linux et Mac seulement (X11)

  • WGL_ : Windows seulement

  • EXT : une extension générique

  • ARB : l’extension a été acceptée par tous les membres du OpenGL Architecture Review Board (les extensions EXT sont souvent promues ARB au bout d’un moment)

  • NV/AMD/INTEL : assez évident =)

Concevoir avec les extensions

Le problème

Disons que ton application OpenGL 3.3 a besoin d’afficher quelques lignes larges. Tu peux écrire un vertex shader compliqué pour ce faire, ou simplement t’appuyer sur GL_NV_path_rendering, qui gère tous les problèmes pour toi.

Tu auras donc un code qui ressemble à ceci :

if ( GLEW_NV_path_rendering ){
    glPathStringNV( ... ); // Affiche la forme. Facile !
}else{
    // Sinon quoi ? Tu as toujours besoin d'afficher des lignes
    // sur les machines NVIDIA plus vieilles, sur les machines AMD, sur les machines INTEL !
    // Donc, tu dois tout de même l'implémenter toi-même !
}

Choisir la limite

Une possibilité est de choisir d’utiliser une extension lorsque le gain dans la qualité du rendu ou la performance dépasse les souffrances de la maintenance de deux codes.

Par exemple, Braid (le jeu 2D où vous voyagez dans le temps) possède plein d’effets de déformation d’images, lorsque vous jouez avec le temps, qui ne sont simplement pas affichés avec du matériel plus ancien.

Avec OpenGL 3.3 et supérieur, tu as déjà 99 % des outils dont tu vas avoir besoin. Quelques extensions peuvent être utiles, comme GL_AMD_pinned_memory, mais c’est différent d’il y a quelques années, lorsqu’avoir GL_ARB_framebuffer_object (utilisé pour le rendu sur texture), pouvait rendre le jeu dix fois plus beau.

Si tu dois gérer du matériel de dinosaure, alors OpenGL 3.3 ne sera pas disponible et tu vas avoir à utiliser OpenGL 2+ à la place. Tu ne pourras pas supposer que tu as toutes ces extensions fantaisistes et tu vas devoir te débrouiller sans.

Pour plus de détails, regardes par exemple la version OpenGL 2.1 du quatorzième tutoriel - rendu sur texture ligne 167 où je dois vérifier la présence de GL_ARB_framebuffer_object à la main.

Conclusion

Les extensions OpenGL fournissent une méthode efficace pour étendre les capacités d’OpenGL, suivant le GPU de l’utilisateur.

Bien que les extensions soient principalement utilisées pour les usages avancés depuis que la plupart des fonctionnalités sont déjà présentes dans le noyau de l’API OpenGL, il reste important de connaître comment elles fonctionnent et comment tu peux les utiliser pour améliorer ton logiciel - pour un coût de maintenance plus chère.

Pour continuer

  • Tutoriel debug_output par Aks, vous pouvez passer la première étape grâce à GLFW
  • Le registre des extensions OpenGL, toutes les spécifications des extensions. La bible
  • GLEW, le OpenGL Extension Wrangler Library
  • gl3w, Chargement simple du profil OpenGL 3/4 core
  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Tutoriel 13 : Normal Mapping

Bienvenue dans le treizième tutoriel ! Aujourd’hui on parle de normal mapping.

Depuis le huitème tutoriel : shaders de base, tu sais comment obtenir un ombrage décent en utilisant les normales des triangles. Jusqu’à présent, il existait un inconvénient : on n’avait qu’une seule normale par sommet dans chaque triangle et elle va varier doucement, contrairement aux couleurs, qui sont échantillonnées à partir d’une texture. L’idée de base de l’application des normales (normal mapping) est de donner aux normales des variations similaires.

Textures de normales

Une « texture de normales » ressemble à ça :

Dans chaque texel RGB est encodé un vecteur XYZ : chacune des composantes d’une couleur est comprise entre 0 et 1 et chacune des composantes d’un vecteur est entre -1 et 1, donc la conversion d’un texel en normale s’effectue ainsi :

normal = (2*color)-1 // on each component

La texture a une teinte bleue car après tout, la normale pointe vers « l’extérieur de la surface ». Comme toujours X va vers la droite ( dans le plan de la texture), Y vers le haut (toujours dans le plan de la texture), et donc, avec la règle de la main droite, Z pointe vers l’extérieur du plan de la texture.

Cette texture est appliquée exactement comme la texture de diffusion ; le gros problème est la conversion de notre normale, qui est exprimée dans l’espace de chaque triangle (espace tangent, aussi appelé espace de l’image), vers l’espace modèle (car c’est ce que l’on utilise dans notre équation d’ombrage).

Tangente et bitangente

Maintenant, que tu connais les matrices tellement bien que tu sais que pour définir un espace (dans notre cas, l’espace tangent), on a besoin de trois vecteurs. On a déjà le vecteur UP : c’est la normale, donnée par Blender ou calculée à partir du triangle à l’aide d’un produit scalaire. Elle est représentée en bleu, tout comme la teinte de la texture de normales :

Ensuite, on a besoin d’une tangente, T : un vecteur perpendiculaire à la surface. Mais il y a vraiment beaucoup de vecteurs correspondant à ce critère :

Lequel choisir ? En théorie, n’importe lequel, mais on doit être consistant avec ses voisins pour éviter de voir apparaître d’affreuses bordures. La méthode standard est d’orienter la tangente dans la même direction que les coordonnées de textures :

Comme on a besoin de trois vecteurs pour définir une base, on doit aussi calculer la bitangente B (qui peut être n’importe quel autre vecteur tangent, mais si tout est perpendiculaire, les calculs sont simplifiées) :

Voici l’algorithme : si on appelle deltaPos1 et deltaPos2 deux côtés de notre triangle et deltaUV1 et deltaUV2 les différences de coordonnées UV correspondante, on peut exprimer notre problème avec l’équation suivante :

deltaPos1 = deltaUV1.x * T + deltaUV1.y * B
deltaPos2 = deltaUV2.x * T + deltaUV2.y * B

Résoud les équations de ce système pour T et B et tu obtiens tes vecteurs ! Voir le code ci-dessous.

Une fois que l’on a les vecteurs T, B et N, on obtient aussi cette jolie matrice qui nous permet de passer de l’espace tangent à l’espace modèle :

Avec cette matrice TBN, on peut transformer les normales (extraites à partir de la texture) dans l’espace modèle. Par contre, habituellement c’est l’inverse que l’on fait : transformer tout de l’espace modèle vers l’espace tangent et garder les normales telles quelles. Tous les calculs sont faits dans l’espace tangent, ce qui ne change absolument rien.

Pour effectuer cette transformation inverse, on doit simplement prendre l’inverse de la matrice, ce qui dans ce cas (une matrice orthogonale, où chaque vecteur est perpendiculaire aux autres. Voir la section « Aller plus loin ».) est aussi sa transposée, moins coûteuse à calculer :

invTBN = transpose(TBN)

Soit :

Préparation du VBO

Calcul des tangentes et bitangentes

Comme les tangentes et bitangentes sont nécessaires en plus des normales, on doit les calculer pour la globalité du modèle. On fait cela dans une fonction à part :

void computeTangentBasis(
    // inputs
    std::vector<glm::vec3> & vertices,
    std::vector<glm::vec2> & uvs,
    std::vector<glm::vec3> & normals,
    // outputs
    std::vector<glm::vec3> & tangents,
    std::vector<glm::vec3> & bitangents
){

Pour chaque triangle, on calcule le côté (deltaPos) et le deltaUV.

    for ( int i=0; i < vertices.size(); i+=3){

        // Shortcuts for vertices
        glm::vec3 & v0 = vertices[i+0];
        glm::vec3 & v1 = vertices[i+1];
        glm::vec3 & v2 = vertices[i+2];

        // Shortcuts for UVs
        glm::vec2 & uv0 = uvs[i+0];
        glm::vec2 & uv1 = uvs[i+1];
        glm::vec2 & uv2 = uvs[i+2];

        // Edges of the triangle : postion delta
        glm::vec3 deltaPos1 = v1-v0;
        glm::vec3 deltaPos2 = v2-v0;

        // UV delta
        glm::vec2 deltaUV1 = uv1-uv0;
        glm::vec2 deltaUV2 = uv2-uv0;

On peut utiliser la formule pour calculer la tangente et la bitangente :

        float r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);
        glm::vec3 tangent = (deltaPos1 * deltaUV2.y   - deltaPos2 * deltaUV1.y)*r;
        glm::vec3 bitangent = (deltaPos2 * deltaUV1.x   - deltaPos1 * deltaUV2.x)*r;

Finalement, on remplit les tampons tangents et bitangents. Rappels-toi, ces tampons ne sont pas encore indexés, donc chaque sommet possède sa propre copie.

        // Set the same tangent for all three vertices of the triangle.
        // They will be merged later, in vboindexer.cpp
        tangents.push_back(tangent);
        tangents.push_back(tangent);
        tangents.push_back(tangent);

        // Same thing for binormals
        bitangents.push_back(bitangent);
        bitangents.push_back(bitangent);
        bitangents.push_back(bitangent);

    }

Indexation

L’indexation du VBO est très similaire à ce que l’on avait l’habitude de faire, mais il y a une légère différence.

Si on trouve un sommet similaire (même position, même normale, même coordonnées de texture), on ne souhaite pas utiliser sa tangente et sa bitangente ; au contraire, on souhaite en faire la moyenne (entre ce sommet et celui similaire). Donc, modifie un peu le vieux code :

        // Try to find a similar vertex in out_XXXX
        unsigned int index;
        bool found = getSimilarVertexIndex(in_vertices[i], in_uvs[i], in_normals[i],     out_vertices, out_uvs, out_normals, index);

        if ( found ){ // A similar vertex is already in the VBO, use it instead !
            out_indices.push_back( index );

            // Average the tangents and the bitangents
            out_tangents[index] += in_tangents[i];
            out_bitangents[index] += in_bitangents[i];
        }else{ // If not, it needs to be added in the output data.
            // Do as usual
            [...]
        }

Remarque que l’on ne normalise rien ici. En réalité c’est pratique, car de cette façon, les petits triangles, qui ont une tangente et bitangente plus petites, auront un effet diminué sur le vecteur final par rapport aux grands triangles (qui contribueront plus à la forme finale).

Le shader

Tampons et variables uniformes supplémentaires

On a besoin de deux nouveaux buffer, un pour les tangentes, l’autre pour les bitangentes :

    GLuint tangentbuffer;
    glGenBuffers(1, &tangentbuffer);
    glBindBuffer(GL_ARRAY_BUFFER, tangentbuffer);
    glBufferData(GL_ARRAY_BUFFER, indexed_tangents.size() * sizeof(glm::vec3), &indexed_tangents[0], GL_STATIC_DRAW);

    GLuint bitangentbuffer;
    glGenBuffers(1, &bitangentbuffer);
    glBindBuffer(GL_ARRAY_BUFFER, bitangentbuffer);
    glBufferData(GL_ARRAY_BUFFER, indexed_bitangents.size() * sizeof(glm::vec3), &indexed_bitangents[0], GL_STATIC_DRAW);

On a aussi besoin d’une nouvelle variable uniforme pour notre nouvelle texture de normales :

    [...]
    GLuint NormalTexture = loadTGA_glfw("normal.tga");
    [...]
    GLuint NormalTextureID  = glGetUniformLocation(programID, "NormalTextureSampler");

Et d’une autre pour la matrice 3x3 de modèle-vue. Cela n’est pas strictement obligatoire, mais c’est plus simple ; plus d’informations là-dessus plus tard. On a besoin que de la partie 3 x 3 supérieure gauche car on va multiplier des directions : on peut donc se débarrasser de la partie liée à la translation.

    GLuint ModelView3x3MatrixID = glGetUniformLocation(programID, "MV3x3");

Au final le code de rendu complet donne :

        // Clear the screen
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // Use our shader
        glUseProgram(programID);

        // Compute the MVP matrix from keyboard and mouse input
        computeMatricesFromInputs();
        glm::mat4 ProjectionMatrix = getProjectionMatrix();
        glm::mat4 ViewMatrix = getViewMatrix();
        glm::mat4 ModelMatrix = glm::mat4(1.0);
        glm::mat4 ModelViewMatrix = ViewMatrix * ModelMatrix;
        glm::mat3 ModelView3x3Matrix = glm::mat3(ModelViewMatrix); // Take the upper-left part of ModelViewMatrix
        glm::mat4 MVP = ProjectionMatrix * ViewMatrix * ModelMatrix;

        // Send our transformation to the currently bound shader,
        // in the "MVP" uniform
        glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);
        glUniformMatrix4fv(ModelMatrixID, 1, GL_FALSE, &ModelMatrix[0][0]);
        glUniformMatrix4fv(ViewMatrixID, 1, GL_FALSE, &ViewMatrix[0][0]);
        glUniformMatrix4fv(ViewMatrixID, 1, GL_FALSE, &ViewMatrix[0][0]);
        glUniformMatrix3fv(ModelView3x3MatrixID, 1, GL_FALSE, &ModelView3x3Matrix[0][0]);

        glm::vec3 lightPos = glm::vec3(0,0,4);
        glUniform3f(LightID, lightPos.x, lightPos.y, lightPos.z);

        // Bind our diffuse texture in Texture Unit 0
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, DiffuseTexture);
        // Set our "DiffuseTextureSampler" sampler to user Texture Unit 0
        glUniform1i(DiffuseTextureID, 0);

        // Bind our normal texture in Texture Unit 1
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, NormalTexture);
        // Set our "Normal    TextureSampler" sampler to user Texture Unit 0
        glUniform1i(NormalTextureID, 1);

        // 1rst attribute buffer : vertices
        glEnableVertexAttribArray(0);
        glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
        glVertexAttribPointer(
            0,                  // attribute
            3,                  // size
            GL_FLOAT,           // type
            GL_FALSE,           // normalized?
            0,                  // stride
            (void*)0            // array buffer offset
        );

        // 2nd attribute buffer : UVs
        glEnableVertexAttribArray(1);
        glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);
        glVertexAttribPointer(
            1,                                // attribute
            2,                                // size
            GL_FLOAT,                         // type
            GL_FALSE,                         // normalized?
            0,                                // stride
            (void*)0                          // array buffer offset
        );

        // 3rd attribute buffer : normals
        glEnableVertexAttribArray(2);
        glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
        glVertexAttribPointer(
            2,                                // attribute
            3,                                // size
            GL_FLOAT,                         // type
            GL_FALSE,                         // normalized?
            0,                                // stride
            (void*)0                          // array buffer offset
        );

        // 4th attribute buffer : tangents
        glEnableVertexAttribArray(3);
        glBindBuffer(GL_ARRAY_BUFFER, tangentbuffer);
        glVertexAttribPointer(
            3,                                // attribute
            3,                                // size
            GL_FLOAT,                         // type
            GL_FALSE,                         // normalized?
            0,                                // stride
            (void*)0                          // array buffer offset
        );

        // 5th attribute buffer : bitangents
        glEnableVertexAttribArray(4);
        glBindBuffer(GL_ARRAY_BUFFER, bitangentbuffer);
        glVertexAttribPointer(
            4,                                // attribute
            3,                                // size
            GL_FLOAT,                         // type
            GL_FALSE,                         // normalized?
            0,                                // stride
            (void*)0                          // array buffer offset
        );

        // Index buffer
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);

        // Draw the triangles !
        glDrawElements(
            GL_TRIANGLES,      // mode
            indices.size(),    // count
            GL_UNSIGNED_INT,   // type
            (void*)0           // element array buffer offset
        );

        glDisableVertexAttribArray(0);
        glDisableVertexAttribArray(1);
        glDisableVertexAttribArray(2);
        glDisableVertexAttribArray(3);
        glDisableVertexAttribArray(4);

        // Swap buffers
        glfwSwapBuffers();

Vertex shader

Comme dit précédemment, on va tout faire dans l’espace de la caméra, car il est plus simple d’obtenir la position du fragment dans cet espace. C’est pourquoi on multiplie nos vecteurs T, B et N avec la matrice de modèle-vue.

    vertexNormal_cameraspace = MV3x3 * normalize(vertexNormal_modelspace);
    vertexTangent_cameraspace = MV3x3 * normalize(vertexTangent_modelspace);
    vertexBitangent_cameraspace = MV3x3 * normalize(vertexBitangent_modelspace);

Ces trois vecteurs définissent la matrice TBN, qui est créée de cette façon :

    mat3 TBN = transpose(mat3(
        vertexTangent_cameraspace,
        vertexBitangent_cameraspace,
        vertexNormal_cameraspace
    )); // You can use dot products instead of building this matrix and transposing it. See References for details.

Cette matrice passe de l’espace de la caméra à l’espace tangent (la même matrice, mais avec XXX_modelspace à la place, permettrai de passer de l’espace modèle à l’espace tangent). On peut l’utiliser pour calculer la direction de la lumière et la direction de l’œil, dans l’espace tangent :

    LightDirection_tangentspace = TBN * LightDirection_cameraspace;
    EyeDirection_tangentspace =  TBN * EyeDirection_cameraspace;

Fragment shader

La normale, dans l’espace tangent, est immédiate à obtenir, c’est la texture :

    // Local normal, in tangent space
    vec3 TextureNormal_tangentspace = normalize(texture( NormalTextureSampler, UV ).rgb*2.0 - 1.0);

Donc, on a tout ce dont nous avons besoin. La lumière diffuse utilise clamp(dot(n,l), 0, 1), avec n et l exprimé dans l’espace tangent (l’espace dans lequel on effectue nos produits scalaire et vectoriel n’importe pas ; la chose importante est que l et n soit tous les deux exprimés dans le même espace). La lumière spéculaire utilise clamp(dot(E,R), 0, 1), où, encore une fois, E et R sont exprimés dans l’espace tangent. Super !

Résultats

Voici le résultat obtenu. Tu peux remarquer que :

  • les briques semblent bosselées car on a de nombreuses variations dans les normales
  • le ciment semble plat car la texture de normales est complètement bleue.

Aller plus loin

Orthogonalisation

Dans le vertex shader on prend la transposée au lieu de l’inverse, car c’est plus rapide. Mais cela ne fonctionne que si l’espace représenté par la matrice est orthogonal, ce qui n’est pas encore le cas à ce moment là. Heureusement, c’est facilement corrigeable : on doit simplement faire que la tangente soit perpendiculaire à la normale à la fin de computeTangentBasis() :

t = glm::normalize(t - n * glm::dot(n, t));

La formule peut être difficile à saisir, donc voici un petit schéma pour aider :

n et t sont presque perpendiculaire, donc on « pousse » t dans la direction de -n multiplié par dot(n,t).

Avant il y avait une petite application qui expliquait aussi cela (utilise seulement deux vecteurs si jamais elle remarche ^^).

Règle de la main droite

Tu n’as normalement pas à t’en inquiéter, mais dans quelques cas, lorsque tu utilises des modèles symétriques, les coordonnées UV sont orientées dans le mauvais sens et ton T possède la mauvaise orientation.

Pour vérifier si elle doit être inversée ou pas, c’est simple : TBN doit former un système de coordonnées répondant à la règle de la main droite. Par exemple, cross(n,t) doit avoir la même orientation que b.

En mathématiques, « Un vecteur A à la même orientation qu’un vecteur B » se traduit par dot(A,B) > 0, donc on doit vérifier si dot(cross(n,t),b) > 0.

Si c’est incorrect, inversez t :

if (glm::dot(glm::cross(n, t), b) < 0.0f){
     t = t * -1.0f;
 }

Cela est aussi effectué pour chaque sommet à la fin de la fonction computeTangentBasis().

Texture spéculaire

Pour le fun, j’ai ajouté une texture spéculaire au code. Ça ressemble à ça :

et je l’ai utilisée pour remplacer le simple gris « vec3(0.3,0.3,0.3) » que l’on utilise pour la couleur spéculaire :

Le ciment est toujours noir : la texture indique qu’il n’y a pas de composante spéculaire.

Débogage avec le mode immédiat

Le vrai but de ce site Web est que tu N’utilises PAS le mode immédiat, qui est obsolète, lent et problématique en de nombreux aspects.

Par contre, il se trouve qu’il soit très pratique pour le débogage :

Ici, on observe notre espace tangent avec les lignes dessinées dans le mode immédiat.

Pour cela, tu dois abandonner le profil core 3.3 :

glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);

Puis passer les matrices au vieux pipeline OpenGL (tu peux aussi écrire un autre shader, mais c’est plus simple de cette façon et tu es en train de bidouiller de toute façon) :

glMatrixMode(GL_PROJECTION);
glLoadMatrixf((const GLfloat*)&ProjectionMatrix[0]);
glMatrixMode(GL_MODELVIEW);
glm::mat4 MV = ViewMatrix * ModelMatrix;
glLoadMatrixf((const GLfloat*)&MV[0]);

Désactiver les shaders :

glUseProgram(0);

Et dessiner tes lignes (dans ce cas, les normales, normalisées et multipliées par 0.1 et appliquées au bon sommet) :

glColor3f(0,0,1);
glBegin(GL_LINES);
for (int i=0; i < indices.size(); i++){
    glm::vec3 p = indexed_vertices[indices[i]];
    glVertex3fv(&p.x);
    glm::vec3 o = glm::normalize(indexed_normals[indices[i]]);
    p+=o*0.1f;
    glVertex3fv(&p.x);
}
glEnd();

Rappel-toi : n’utilise pas le mode immédiat dans une vraie application ! Uniquement pour du débogage ! Et n’oublie pas de réactiver le profil core après coup, cela t’empêcheras d’utiliser le mode immédiat par inadvertence.

Débogage avec les couleurs

Lors du débogage, il peut être utile de visualiser la valeur d’un vecteur. La façon la plus simple pour ce faire est d’écrire sa valeur dans le tampon d’image au lieu de la couleur actuelle. Par exemple, pour visualiser LightDiretion_tangentspace :

color.xyz = LightDirection_tangentspace;

Cela signifie que :

  • Sur la partie droite du cylindre, la lumière (représentée par la fine ligne blanche) est vers le HAUT (dans l’espace tangent). En d’autres mots, la lumière est dans la direction de la normale des triangles ;
  • Sur le milieu du cylindre, la lumière est dans la direction de la tangente (avant+X).

Quelques conseils :

  • Suivant ce que tu essayes de voir, tu peux souhaiter le normaliser
  • Si tu ne comprends pas ce que tu vois, visualises toutes les composantes séparément en forçant par exemple le vert et le bleu à 0
  • Evite de jouer avec l’alpha, c’est trop compliqué :)
  • Si tu veux visualiser une valeur négative, tu peux utiliser la même astuce que celle pour notre texture de normales : visualise (v+1.0)/2.0 à la place. Le noir signifie -1 et la couleur +1. Toutefois, c’est pas toujurs facile d’interpréter le rendu.

Débogage avec les noms des variables

Comme dit précédemment, il est important de connaître exactement dans quel espace vos vecteurs se trouvent. Ne faites pas le produit scalaire d’un vecteur situé dans l’espace de la caméra avec un vecteur dans l’espace modèle.

Ajouter l’espace de chaque vecteur à son nom (« …_modelspace ») aide énormement à la correction de bogues mathématiques.

Comment créer une texture de normales

Créé par James O’Hare. Clique pour agrandir :

Exercices

  • Normalise les vecteurs dans indexVBO_TBN avant l’addition et vois ce que ça fait.
  • Visualise les autres vecteurs (par exemple, EyeDirection_tangentspace) dans le mode de débogage avec les couleurs et essayez de comprendre ce que tu vois

Outils et liens

  • Crazybump, un outil super pour faire des textures de normales. Payant.
  • Plugin Nvidia pour Photoshop. Gratuit, mais Photoshop ne l’est pas …
  • Fais tes propres textures de normales à partir de plusieurs photos.
  • Faites vos propres textures de normales à partir d’une photo.
  • Plus d’informations sur la transposition de matrices.

References

  • Lengyel, Eric. “Computing Tangent Space Basis Vectors for an Arbitrary Mesh”. Terathon Software 3D Graphics Library, 2001.
  • Real Time Rendering, troisième édition.
  • ShaderX4
  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Tutoriel 14 : rendu dans une texture

Le rendu dans une texture (render to texture) est une méthode pratique pour créer une multitude d’effets. L’idée de base est de dessiner une scène, tout comme vous le faites habituellement, mais cette fois, dans une texture que tu peux réutiliser plus tard.

Les applications de cette technique incluent les caméras dans les jeux, le post-processing et autant d’effets graphiques que tu le souhaites.

Rendu dans une texture

On a trois tâches : créer une texture dans laquelle on va faire le rendu, faire le rendu dans celle-ci et utiliser la texture générée.

Créer la render target

La chose dans laquelle on va dessiner s’appelle un Framebuffer (tampon d’image). C’est un conteneur pour les textures et optionnellement un Z buffer (tampon de profondeur). Il est créé exactement comme n’importe quel un autre objet OpenGL :

// The framebuffer, which regroups 0, 1, or more textures, and 0 or 1 depth buffer.
GLuint FramebufferName = 0;
glGenFramebuffers(1, &FramebufferName);
glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);

Maintenant on doit créer la texture qui va contenir la sortie RGB du shader. Ce code est très classique :

// The texture we're going to render to
GLuint renderedTexture;
glGenTextures(1, &renderedTexture);

// "Bind" the newly created texture : all future texture functions will modify this texture
glBindTexture(GL_TEXTURE_2D, renderedTexture);

// Give an empty image to OpenGL ( the last "0" )
glTexImage2D(GL_TEXTURE_2D, 0,GL_RGB, 1024, 768, 0,GL_RGB, GL_UNSIGNED_BYTE, 0);

// Poor filtering. Needed !
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

On a aussi besoin d’un tampon de profondeur. C’est optionnel et dépend de ce que tu veux afficher dans ta texture ; mais comme on va afficher Suzanne, on a besoin du test de profondeur.

// The depth buffer
GLuint depthrenderbuffer;
glGenRenderbuffers(1, &depthrenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, depthrenderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, 1024, 768);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthrenderbuffer);

Finalement, on configure le framebuffer :

// Set "renderedTexture" as our colour attachement #0
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, renderedTexture, 0);

// Set the list of draw buffers.
GLenum DrawBuffers[1] = {GL_COLOR_ATTACHMENT0};
glDrawBuffers(1, DrawBuffers); // "1" is the size of DrawBuffers

Quelque chose a pu mal se passer durant ce processus, suivant les capacités du GPU. On vérifie les erreurs comme ça :

// Always check that our framebuffer is ok
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
return false;

Dessiner dans la texture

Le rendu dans la texture est direct. On lie simplement le framebuffer et on dessine notre scène comme d’hab. Facile !

// Render to our framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);
glViewport(0,0,1024,768); // Render on the whole framebuffer, complete from the lower left corner to the upper right

Le fragment shader nécessite une adaptation mineure :

layout(location = 0) out vec3 color;

Cela signifie que lors de l’écriture dans la variable « color », on va écrire dans la cible de rendu 0, qui correspond à la texture, car DrawBuffers[0] est GL_COLOR_ATTACHMENTi, qui est dans notre cas, renderedTexture.

Pour récapituler :

  • color sera écrit dans le premier tampon à cause du layout(location=0)
  • Le premier buffer est GL_COLOR_ATTACHMENT0 car DrawBuffers[1] = {GL_COLOR_ATTACHMENT0}
  • renderedTexture est attaché à GL_COLOR_ATTACHMENT0, donc c’est la que la couleur sera écrite
  • En d’autres mots, tu peux remplacer GL_COLOR_ATTACHMENT0 par GL_COLOR_ATTACHMENT2 et ça fonctionnera toujours.

Il n’y a pas de layout(location=i) dans OpenGL < 3.3, mais tu peux utiliser glFragData[i] = myvalue à la place.

Utiliser la texture générée

On va dessiner un simple rectangle pour remplir l’écran. On doit utiliser les tampons habituels, shaders, identifiants …

// The fullscreen quad's FBO
GLuint quad_VertexArrayID;
glGenVertexArrays(1, &quad_VertexArrayID);
glBindVertexArray(quad_VertexArrayID);

static const GLfloat g_quad_vertex_buffer_data[] = {
    -1.0f, -1.0f, 0.0f,
    1.0f, -1.0f, 0.0f,
    -1.0f,  1.0f, 0.0f,
    -1.0f,  1.0f, 0.0f,
    1.0f, -1.0f, 0.0f,
    1.0f,  1.0f, 0.0f,
};

GLuint quad_vertexbuffer;
glGenBuffers(1, &quad_vertexbuffer);
glBindBuffer(GL_ARRAY_BUFFER, quad_vertexbuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(g_quad_vertex_buffer_data), g_quad_vertex_buffer_data, GL_STATIC_DRAW);

// Create and compile our GLSL program from the shaders
GLuint quad_programID = LoadShaders( "Passthrough.vertexshader", "SimpleTexture.fragmentshader" );
GLuint texID = glGetUniformLocation(quad_programID, "renderedTexture");
GLuint timeID = glGetUniformLocation(quad_programID, "time");

Maintenant tu veux dessiner sur l’écran. Cela se fait en utilisant 0 comme second paramètre de glBindFramebuffer.

// Render to the screen
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0,0,1024,768); // Render on the whole framebuffer, complete from the lower left corner to the upper right

On peut dessiner le rectangle plein écran avec un tel shader :

#version 330 core

in vec2 UV;

out vec3 color;

uniform sampler2D renderedTexture;
uniform float time;

void main(){
    color = texture( renderedTexture, UV + 0.005*vec2( sin(time+1024.0*UV.x),cos(time+768.0*UV.y)) ).xyz;
}

Ce code échantillonne simplement la texture, mais ajoute un léger décalage dépendant du temps.

Results

Aller plus loin

Utiliser la profondeur

Dans quelques cas tu peux avoir besoin de la profondeur lors de l’utilisation de la texture générée. Dans ce cas, dessine simplement la texture comme suit :

glTexImage2D(GL_TEXTURE_2D, 0,GL_DEPTH_COMPONENT24, 1024, 768, 0,GL_DEPTH_COMPONENT, GL_FLOAT, 0);

« 24 » est la précision, en bits. Tu peux choisir entre 16, 24 et 32, suivant tes besoins. Habituellement 24 suffit.

Cela devrait être assez pour pouvoir démarrer.

Remarque que cela devrait être quelque peu plus lent, car le pilote ne sera pas capable d’utiliser quelques optimisations comme Hi-Z.

Dans cette capture d’écran, les niveaux de profondeur sont rendu artificiellement « plus beaux ». Habituellement, c’est beaucoup plus difficile de voir quelque chose sur une texture de profondeur. Proche = Z tendant vers 0 = noir, Lointain = Z tendant vers 1 = blanc.

Multi-échantillonnage

Tu peux écrire dans des textures utilisant le multi-échantillonnage au lieu d’utiliser les textures de « base » : tu dois simplement remplacer glTexImage2D par glTexImage2DMultisample dans le code C++ et sampler2D/texture par sampler2DMS/texelFetch dans le fragment shader.

Par contre, il y a un énorme inconvénient : texelFetch a besoin d’un argument supplémentaire, correspondant au numéro de l’échantillon à récupérer. En d’autres mots, il n’y a pas de méthode automatique de « filtrage » (le terme correct, lorsque l’on parle de multi-échantillonnage, est « résolution »).

Donc, vous devras peut être résoudre la texture utilisant le multi-échantillonnage toi-même, dans une autre texture sans multi-échantillonnage, grâce à un autre shader.

Rien de difficile, mais c’est simplement pénible.

Cible de rendu multiples

Tu peux écrire dans plusieurs textures en même temps.

Crée simplement plusieurs textures (toutes avec la même et correcte taille !), appele glFramebufferTexture avec une couleur d’attache différente pour chaque, appele glDrawBuffers avec des paramètres mis à jour (quelque chose comme (2, {GL_COLOR_ATTACHMENT0,GL_COLOR_ATTACHMENT1})) et ajoutez une autre variable de sortie dans votre fragment shader :

layout(location = 1) out vec3 normal_tangentspace; // or whatever

Si tu as besoin d’écrire un vecteur dans une texture, les textures à virgule flottante existe, avec une précision de 16 ou 32 bits au lieu de 8 … voir la documentation de glTexImage2D (ctrl+f GL_FLOAT).

Dans les versions précédentes d’OpenGL, utilise glFragData[1] = myvalue à la place.

Exercices

  • Essaye d’utiliser glViewport(0,0,512,768); à la place de glViewport(0,0,1024,768); (essaye avec le framebuffer et l’écran)
  • Expérimente avec d’autres coordonnées UV dans le dernier fragment shader.
  • Transforme le rectangle avec une vraie matrice de transformation. Code-la en dur puis essaye d’utiliser les fonctions de controls.hpp ; que remarque-vous ?
  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Tutoriel 15 : Lightmaps

Introduction

J’aurais bien traduit lightmap mais en programmation graphique tout le monde utilise ce terme. On pourrait peut être dire Cartes de lumière mais ca serait une traduction artificielle.

Ce tutoriel est un tutoriel vidéo seulement. Il n’introduit aucune nouvelle technique/syntaxe spécifique à OpenGL, mais te présente comment utiliser les techniques que tu connais déjà pour créer des ombres de grande qualité.

Tu vas voir comment créer un monde simple dans Blender et préparer des textures de lumière afin que tu puisses les utiliser dans ton application.

Aucune connaissance de Blender n’est nécessaire. Je vais expliquer tous les raccourcis clavier et tout ce que je fais.

Une note sur les textures de lumière

Les lightmaps sont précalculées. Elles sont gravées dans le marbre. Cela signifie qu’elles sont complètement statiques, tu ne peux pas décider de déplacer une lumière durant l’exécution. Même pas de la supprimer.

Mais cela peut toujours être utile pour la lumière du soleil, ou encore, dans les scènes d’intérieur où on ne peut pas casser d’ampoules. Mirror Edge, publiée en 2009, les utilisent intensivement, que ce soit en extérieur ou en intérieur.

De plus, c’est très simple à mettre en place et tu ne peux rien faire de moins couteux.

La vidéo

C’est une vidéo en 1024 x 768p, utilise le mode HD …

Appendice

Lors du rendu en OpenGL, tu peux remarquer quelques corruptions (ici exagérées) :

Cela est dû aux MIP maps, qui mélangent les texels ensemble lorsqu’ils sont visualisés de loin. Les pixels noirs provenant du fond de la texture sont mélangés avec les bonnes parties de la texture de lumière. Pour éviter cela, il y a quelques petits ajustements que tu peux faire :

  • Tu peux demander à Blender de générer une marge autour des limites des coordonnées UV. Cela est accessible à travers le paramètre « margin » dans le panneau « bake ». Pour de bons résultats, tu peux aller jusqu’à une marge de 20 pixels ;

  • Tu peux utiliser un biais dans accèder à la texture :

    glsl fs color = texture( myTextureSampler, UV, -2.0 ).rgb;

    -2 est LE biais. Tu peux expérimenter avec cette valeur. La capture d’écran ci-dessus a été effectuée avec un biais de +2, ce qui signifie que OpenGL va sélectionner deux MIP maps au-dessus de celle qu’il aurait choisi (donc 16 fois plus petites, provoquant les corruptions)

  • Tu peux remplir le fond noir dans une étape de post-process. Je vais poster plus tard plus d’information là-dessus.
  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Tutoriel 16 : Shadow mapping

Introduction

Dans le quinzième tutoriel, on a appris à créer des lightmaps, qui incluent l’éclairage statique. Bien que cela produise de ombres vraiment belles, ça ne gère pas les modèles animés.

Les shadow maps (textures d’ombre) sont la technique actuelle (en 2012) pour créer des ombres dynamiques. Leur bon point c’est qu’elles sont relativement faciles à mettre en place. En contrepartie, c’est extrêmement difficile de les faire fonctionner correctement.

Dans ce tutoriel, on introduira l’algorithme de base, on verra ses défauts et on implémentera quelques techniques pour obtenir de meilleurs résultats. Sachant qu’au moment de l’écriture (2012), les textures d’ombre sont un sujet de recherche d’actualité, on donnera quelques directions pour te permettre d’améliorer tes propres textures d’ombre, suivant tes besoins.

Shadowmap basique

L’algorithme des shadowmap de base se décompose en deux passes. Premièrement, la scène est dessinée à partir de la position de la lumière. Seule la profondeur de chaque fragment est calculée. Ensuite, la scène est affichée comme d’habitude, mais avec un test supplémentaire pour vérifier si le fragment actuel est dans l’ombre.

Le test « être dans l’ombre » est vraiment très simple. Si l’échantillon actuel est plus éloigné de la lumière que la texture d’ombre au même point, cela signifie que la scène contient un objet plus proche de la lumière. En d’autres termes, le fragment actuel est dans l’ombrage.

L’image suivante aide à comprendre le principe :

Génération de la shadow map

Dans ce tutoriel, on ne considérera que les lumières directionnelles - lumières qui sont si loin que l’on peut considérer les rayons comme étant parallèles. Ainsi, la génération de la texture d’ombre est effectuée avec une matrice de projection orthographique. Une matrice de projection orthographique est exactement comme une matrice de projection en perspective, sauf qu’aucune perspective n’est prise en compte - un objet sera identique indépendamment de sa distance avec la caméra.

Configurer le renderbuffer et la matrice MVP

Depuis le quatorzième tutoriel, tu sais comment dessiner une scène dans une texture afin d’y accéder plus tard à partir d’un shader.

Ici, on utilise une texture de profondeur 1024x1024 sur 16 bits pour stocker la texture d’ombre. 16 bits sont généralement suffisants pour une texture d’ombre. Libre à toi d’expérimenter avec d’autres valeurs.

On utilise une texture de profondeur et non pas un renderbuffer pour la profondeur, car on a besoin de l’échantillonner par la suite.

 // The framebuffer, which regroups 0, 1, or more textures, and 0 or 1 depth buffer.
 GLuint FramebufferName = 0;
 glGenFramebuffers(1, &FramebufferName);
 glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);

 // Depth texture. Slower than a depth buffer, but you can sample it later in your shader
 GLuint depthTexture;
 glGenTextures(1, &depthTexture);
 glBindTexture(GL_TEXTURE_2D, depthTexture);
 glTexImage2D(GL_TEXTURE_2D, 0,GL_DEPTH_COMPONENT16, 1024, 1024, 0,GL_DEPTH_COMPONENT, GL_FLOAT, 0);
 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

 glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTexture, 0);

 glDrawBuffer(GL_NONE); // No color buffer is drawn to.

 // Always check that our framebuffer is ok
 if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
 return false;

La matrice MVP utilisée pour dessiner la scène du point de vue de la lumière est calculée comme suit :

  • La matrice de projection est une matrice orthographique incluant tout ce qui est compris dans une boîte alignée aux axes de (-10, 10), (-10, 10), (-10, 20) sur les axes X, Y et Z, respectivement. Ces valeurs ont été choisies pour que toute la partie visible de la scène soit toujours visible ; plus d’informations sur ce point dans la section : voir plus loin
  • Notre matrice de vue tournera le monde afin que, dans l’espace caméra, la direction de la lumière soit -Z (Envie de refaire un tour dans le troisième tutoriel ?)
  • La matrice de modèle est peut importe ce que tu veux.
glm::vec3 lightInvDir = glm::vec3(0.5f,2,2);

 // Compute the MVP matrix from the light's point of view
 glm::mat4 depthProjectionMatrix = glm::ortho<float>(-10,10,-10,10,-10,20);
 glm::mat4 depthViewMatrix = glm::lookAt(lightInvDir, glm::vec3(0,0,0), glm::vec3(0,1,0));
 glm::mat4 depthModelMatrix = glm::mat4(1.0);
 glm::mat4 depthMVP = depthProjectionMatrix * depthViewMatrix * depthModelMatrix;

 // Send our transformation to the currently bound shader,
 // in the "MVP" uniform
 glUniformMatrix4fv(depthMatrixID, 1, GL_FALSE, &depthMVP[0][0])

Les shaders

Les shaders utilisés durant cette passe sont très simples. Le vertex shader est un shader passant les données au fragment shader qui calcule simplement la position des sommets en coordonnées homogènes :

#version 330 core

// Input vertex data, different for all executions of this shader.
layout(location = 0) in vec3 vertexPosition_modelspace;

// Values that stay constant for the whole mesh.
uniform mat4 depthMVP;

void main(){
 gl_Position =  depthMVP * vec4(vertexPosition_modelspace,1);
}

Le fragment shader est tout aussi simple : il écrit la profondeur du fragment à l’emplacement 0 (c’est-à-dire dans la texture de profondeur).

#version 330 core

// Ouput data
layout(location = 0) out float fragmentdepth;

void main(){
    // Not really needed, OpenGL does it anyway
    fragmentdepth = gl_FragCoord.z;
}

Le rendu d’une shadow map est généralement deux fois plus rapide qu’un rendu normal, car seule une profondeur en faible précision est écrite, au lieu de la profondeur et de la couleur. La bande passante mémoire est souvent le plus grand problème de performance sur les GPU.

Résultat

La texture ressemble à ça :

Une couleur sombre signifie un petit z ; donc, le coin supérieur droit du mur est proche de la caméra. Au contraire, le blanc signifie z=1 (en coordonnées homogènes), donc que l’élément est très loin.

Utiliser la shadow map

Shader de base

Maintenant, on retourne à notre shader habituel. Pour chaque fragment que l’on calcule, on doit tester s’il se trouve « derrière » la texture d’ombre ou pas.

Pour ce faire, on doit calculer la position actuelle du fragment dans le même espace que celui que nous avons utilisé lors de la création de la texture d’ombre. Donc, on doit le transformer avec la matrice MVP habituelle et une seconde fois avec la matrice MVP de la passe de profondeur (depthMVP).

Il y a tout de même une petite astuce. La multiplication de la position du sommet avec la matrice depthMVP donnera des coordonnées homogènes, qui sont comprises entre [-1, 1]. Mais l’échantillonnage d’une texture doit être fait entre [0, 1].

Par exemple, un fragment au milieu de l’écran sera en (0, 0) dans l’espace de coordonnées homogènes. Mais comme il devra correspondre au milieu de la texture, les coordonnées UV devront être (0.5, 0.5).

Cela peut être corrigé en ajustant les coordonnées directement dans le fragment shader, mais c’est plus efficace de multiplier les coordonnées homogènes avec la matrice suivante, qui divise simplement les coordonnées par 2 (la diagonale : [-1, 1]-> [-0.5, 0.5]) et les déplace (la ligne du bas : [-0.5, 0.5]-> [0, 1]).

glm::mat4 biasMatrix(
0.5, 0.0, 0.0, 0.0,
0.0, 0.5, 0.0, 0.0,
0.0, 0.0, 0.5, 0.0,
0.5, 0.5, 0.5, 1.0
);
glm::mat4 depthBiasMVP = biasMatrix*depthMVP;

On peut maintenant écrire le vertex shader. Il est identique au précédent, mais on écrit deux positions au lieu d’une :

  • gl_Position est la position du sommet tel qu’il est vu par la caméra actuelle.
  • ShadowCoord est la position du sommet tel qu’il est vu à partir de l’ancienne caméra (la lumière).
// Output position of the vertex, in clip space : MVP * position
gl_Position =  MVP * vec4(vertexPosition_modelspace,1);

// Same, but with the light's view matrix
ShadowCoord = DepthBiasMVP * vec4(vertexPosition_modelspace,1);

Ensuite, le fragment shader est très simple :

  • texture(shadowMap, ShadowCoord.xy).z est la distance entre la lumière et l’objet le plus proche
  • ShadowCoord.z est la distance entre la lumière et le fragment actuel

Donc si le fragment actuel est plus loin que l’objet le plus proche, cela signifie que l’on se trouve dans l’ombre (de objet plus proche) :

float visibility = 1.0;
if ( texture( shadowMap, ShadowCoord.xy ).z  <  ShadowCoord.z){
    visibility = 0.5;
}

On a juste besoin d’utiliser ça pour modifier l’ombrage. Bien sûr, la couleur ambiante n’est pas modifiée, car son but est d’imiter une lumière arrivant même lorsqu’on se trouve dans l’ombrage (ou sinon tout ce qui est dans l’ombre serait complètement noir).

color =
 // Ambient : simulates indirect lighting
 MaterialAmbientColor +
 // Diffuse : "color" of the object
 visibility * MaterialDiffuseColor * LightColor * LightPower * cosTheta+
 // Specular : reflective highlight, like a mirror
 visibility * MaterialSpecularColor * LightColor * LightPower * pow(cosAlpha,5);

Résultat - acné d’ombre

Voici le résultat du code actuel. Évidemment, l’idée générale est présente, mais la qualité est inacceptable.

On va regarder chacun des problèmes de l’image dans les sections suivantes. Le code possède deux projets : shadowmaps et shadowmaps_simple : commence par celui que tu préféres. La version simple est tout aussi laide que l’image ci-dessus, mais plus facile à comprendre.

Problèmes

Acné d’ombre

Le problème le plus évident s’appelle shadow acne (acné de d’ombre) :

Ce phénomène est facilement explicable avec une image :

Le « correctif » habituel pour ça consiste à utiliser une marge d’erreur : on n’ajoute l’ombre que si la profondeur du fragment actuel (encore une fois, dans l’espace de la lumière) est vraiment loin de la valeur de la texture de lumière. On fait ça en ajoutant un biais :

float bias = 0.005;
float visibility = 1.0;
if ( texture( shadowMap, ShadowCoord.xy ).z  <  ShadowCoord.z-bias){
    visibility = 0.5;
}

Le résultat est déjà beaucoup plus beau :

Par contre, tu peux remarquer qu’à cause de notre biais, l’artefact entre le sol est le mur s’est empiré. Qui plus est, un biais de 0.005 semble trop important pour le sol, mais pas assez pour les surfaces arrondies : quelques artefacts sont toujours visibles sur le cylindre et la sphère.

Une approche commune consiste à modifier le biais suivant la pente :

float bias = 0.005*tan(acos(cosTheta)); // cosTheta is dot( n,l ), clamped between 0 and 1
bias = clamp(bias, 0,0.01);

L’acné d’ombrage n’est plus là, même sur les surfaces arrondies :

Une autre astuce qui peut ou non fonctionner suivant vos modèles est d’afficher seulement les faces arrière dans la texture d’ombre. Cela force à utiliser une scène spécifique (voir la prochaine section - Peter Panning) avec les murs fins. Mais au moins, l’acné sera sur les surfaces qui sont dans l’ombre (on la verra donc pas) :

Lors du rendu de la texture d’ombre, supprime les faces avant des triangles :

        // We don't use bias in the shader, but instead we draw back faces,
        // which are already separated from the front faces by a small distance
        // (if your geometry is made this way)
        glCullFace(GL_FRONT); // Cull front-facing triangles -> draw only back-facing triangles

Et ensuite, affiche la scène avec un rendu normal (suppression des faces arrière) :

         glCullFace(GL_BACK); // Cull back-facing triangles -> draw only front-facing triangles

Cette méthode est utilisée dans le code, en plus du biais.

Peter Panning

On n’a plus d’acné d’ombrages, mais on a toujours un mauvais ombrage sur le sol, faisant comme si les murs volaient (d’où le terme « Peter Panning »). En fait, en ajoutant le biais, c’est devenu pire.

Celui-ci est très facile à corriger : évite tout simplement les géométries sans épaisseurs. Ça a deux avantages :

  • Premièrement, ça corrige le « Peter Panning » : si ta géométrie est plus épaisse que le biais, tout est bon
  • Deuxièmement, tu peux réactiver la suppression des faces arrière lors du rendu de la texture de lumière, car maintenant, il y a un polygone du mur qui fait face à la lumière et donc cache l’autre côté, qui n’aurait pas été affiché avec la suppression des faces arrière.

L’inconvénient est que tu as plus de triangles à afficher (deux fois par image !).

Aliasing

Mais avec ces deux astuces, tu vas remarquer qu’il y a toujours du crénelage sur le bord de l’ombre. En d’autres termes, un pixel est blanc et le prochain noir, sans même de transition douce entre les deux.

PCF

La façon la plus simple d’améliorer ça est de changer le type d’échantillonnage de la texture d’ombre en shadow2DShadow. La conséquence est que, lorsque tu échantillonnes une fois, le matériel fera en réalité aussi un échantillonnage des pixels voisins, une comparaison entre eux et retournera un nombre à virgule flottante compris dans [0, 1] avec un filtrage bilinéaire du résultat de la comparaison.

Par exemple, 0.5 signifie que deux échantillons sont dans l’ombre et deux échantillons dans la lumière.

Ce n’est pas la même chose qu’un échantillonnage simple d’une texture de profondeur filtrée ! Une comparaison retourne toujours vrai ou faux ; PCF donne une interpolation de quatre « vrai ou faux ».

Comme vous pouvez le voir, les bordures de l’ombre sont douces, mais la texture d’ombre est toujours visible.

###Échantillonnage de Poisson

Une méthode simple pour le gérer est d’échantillonner la texture d’ombre N fois au lieu d’une seule. En combinaison avec le PCF, cela peut donner de très bons résultats, même avec un petit N. Voici le code pour quatre échantillonnages :

for (int i=0;i<4;i++){
  if ( texture( shadowMap, ShadowCoord.xy + poissonDisk[i]/700.0 ).z  <  ShadowCoord.z-bias ){
    visibility-=0.2;
  }
}

poissonDisk est un tableau constant qui peut être défini comme suit :

vec2 poissonDisk[4] = vec2[](
  vec2( -0.94201624, -0.39906216 ),
  vec2( 0.94558609, -0.76890725 ),
  vec2( -0.094184101, -0.92938870 ),
  vec2( 0.34495938, 0.29387760 )
);

De cette façon, suivant le nombre d’échantillon de la shadow map qui passe le test, le fragment généré sera plus ou moins sombre :

La constante 700.0 définit la « diffusion » des échantillons. Si la diffusion est trop petite, on obtient du crénelage ; si la diffusion est trop importante, on a alors des bandes (cette capture n’utilise pas PCF pour accentuer l’effet, mais utilise 16 échantillons à la place).

###Échantillonnage de Poisson stratifié

On peut supprimer cet effet de bande en choisissant différents échantillons pour chaque pixel. Il y a deux méthodes principales : l’échantillonnage de Poisson stratifié ou de Poisson tourné. Le stratifié choisit différents échantillons, le tourné toujours les mêmes, mais avec une rotation aléatoire afin qu’ils semblent différents. Dans ce tutoriel, je vais expliquer seulement la version stratifiée.

La seule différence avec la version précédente est que l’on indexe poissonDisk avec un indice aléatoire :

    for (int i=0;i<4;i++){
        int index = // A random number between 0 and 15, different for each pixel (and each i !)
        visibility -= 0.2*(1.0-texture( shadowMap, vec3(ShadowCoord.xy + poissonDisk[index]/700.0,  (ShadowCoord.z-bias)/ShadowCoord.w) ));
    }

On peut générer un nombre aléatoire avec une ligne comme celle-ci, qui retourne un nombre entre [0, 1[ :

    float dot_product = dot(seed4, vec4(12.9898,78.233,45.164,94.673));
    return fract(sin(dot_product) * 43758.5453);

Dans notre cas, seed4 sera une combinaison de i (faisant que l’on échantillonne à quatre emplacements différents) et … quelque chose d’autre. On peut utiliser gl_FragCoord (l’emplacement du pixel sur l’image) ou Position_worldspace :

        //  - A random sample, based on the pixel's screen location.
        //    No banding, but the shadow moves with the camera, which looks weird.
        int index = int(16.0*random(gl_FragCoord.xyy, i))%16;
        //  - A random sample, based on the pixel's position in world space.
        //    The position is rounded to the millimeter to avoid too much aliasing
        //int index = int(16.0*random(floor(Position_worldspace.xyz*1000.0), i))%16;

Cela fera que les motifs de l’image ci-dessus vont disparaître, au détriment d’un bruit visuel. Mais au final un bruit correctement distribué soit souvent moins désagréable que les motifs précédents.

Regarde le fichier tutorial16/ShadowMapping.fragmentshader pour les trois exemples d’implémentation.

Aller plus loin

Même avec toutes ces astuces, il reste de nombreuses, très nombreuses méthodes pour améliorer tes ombres. Voici les plus répandues :

Early bailing

Au lieu de prendre seize échantillons pour chaque fragment (encore une fois, c’est beaucoup), on prend quatre échantillons distants. S’ils sont tous dans la lumière ou dans l’ombre, tu peux sûrement considérer que les seize échantillons auraient donné le même résultat : tu peux arrêter tout de suite (bail early). Si certains sont différents, tu es probablement sur une bordure d’ombre, donc les seize sont nécessaires.

Spot lights

La gestion des lumières de type “spots” nécessite quelques petites modifications. La plus évidente est de changer la matrice de projection orthographique pour une matrice de projection en perspective :

glm::vec3 lightPos(5, 20, 20);
glm::mat4 depthProjectionMatrix = glm::perspective<float>(45.0f, 1.0f, 2.0f, 50.0f);
glm::mat4 depthViewMatrix = glm::lookAt(lightPos, lightPos-lightInvDir, glm::vec3(0,1,0));

Même chose, mais avec une pyramide tronquée de matrice de perspective à la place de celle de la matrice orthographique. Utilise texture2Dproj pour prendre en compte la division de la perspective (voir les notes de bas de pages du troisième tutoriel sur les matrices).

La seconde étape consiste à prendre en compte la perspective dans le shader (voir les notes de bas de pages du troisième tutoriel sur les matrices aussi). En résumé, une matrice de projection perspective n’a en fait pas besoin d’effectuer de perspective. Cela est effectué par le matériel, lors de la division avec la coordonnée projetée w. Ici, on émule la transformation dans le shader, donc on doit effectuer la division de la perspective. D’ailleurs, une matrice orthographique génère toujours des vecteurs homogènes avec w = 1, faisant qu’elle ne produit jamais de perspective.

Voici deux façons de faire cela en GLSL. La seconde utilise la fonction du langage textureProj, mais les deux méthodes produisent le exactement le même résultat.

if ( texture( shadowMap, (ShadowCoord.xy/ShadowCoord.w) ).z  <  (ShadowCoord.z-bias)/ShadowCoord.w )
if ( textureProj( shadowMap, ShadowCoord.xyw ).z  <  (ShadowCoord.z-bias)/ShadowCoord.w )

Lumières ponctuelles

Même chose, mais avec une cubemap (carte cubique) de profondeur. Une cubemap est un ensemble de six textures, une pour chaque côté du cube. De plus, l’accès ne se fait pas avec des coordonnées UV standard, mais avec un vecteur 3D représentant une direction.

La profondeur est conservée pour chaque direction dans l’espace, rendant possible la projection des ombres tout autour de la lumière ponctuelle.

Combination of several lights

L’algorithme gère plusieurs lumières, mais garde à l’esprit que chaque lumière nécessite un rendu supplémentaire de la scène afin de produire la carte d’ombres. Cela nécessitera une quantité énorme de mémoire lors de l’application des ombres et tu peux être très rapidement limité par la bande passante.

Automatic light frustum

Dans ce tutoriel, la zone de lumière est produite à la main pour contenir toute la scène. Bien que cela fonctionne dans cet exemple restreint, c’est à éviter. Si ta carte s’étend sur 1 km x 1 km, chaque texel de ta shadow map 1024x1024 prendra un mètre carré, ce qui est honteux. La matrice de projection de la lumière doit être aussi compacte que possible.

Pour les lumières spots, cela peut être facilement modifié en jouant sur sa portée.

Pour les lumières directionnelles, comme le soleil, c’est plus compliqué : elles illuminent réellement toute la scène. Voici une façon de calculer cette pyramide tronquée :

  1. Les Potential Shadow Receivers, ou PSR, sont des objets qui appartiennent en même temps à la zone de lumière, à celle de la vue et à la boîte englobante de la scène. Comme leur nom le suggère, ces objets sont susceptibles d’être dans l’ombre : ils sont visibles par la caméra et la lumière ;

  2. Les Potential Shadow Casters, ou PSC, sont tous les Potential Shadow Receivers, plus tous les objets qui se tiennent entre eux et la lumière (un objet peut ne pas être visible mais produire une ombre visible).

Donc, pour calculer la matrice de projection de la lumière, prend tous les objets visibles, retire ceux qui sont trop loin et calcule leur boîte englobante. Ajoute les objets se tenant entre la boîte englobante et la lumière, et calcule la nouvelle boîte englobante (mais cette fois, alignée suivant la direction de la lumière).

Le calcul précis de ces ensembles implique le calcul d’intersections d’enveloppes convexes, mais cette méthode est bien plus facile à implémenter.

Cette méthode provoquera des apparitions soudaines lorsque les objets disparaîtrons de la zone, car la résolution de la texture d’ombre diminuera brusquement. Les textures d’ombre en cascade ne souffrent pas de ce problème, mais sont plus compliquées à implémenter et tu peux toujours compenser cela en adoucissant le redimensionnement de la texture dans le temps.

Shadow maps exponentielles

Les cartes d’ombres exponentielles tentent de limiter le crénelage en supposant qu’un fragment se situant dans l’ombre, mais proche de la surface éclairée, est en fait « quelque part entre les deux ». Cela est lié au biais, sauf que le test n’est plus binaire : le fragment devient plus sombre lorsque la distance de la surface éclairée augmente.

C’est de la triche, évidemment, et des artefacts peuvent apparaître lorsque deux objets se recouvrent.

Light-space perspective shadow maps

Les LiSPSM ajustent la matrice de projection de la lumière afin d’obtenir une plus grande précision pour les objets proches de la caméra. C’est très important dans les cas de « duelling frustra » : tu regardes dans une direction, mais la lumière spot « semble » dans la direction opposée. Vous avez une grande précision pour la carte d’ombres près de la lumière, soit loin de toi et une faible résolution proche de la caméra, là où tu en as le plus besoin.

Par contre, les LiSPSM sont délicates à implémenter. Lis les références pour des détails d’implémentation.

Shadow maps en cascade

Les CSM gèrent le même problème que les LiSPSM mais d’une manière différente. Elles utilisent simplement plusieurs (2-4) cartes d’ombres standards pour les différentes parties de la zone vue. La première gère les premiers mètres, donc tu vas obtenir une bonne résolution pour une petite zone. La prochaine carte d’ombres gère les objets plus loin. La dernière carte d’ombres gère la grosse partie de la scène, mais à cause de la perspective, elle ne sera pas aussi importante visuellement que la zone la plus proche.

Les cartes d’ombres en cascade ont, au moment de l’écriture (2012), le meilleur compromis complexité/qualité. C’est la solution de choix dans bien des cas.

Conclusion

Comme tu peux le voir, les shadow maps sont un sujet compliqué. Chaque année, de nouvelles variations et améliorations sont publiées, et aujourd’hui (2012), aucune solution n’est parfaite. (PS: C’est toujours vrai en 2015)

Heureusement, les solutions présentées peuvent être mélangées : il est parfaitement possible d’avoir des shadow maps en cascade dans une perspective dans l’espace lumière, adoucie avec le PCF… Essaye d’expérimenter toutes ces techniques.

Comme conclusion, je vous suggère de rester avec les lightmap précalculées autant que possible et d’utiliser les shadow maps seulement pour les objets dynamiques. Et assures-toi que la qualité visuelle des deux soit équivalente : il n’est pas bon d’avoir des ombres statiques parfaites et des ombres dynamiques atroces.

  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Tutoriel 17 : Les rotations

Introduction

Ce tutoriel sort du contexte d’OpenGL, mais s’attaque néanmoins à un problème très courant : comment représenter les rotations ?

Dans le troisième tutoriel - les matrices, on a appris que celles-ci sont capables de tourner un point autour d’un axe donné. Bien que les matrices offrent une méthode propre pour transformer les sommets, leur gestion est difficile : comme, par exemple, l’obtention de la matrice de rotation à partir de la matrice finale.

On présentera les deux manières les plus courantes pour définir des rotations : les angles d’Euler et les quaternions. Et le plus important, on expliquera pourquoi tu devrais probablement utiliser les quaternions.

Avant-propos : rotation VS orientation

En lisant des articles sur les rotations, tu peux être pertubé par le vocabulaire. Dans ce tutoriel :

  • Une orientation est un état : « l’orientation de l’objet est …»
  • Une rotation est une opération : « appliquer cette rotation à l’objet »

Lorsque tu appliques une rotation, tu changes l’orientation. Les deux peuvent être représentés avec les mêmes outils, menant à la confusion. Maintenant, on y va …

Euler Angles

Les angles d’Euler sont la méthode la plus simple d’imaginer une orientation. Tu stockes trois orientations autour des axes X, Y et Z. C’est un concept très facile à comprendre. Tu peux utiliser un vec3 pour stocker la rotation :

vec3 EulerAngles( RotationAroundXInRadians, RotationAroundYInRadians, RotationAroundZInRadians);

Ces trois rotations sont ensuite appliquées successivement, habituellement dans cet ordre : en premier Y, puis Z et X (mais pas obligatoirement). L’utilisation d’un autre ordre entraîne d’autres résultats.

Une utilisation simple des angles d’Euler est l’orientation d’un personnage. Habituellement, les personnages de jeux ne tournent pas autour des axes X et Z, seulement autour de l’axe vertical. Donc, il est plus simple d’écrire, de comprendre et de maintenir un float direction; que trois orientations différentes.

Voila une autre bonne utilisation des angles d’Euler : une caméra de type FPS (First Person Shooter) : tu as un angle pour le cap (Y) et un pour le haut/bas (X). Jetez un œil au fichier common/controls.cpp pour voir un exemple.

Cependant, lorsque les choses deviennent plus complexes, les angles d’Euler rendront le travail plus difficile. Par exemple :

  • L’interpolation douce entre deux orientations est complexe. L’interpolation naïve des angles autour de X, Y et Z sera horrible
  • L’application de plusieurs rotations est compliquée et inexacte : tu dois calculer la matrice de rotation finale et deviner les angles d’Euler de cette matrice
  • Un problème bien connu : le « blocage de cardan » (« Gimbal lock »). Il bloquera quelques fois vos rotations et d’autres singularités retourneront votre modèle à l’envers.
  • Des angles différents donnent la même rotation (-180° et 180°, par exemple), ça complique les comparaison d’orientation.
  • C’est le bordel ! Comme dit précédemment, le bon ordre est habituellement YZX, mais si tu utilises une bibliothèque avec un autre ordre, tu vas avoir des soucis ;
  • Certaines opérations sont complexes : par exemple, la rotation de N degrés autour d’un axe spécifique.

Les quaternions sont l’outil qui résout ces problèmes pour représenter les rotations.

Quaternions

Un quaternion est un ensemble de quatre nombres, [x, y, z, w], qui représentent les rotations de la façon suivante :

// RotationAngle is in radians
x = RotationAxis.x * sin(RotationAngle / 2)
y = RotationAxis.y * sin(RotationAngle / 2)
z = RotationAxis.z * sin(RotationAngle / 2)
w = cos(RotationAngle / 2)

RotationAxis est, comme son nom l’indique, l’axe autour duquel vous souhaitez effectuer la rotation.

RotationAngle est l’angle de rotation autour de cet axe.

Donc fondamentalement, les quaternions stockent un axe de rotation et un angle de rotation, d’une façon qui simplifira la combinaison des rotations.

Lire des quaternions

Le format est définitivement moins intuitif que celui des angles d’Euler, mais il reste lisible : les composantes xyz correspondent grossièrement à l’axe de rotation et w est l’arc cosinus de l’angle de rotation (divisé par 2). Par exemple, imagines que tu veux les valeurs suivantes dans le débogueur : [0.7, 0, 0, 0.7]. x = 0.7, c’est plus grand que y et z, donc vous savez grossièrement que la rotation est principalement autour de l’axe X, et 2 * acos(0.7) = 1.59 radians, donc que la rotation est de 90°.

Pareillement, [0, 0, 0, 1] (w = 1) signifie que l’angle = 2*acos(1) = 0, donc que c’est un quaternion unitaire, ne faisant aucune rotation.

Opérations de bases

Connaître les mathématiques derrière les quaternions n’est que rarement utile : la représentation est tellement non intuitive que tu te reposes habituellement sur les fonctions utilitaires qui feront les calculs pour toi. Si tu êtes intéressés, regardez les livres de mathématiques dans la page des outils et liens utiles.

Comment créer un quaternion en C++ ?

// Don't forget to #include <glm/gtc/quaternion.hpp> and <glm/gtx/quaternion.hpp>

// Creates an identity quaternion (no rotation)
quat MyQuaternion;

// Direct specification of the 4 components
// You almost never use this directly
MyQuaternion = quat(w,x,y,z);

// Conversion from Euler angles (in radians) to Quaternion
vec3 EulerAngles(90, 45, 0);
MyQuaternion = quat(EulerAngles);

// Conversion from axis-angle
// In GLM the angle must be in degrees here, so convert it.
MyQuaternion = gtx::quaternion::angleAxis(degrees(RotationAngle), RotationAxis);

Comment créer un quaternion en GLSL ?

Tu ne le fais pas. Convertis le quaternion vers une matrice de rotation et utilises-la dans la matrice de modèle. Tes sommets seront tournés comme d’habitude, avec la matrice MVP.

Dans quelques cas, tu peux réellement vouloir utiliser les quaternions en GLSL, par exemple si tu fais l’animation d’un squelette sur le GPU. Il n’y a pas de type pour les quaternions en GLSL, mais tu peux utiliser un vec4 et faire les mathématiques toi-même dans le shader.

Comment convertir un quaternion vers une matrice ?

mat4 RotationMatrix = quaternion::toMat4(quaternion);

Tu peux maintenant construire ta matrice de modèle comme d’habitude :

mat4 RotationMatrix = quaternion::toMat4(quaternion);
...
mat4 ModelMatrix = TranslationMatrix * RotationMatrix * ScaleMatrix;
// Tu peux maintenant utiliser ModelMatrix pour construire la matrice MVP

Donc, quelle méthode choisir ?

Choisir entre les angles d’Euler et les quaternions est difficile. Les angles d’Euler sont intuitifs pour les artistes, donc si tu écris un éditeur 3D, utilises-les. Mais les quaternions sont pratiques pour les programmeurs et aussi moins couteux, donc tu devrais les utiliser dans le cœur du moteur 3D.

Le consensus général est exactement cela : utilises les quaternions en interne et exposes les angles d’Euler à chaque fois qu’il y a une interface utilisateur.

Tu vas être capable de gérer tout ce que tu veux (ou du moins, cela sera plus facile) et tu peux toujours utiliser les angles d’Euler pour les entités le nécessitant (comme dit précédemment : la caméra, les humanoïdes et c’est à peu près tout) avec une simple conversion.

Autres ressources

  • Les livres dans la page des outils et liens utiles !
  • Aussi vieux qu’il puisse être, le Game Programming Gem 1 contient quelques articles géniaux sur les quaternions. Tu peux probablement le trouver en ligne aussi.
  • Cette présentation de la GDC sur les rotations
  • Le tutoriel sur les quaternions du wiki des programmeurs de jeux vidéo
  • La FAQ sur les quaternions d’Ogre, bien que la seconde partie soit principalement spécifique à Ogre
  • Les fichiers d’Ogre Vector3D.h et Quaternions.cpp.

Feuille de triche

Comment savoir que deux quaternions sont similaires ?

Lors de l’utilisation d’un vecteur, le produit scalaire donne le cosinus de l’angle entre ces deux vecteurs. Si cette valeur est 1, alors les vecteurs sont dans la même direction.

Avec les quaternions, c’est la même chose :

float matching = quaternion::dot(q1, q2);
if ( abs(matching-1.0) < 0.001 ){
    // q1 and q2 are similar
}

Tu peux aussi obtenir l’angle entre q1 et q2 en prenant le acos() de ce produit scalaire.

Comment appliquer une rotation sur un point ?

Tu peux faire comme ça :

rotated_point = orientation_quaternion *  point;

… mais si tu souhaites calculer ta matrice de modèle, tu devrais plutot convertir le quaternion en une matrice.

Le centre de la rotation est toujours l’origine.

Si tu souhaites tourner autour d’un autre point :

rotated_point = origin + (orientation_quaternion * (point-origin));

Comment interpoler entre deux quaternions ?

Cela s’appelle SLERP : Sphérical Liner intERPolation. Avec GLM, tu peux le faire avec la fonction mix :

glm::quat interpolatedquat = quaternion::mix(quat1, quat2, 0.5f); // or whatever factor

Comment accumuler deux rotations ?

C’est simple ! Multiplie les deux quaternions ensemble. L’ordre est identique à celui des matrices, c’est-à-dire l’inverse :

quat combined_rotation = second_rotation * first_rotation;

Comment trouver la rotation entre deux vecteurs ?

(En d’autres mots : Comment trouver le quaternion dont on a besoin pour tourner v1 et qu’il corresponde à v2.)

L’idée de base est évidente :

  • L’angle entre deux vecteurs est facilement trouvable : le produit scalaire donne son cosinus
  • L’axe nécessaire est aussi facilement trouvable : c’est le produit vectoriel des deux vecteurs.

L’algorithme suivant fait exactement cela, mais gère aussi quelques cas spéciaux :

quat RotationBetweenVectors(vec3 start, vec3 dest){
    start = normalize(start);
    dest = normalize(dest);

    float cosTheta = dot(start, dest);
    vec3 rotationAxis;

    if (cosTheta < -1 + 0.001f){
        // special case when vectors in opposite directions:
        // there is no "ideal" rotation axis
        // So guess one; any will do as long as it's perpendicular to start
        rotationAxis = cross(vec3(0.0f, 0.0f, 1.0f), start);
        if (gtx::norm::length2(rotationAxis) < 0.01 ) // bad luck, they were parallel, try again!
            rotationAxis = cross(vec3(1.0f, 0.0f, 0.0f), start);

        rotationAxis = normalize(rotationAxis);
        return gtx::quaternion::angleAxis(180.0f, rotationAxis);
    }

    rotationAxis = cross(start, dest);

    float s = sqrt( (1+cosTheta)*2 );
    float invs = 1 / s;

    return quat(
        s * 0.5f,
        rotationAxis.x * invs,
        rotationAxis.y * invs,
        rotationAxis.z * invs
    );

}

(Tu peux trouver cette fonction dans common/quaternion_utils.cpp.)

J’ai besoin d’un équivalent à gluLookAt. Comment orienter un objet vers un point ?

Utilise RotationBetweenVectors !

// Find the rotation between the front of the object (that we assume towards +Z,
// but this depends on your model) and the desired direction
quat rot1 = RotationBetweenVectors(vec3(0.0f, 0.0f, 1.0f), direction);

Maintenant, tu peux aussi souhaiter que ton objet soit droit :

// Recompute desiredUp so that it's perpendicular to the direction
// You can skip that part if you really want to force desiredUp
vec3 right = cross(direction, desiredUp);
desiredUp = cross(right, direction);

// Because of the 1rst rotation, the up is probably completely screwed up.
// Find the rotation between the "up" of the rotated object, and the desired up
vec3 newUp = rot1 * vec3(0.0f, 1.0f, 0.0f);
quat rot2 = RotationBetweenVectors(newUp, desiredUp);

Maintenant, combine-les :

quat targetOrientation = rot2 * rot1; // remember, in reverse order.

Attention, « direction » est, bien sûr, une direction et non la position cible ! Tu peux calculer la position simplement avec : targetPos - currentPos.

Une fois que tu as l’orientation cible, tu vas probablement souhaiter effectuer une interpolation entre startOrientation et targetOrientation. (Tu peux trouver cette fonction dans common/quaternion_utils.cpp).

Comment utiliser LookAt mais en limitant la rotation à une certaine vitesse ?

L’idée de base est d’effectuer un SLERP (utilise glm::mix), mais de jouer avec la valeur d’interpolation afin que l’angle ne soit pas supérieur à la valeur désirée :

float mixFactor = maxAllowedAngle / angleBetweenQuaternions;
quat result = glm::gtc::quaternion::mix(q1, q2, mixFactor);

Voici une fonction plus complexe, qui gère les cas spéciaux. Elle n’utilise même pas directement mix() comme optimisation :

quat RotateTowards(quat q1, quat q2, float maxAngle){

    if( maxAngle < 0.001f ){
        // No rotation allowed. Prevent dividing by 0 later.
        return q1;
    }

    float cosTheta = dot(q1, q2);

    // q1 and q2 are already equal.
    // Force q2 just to be sure
    if(cosTheta > 0.9999f){
        return q2;
    }

    // Avoid taking the long path around the sphere
    if (cosTheta < 0){
        q1 = q1*-1.0f;
        cosTheta *= -1.0f;
    }

    float angle = acos(cosTheta);

    // If there is only a 2&deg; difference, and we are allowed 5&deg;,
    // then we arrived.
    if (angle < maxAngle){
        return q2;
    }

    float fT = maxAngle / angle;
    angle = maxAngle;

    quat res = (sin((1.0f - fT) * angle) * q1 + sin(fT * angle) * q2) / sin(angle);
    res = normalize(res);
    return res;

}

Que tu peux utiliser comme suit :

CurrentOrientation = RotateTowards(CurrentOrientation, TargetOrientation, 3.14f * deltaTime );

(Tu peux trouver cette fonction dans common/quaternion_utils.cpp)

Comment je peux …

Si tu arrives pas à trouver comment faire quelque chose, envoie nous un email, et on l’ajoutera à la liste !

  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Tutorial 18 : Billboards & Particles

Ce tutoriel est divisé en 2 parties :

  • Partie 1: Billboards
  • Partie 2: Particules et instanciation d’objet multiple
  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Tutoriel 9 : indexation de VBO

Un compteur de FPS

Dans le monde des graphismes en temps réel, il est important de garder un œil sur les performances. Une bonne pratique est de déterminer un nombre de FPS comme objectif (habituellement 60 ou 30) et faire tout son possible pour s’y tenir.

Un compteur de FPS ressemble à cela :

 double lastTime = glfwGetTime();
 int nbFrames = 0;

 do{

     // Measure speed
     double currentTime = glfwGetTime();
     nbFrames++;
     if ( currentTime - lastTime >= 1.0 ){ // If last prinf() was more than 1 sec ago
         // printf and reset timer
         printf("%f ms/frame\n", 1000.0/double(nbFrames));
         nbFrames = 0;
         lastTime += 1.0;
     }

     ... rest of the main loop

Il y a une chose étrange dans ce code. Il affiche le temps, en millisecondes, nécessaire pour afficher une image (la moyenne sur une seconde) au lieu du nombre d’images dessinées durant la dernière seconde.

En fait, c’est généralement beaucoup mieux. Ne fais pas confiance aux FPS. FramePerSecond = 1/SecondPerFrame, ce qui est une proportionnelle inverse et les humains sont nuls pour comprendre ce genre de proportionnelle. Prend un exemple : tu écris une superbe fonction de rendu qui fonctionne à 1000 FPS (1ms/image), mais tu oublies un petit calcul dans le shader, qui ajoute un coût de 0,1 ms. Et bam, 1/0.0011 = 900, tu viens de perdre 100 FPS. Moralité : n’utilise jamais les FPS pour des analyses de performances.

Si tu prévois de faire un jeu fonctionnant à 60 FPS, ton objectif sera 16,6666 ms ; si tu prévois de faire un jeu fonctionnant à 30 FPS, votre objectif sera 33,3333 ms. C’est tout ce que tu dois savoir.

Ce code est utilisé dans tous les tutoriels à partir du neuvième : indexation VBO ; regardez le fichier tutorial09_vbo_indexing/tutorial09.cpp. D’autres outils pour les performances sont disponibles dans la page Outils - débogueurs.

  • contact@opengl-tutorial.org

Free tutorials for modern Opengl (3.3 and later) in C/C++


Développez vos applications 3D avec OpenGL 3

Développez vos applications 3D avec OpenGL 3.3

Vu dans ... https://openclassrooms.com/courses/developpez-vos-applications-3d-avec-opengl-3-3

Introduction
 :magicien:
Créer une fenêtre avec la SDL 2.0
Premier affichage
Introduction aux shaders
Les matrices
La troisième dimension (Partie 1/2)
La troisième dimension (Partie 2/2)
Push et Pop
Les évènements avec la SDL 2.0
Les textures
La caméra
TP : Une relique retrouvée

:magicien:
Les Vertex Buffer Objects
Les Vertex Array Objects
La compilation de shaders
OpenGL Shading Language
Les shaders démystifiés (Partie 1/2)
Les shaders démystifiés (Partie 2/2)
Les Frame Buffer Objects


Créer une fenêtre avec la SDL 2.0

Dans ce chapitre nous allons créer une fenêtre avec la librairie SDL. Pour ceux qui connaissent la SDL avec le tuto de M@teo, vous pourrez comparer les deux versions. ;)

Préparation de l'IDE

Pour coder à travers ce tutoriel, je vous recommande d'utiliser l'IDE Code::Blocks qui est un outil très utilisé sur le site du Zéro. :) Vous pourrez donc trouver plus facilement de l'aide en cas de problème. Pour ceux qui souhaitent l'utiliser, voici un lien pour le télécharger (Windows uniquement). Il existe deux versions de cet IDE, vous pouvez choisir celle que vous voulez, les projets donnés devraient fonctionner sur les deux sans problème.

Télécharger : Code::Blocks 12.11 (Windows uniquement)

Pour les linuxiens :

sudo apt-get install codeblocks

D'ailleurs, si vous utilisez cet IDE je vais pouvoir vous fournir directement le template nécessaire pour créer un projet SDL 2.0 . Ce template vous permettra de linker les librairies automatiquement sans que vous ayez à faire des manips compliquées. ;)

Télécharger : Template SDL 2.0 Code::Blocks 10.05 et 12.11 - Windows et Linux

Pour Windows, dézippez l'archive et fusionnez le dossier "share" de l'archive avec le dossier "share" de Code::Blocks (chez moi : C:\Program Files (x86)\CodeBlocks\share ).

Pour Linux, dézippez l'archive où vous voulez puis exécutez les commandes suivantes (pas dans le dossier "share" mais dans celui qui le contient) :

sudo cp -r share/CodeBlocks/* /usr/share/codeblocks/
sudo chmod -R 715 /usr/share/codeblocks/templates/wizard/sdl2/
sudo chmod 715 /usr/share/codeblocks/templates/wizard/config.script

Si vous avez un problème pour utiliser les espaces dans Code::Blocks c'est certainement parce que vous avez un problème de clavier. Faites un tour dans les paramètres généraux pour configurer votre clavier en Français(France).

Nous pouvons maintenant créer notre premier programme en SDL 2.0. :D

Pour cela, il faut d'abord créer un projet SDL 2.0 et non pas OpenGL, ne vous trompez pas ! Sous Code::Blocks la procédure est la suivante :

. File -> New -> Project -> SDL 2.0 project

Vous vous souvenez du dossier "SDL-2.0" du chapitre précédent ? Si vous ne l'avez pas encore installé au bon endroit faites-le maintenant :) (reportez-vous au chapitre précédent).

Occupons-nous maintenant du linkage d' OpenGL , il faut dire à notre IDE que nous souhaitons utiliser cette librairie sinon il va nous mettre plein d'erreurs au moment de la compilation. Voici la procédure à effectuer sous Code::Blocks :

. Project -> Build options -> Linker settings -> Add

Un petit encadré vous demande quelle librairie vous souhaitez linker, cela dépend de votre système d'exploitation. Vous ne pouvez renseigner qu'une seule librairie par encadré. Ré-appuyez sur le bouton Add pour en ajouter une nouvelle.

IDE | Option
Code::Blocks Windows | opengl32 glew32
Code::Blocks Linux | GL
DevC++ | -lopengl32 -lglew32
Visual Studio | opengl32.lib glew32.lib

Selon votre OS, il suffit d'ajouter le ou les mot-clef(s) dans le petit encadré.

Pour ceux qui se demanderaient où est passée l'option de linkage pour GLU, rappelez-vous que nous n'utiliserons plus gluPerspective et gluLookAt, ces fonctions étant invalides avec la nouvelle version d'OpenGL.

Pour les utilisateurs de CodeBlocks sous Windows et uniquement sous Windows, Code::Blocks va certainement vous demander où se trouve la SDL avec cette popup :

Dans le champ base qui vous est proposé, renseignez le chemin vers le répertoire "SDL-2.0" que vous avez installé précédemment (chez moi : C:\Program Files (x86)\CodeBlocks\MinGW\SDL-2.0 ). Ne renseignez aucun autre champ et ça devrait être bon. Une popup vous indiquera surement un message du genre "Please select a valid location", mais ce sera une fausse alerte ne vous en faites pas, appuyez sur le bouton next. ;)

En parlant de ce dossier, si vous avez jeté un coup d’œil à l'intérieur vous vous apercevrez qu'il y a un sous-dossier nommé "dll". Ce dossier contient les fichiers SDL2.dll et glew32.dll, ils sont indispensables pour lancer vos futurs programmes.

Vous avez deux solutions pour les utiliser : soit vous les incluez dans le dossier de chaque projet, soit vous les copier dans le dossier "bin" du compilateur MinGW (chez moi : C:\Program Files (x86)\CodeBlocks\MinGW\bin ). La première solution est la plus propre mais aussi la plus contraignante, faites comme bon vous semble mais il faut que vous utilisiez au moins une de ces techniques pour lancer vos programmes. ;)

Pour ceux qui utilisent d'autres IDE comme Visual C++ ou Eclipse vous allez devoir renseigner d'autres librairies car je n'ai pas de template tout fait à vous proposer. Il faudra que vous renseigneriez les dossiers où se trouvent les librairies et les includes. De plus, vous devrez linker manuellement deux librairies supplémentaires :

Link

SDL2main

SDL2

Premier programme

Nous avons tous les outils, ceux-ci sont tous configurés. Bien, commençons. :magicien:

Voici un code de base permettant de créer une fenêtre avec la SDL 1.2 :

#include <SDL/SDL.h>

#include <iostream>

int main(int argc, char **argv)

{

   // Notre fenêtre et le clavier

   SDL_Surface *fenetre(0);

   SDL_Event evenements = {0};

   bool terminer(false);

   // Initialisation de la SDL

   if(SDL_Init(SDL_INIT_VIDEO) < 0)

   {

       std::cout << "Erreur lors de l'initialisation de la SDL : " << SDL_GetError() << std::endl;

       SDL_Quit();

       return -1;

   }

   // Création de la fenêtre

   fenetre = SDL_SetVideoMode(800, 600, 32, SDL_HWSURFACE);

   // Boucle principale

   while(!terminer)

   {

        SDL_WaitEvent(&evenements);

        if(evenements.type == SDL_QUIT)

            terminer = true;

   }

   // On quitte la SDL

   SDL_Quit();

   return 0;

}

Facile non ? Et bien maintenant, je vais vous demander d'oublier la moitié de ce code. :D

Quoi ? C'est si différent que ça ?

Oui en effet, car souvenez-vous que nous travaillons avec la version 2.0. Et vu que l'on passe d'une version 1.x à une version 2.0, le code change beaucoup.

Commençons à coder avec la SDL 2.0 pour se mettre l'eau à la bouche :

#include <SDL2/SDL.h>

#include <iostream>

int main(int argc, char **argv)

{

    // Initialisation de la SDL

    if(SDL_Init(SDL_INIT_VIDEO) < 0)

    {

        std::cout << "Erreur lors de l'initialisation de la SDL : " << SDL_GetError() << std::endl;

        SDL_Quit();

        return -1;

    }

    // On quitte la SDL

    SDL_Quit();

    return 0;

}

Bon jusqu'ici, pas de grand changement. Mis à part l'inclusion de la SDL qui passe de " SDL/SDL.h " à " SDL2/SDL.h ". En même temps on ne peut pas changer grand chose. :p

Voici le nouveau code qui permet de créer une fenêtre :

#include <SDL2/SDL.h>

#include <iostream>

int main(int argc, char **argv)

{

    // Notre fenêtre

    SDL_Window* fenetre(0);

    // Initialisation de la SDL

    if(SDL_Init(SDL_INIT_VIDEO) < 0)

    {

        std::cout << "Erreur lors de l'initialisation de la SDL : " << SDL_GetError() << std::endl;

        SDL_Quit();

        return -1;

    }

    // Création de la fenêtre

    fenetre = SDL_CreateWindow("Test SDL 2.0", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_SHOWN);

    // On quitte la SDL

    SDL_DestroyWindow(fenetre);

    SDL_Quit();

    return 0;

}

Nous voyons ici deux choses importantes, premièrement :

SDL_Window* fenetre;

Le pointeur SDL_Window remplacera désormais notre fenêtre, il n'y a donc plus de SDL_Surface. Maintenant notre fenêtre aura sa structure à part entière bien différente des surfaces classiques.

Deuxièmement :

fenetre = SDL_CreateWindow("Test SDL 2.0", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_SHOWN);

Vous l'aurez compris, cette fonction va nous permettre de créer notre fenêtre, elle vient donc remplacer notre bonne vielle SDL_SetVideoMode. Voici le prototype de cette fonction :

SDL_Window* SDL_CreateWindow(const char *title, int x, int y, int w, int h, Uint32 flags);

  • title : le titre de notre fenêtre

  • x : l'abscisse de la position de la fenêtre, SDL_WINDOWPOS_CENTERED signifie que nous centrons la fenêtre par rapport à l'axe x

  • y : l'ordonnée de la position, SDL_WINDOWPOS_CENTERED signifie la même chose mais sur l'axe y

  • w : (widht) la largeur de la fenêtre

  • h : (height) la hauteur de la fenêtre

  • flags : ce paramètre est un peu spécial, il faudra toujours mettre SDL_WINDOW_SHOWN. Plus tard, ce sera ici que l'on indiquera à la SDL que nous souhaitons utiliser OpenGL

Cette fonction retourne notre fenêtre dans un pointeur de type SDL_Window.

Quant à la dernière fonction, son utilisation est simple : elle permet de détruire proprement notre fenêtre :

SDL_DestroyWindow(SDL_Window *window);

Nous lui donnerons la fenêtre créée au début du programme.

SDL_DestroyWindow(fenetre);

Puisque l'on parle de l'initialisation de la fenêtre, on va en profiter pour vérifier la valeur du pointeur SDL_Window. En effet dans de rares cas, celui-ci peut être nul à cause d'un éventuel problème logiciel ou matériel. On va donc tester sa valeur dans un if, s'il est nul alors on quitte la SDL en fournissant un message d'erreur, sinon c'est que tout va bien donc on continue :

// Création de la fenêtre

fenetre = SDL_CreateWindow("Test SDL 2.0", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_SHOWN);

if(fenetre == 0)

{

    std::cout << "Erreur lors de la creation de la fenetre : " << SDL_GetError() << std::endl;

    SDL_Quit();

    return -1;

}

Ajoutons maintenant le code gérant les évènements qui connait, lui-aussi, son lot de modifications.

Avant, pour savoir si une fenêtre devait se fermer, nous devions vérifier la variable type d'une structure SDL_Event comme ceci par exemple :

// Variables

SDL_Event evenements;

bool terminer(false);

// Gestion des évènements

SDL_WaitEvent(&evenements);

if(evenements.type == SDL_QUIT)

    terminer = true;

Avec la SDL 2.0, la gestion des évènements change quelques peu (nous verrons les différences un peu plus tard). Maintenant, pour savoir si on doit fermer une fenêtre, nous ferrons ceci :

// Variables

SDL_Event evenements;

bool terminer(false);

// Gestion des évènements

SDL_WaitEvent(&evenements);

if(evenements.window.event == SDL_WINDOWEVENT_CLOSE)

    terminer = true;

Bien, résumons tout cela en un seul code. :)

#include <SDL2/SDL.h>

#include <iostream>

int main(int argc, char **argv)

{

    // Notre fenêtre

    SDL_Window* fenetre(0);

    SDL_Event evenements;

    bool terminer(false);

    // Initialisation de la SDL

    if(SDL_Init(SDL_INIT_VIDEO) < 0)

    {

        std::cout << "Erreur lors de l'initialisation de la SDL : " << SDL_GetError() << std::endl;

        SDL_Quit();

        return -1;

    }

    // Création de la fenêtre

    fenetre = SDL_CreateWindow("Test SDL 2.0", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_SHOWN);

    if(fenetre == 0)

    {

        std::cout << "Erreur lors de la creation de la fenetre : " << SDL_GetError() << std::endl;

        SDL_Quit();

        return -1;

    }

    // Boucle principale

    while(!terminer)

    {

    SDL_WaitEvent(&evenements);

    if(evenements.window.event == SDL_WINDOWEVENT_CLOSE)

        terminer = true;

    }

    // On quitte la SDL

    SDL_DestroyWindow(fenetre);

    SDL_Quit();

    return 0;

}

Les paramètres OpenGL

Tout ce code c'est bien mais il n'y a rien qui permet d'exploiter OpenGL, mais qu'attendons-nous ? :diable:

Il y a pas mal d'attributs (des options de configuration) à paramétrer pour rendre notre programme compatible avec OpenGL, commençons par le plus important :

SDL_GLContext contexteOpenGL;

Cette structure va permettre à la SDL de créer un contexte OpenGL. Ce contexte est très important, pour utiliser l'API graphique et ses fonctions. Il faut tout d'abord le configurer.

Voici le prototype de la fonction qui permet de spécifier des attributs OpenGL à la SDL :

SDL_GL_SetAttribute(SDL_GLattr attr, int value);

  • attr : c'est notre attribut, nous verrons lesquels il faut spécifier

  • value : c'est la valeur de l'attribut

Avec cette simple fonction, nous allons pourvoir spécifier à la SDL quelle version d'OpenGL on souhaite utiliser :

SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);

SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);

Vous l'aurez compris :

  • SDL_GL_CONTEXT_MAJOR_VERSION pour OpenGL 3.x

  • SDL_GL_CONTEXT_MINOR_VERSION pour la version mineure : x.1 (Dans le futur, cette valeur sera de 3 lorsque nous saurons manier OpenGL)

Petite question pour ceux qui connaissent la SDL 1.2, vous savez comment on utilise le double buffering ? :-°

Voici la réponse :p :

SDL_SetVideoMode(800, 600, 32, SDL_HWSURFACE | SDL_DOUBLEBUF);

Comme avec la SDL, on peut aussi activer le Double Buffuring avec OpenGL. Cependant ça ne se passe pas de la même façon. Voici comment faire :

SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);

  • SDL_GL_DOUBLEBUFFER : attribut activant le Double Buffering. Mettez 1 pour l'activer et 0 pour faire le contraire. ;)

  • SDL_GL_DEPTH_SIZE : attribut de profondeur du Double Buffer. Nous mettons sa valeur à 24 pour une profondeur de 24 bits.

Bon on avance c'est bien :) . Mais tout à l'heure, tu as parlé d'une structure : SDL_GLContext, qu'est-ce qu'on en fait ?

Très bonne remarque, on va l'utiliser maintenant justement grâce à la fonction :

SDL_GLContext SDL_GL_CreateContext(SDL_Window *window);

Cette fonction demande un pointeur SDL_Window pour y attacher le contexte OpenGL. De plus, elle renvoie une structure SDL_GLContext , et justement c'est ce dont nous avons besoin :)

Dans notre cas, nous lui donnerons notre fenêtre puis nous récupéreront la structure retournée :

contexteOpenGL = SDL_GL_CreateContext(fenetre);

Comme pour la fenêtre SDL , la création du contexte OpenGL peut lui aussi échouer. Le plus souvent ce sera parce que la version OpenGL demandée ne sera pas supportée par la carte graphique. Il faut donc tester la valeur de la variable contexteOpenGL. Si elle est égale à 0 c'est qu'il y a eu un problème. Dans ce cas on affiche un message d'erreur, on détruit la fenêtre puis on quitte la SDL :

// Création du contexte OpenGL

contexteOpenGL = SDL_GL_CreateContext(fenetre);

if(contexteOpenGL == 0)

{

    std::cout << SDL_GetError() << std::endl;

    SDL_DestroyWindow(fenetre);

    SDL_Quit();

    return -1;

}

Si tout se passe bien lors de la création du contexte alors on continue.

Tout comme pour la fenêtre SDL encore une fois, il existe aussi une fonction qui permet de détruire le contexte lorsque nous n'en avons plus besoin. Son appel ressemble à ceci :

SDL_GL_DeleteContext(contexteOpenGL);

Bien, résumons tout cela :

#include <SDL2/SDL.h>

#include <iostream>

int main(int argc, char **argv)

{

    // Notre fenêtre

    SDL_Window* fenetre(0);

    SDL_GLContext contexteOpenGL(0);

    SDL_Event evenements;

    bool terminer(false);

    // Initialisation de la SDL

    if(SDL_Init(SDL_INIT_VIDEO) < 0)

    {

        std::cout << "Erreur lors de l'initialisation de la SDL : " << SDL_GetError() << std::endl;

        SDL_Quit();

        return -1;

    }

    // Version d'OpenGL

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);

    // Double Buffer

    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);

    // Création de la fenêtre

    fenetre = SDL_CreateWindow("Test SDL 2.0", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_SHOWN);

    if(fenetre == 0)

    {

        std::cout << "Erreur lors de la creation de la fenetre : " << SDL_GetError() << std::endl;

        SDL_Quit();

        return -1;

    }

    // Création du contexte OpenGL

    contexteOpenGL = SDL_GL_CreateContext(fenetre);

    if(contexteOpenGL == 0)

    {

        std::cout << SDL_GetError() << std::endl;

        SDL_DestroyWindow(fenetre);

        SDL_Quit();

        return -1;

    }

    // Boucle principale

    while(!terminer)

    {

        SDL_WaitEvent(&evenements);

        if(evenements.window.event == SDL_WINDOWEVENT_CLOSE)

        terminer = true;

    }

    // On quitte la SDL

    SDL_GL_DeleteContext(contexteOpenGL);

    SDL_DestroyWindow(fenetre);

    SDL_Quit();

    return 0;

}

On est presque au bout courage. :p Il nous faut juste rajouter un paramètre dans la fonction GL_CreateWindow. Avec la SDL 1.2, ce paramètre ressemblait à ça :

SDL_SetVideoMode(.., .., .., SDL_OPENGL);

Avec la SDL 2.0, le nom change légèrement :

SDL_WINDOW_OPENGL

Il se place également dans la fonction qui créé la fenêtre :

fenetre = SDL_CreateWindow("Test SDL 2.0", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL);

Et voila notre code final :

#include <SDL2/SDL.h>

#include <iostream>

int main(int argc, char **argv)

{

    // Notre fenêtre

    SDL_Window* fenetre(0);

    SDL_GLContext contexteOpenGL(0);

    SDL_Event evenements;

    bool terminer(false);

    // Initialisation de la SDL

    if(SDL_Init(SDL_INIT_VIDEO) < 0)

    {

        std::cout << "Erreur lors de l'initialisation de la SDL : " << SDL_GetError() << std::endl;

        SDL_Quit();

        return -1;

    }

    // Version d'OpenGL

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);

    // Double Buffer

    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);

    // Création de la fenêtre

    fenetre = SDL_CreateWindow("Test SDL 2.0", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL);

    if(fenetre == 0)

    {

        std::cout << "Erreur lors de la creation de la fenetre : " << SDL_GetError() << std::endl;

        SDL_Quit();

        return -1;

    }

    // Création du contexte OpenGL

    contexteOpenGL = SDL_GL_CreateContext(fenetre);

    if(contexteOpenGL == 0)

    {

        std::cout << SDL_GetError() << std::endl;

        SDL_DestroyWindow(fenetre);

        SDL_Quit();

        return -1;

    }

    // Boucle principale

    while(!terminer)

    {

        SDL_WaitEvent(&evenements);

        if(evenements.window.event == SDL_WINDOWEVENT_CLOSE)

        terminer = true;

    }

    // On quitte la SDL

    SDL_GL_DeleteContext(contexteOpenGL);

    SDL_DestroyWindow(fenetre);

    SDL_Quit();

    return 0;

}

Vous devriez obtenir quelque chose comme ceci :

Quoi ! Mais on a une fenêtre blanche c'est nul !

Du calme, vous n'imaginez pas ce que l'on vient de faire (enfin ce que la SDL vient de faire), nous venons de créer un contexte dans lequel OpenGL va évoluer. En gros nous venons de créer l'univers. :D

Bref nous avons enfin une fenêtre SDL compatible avec OpenGL 3.1. Comparez le code avant et après, vous verrez la différence. Il y a plus de code certes, mais toutes ces instructions sont importantes. Ce nouvel environnement va nous permettre de créer des programmes plus paramétrables et donc plus puissants. ;)

Télécharger : Code Source C++ du Chapitre 2

Piouf, ça fait beaucoup de changements pour arriver grosso modo à la même chose. Mais au moins on passe à la nouvelle génération de programme.
Bon, on a une fenêtre c'est bien, mais pour le moment on a linké OpenGL pour rien. Passons maintenant à la partie OpenGL :pirate: .


Premier affichage

Nouveau chapitre, nouvelles notions.

Nous allons apprendre ici à faire nos premiers affichages dans la fenêtre SDL. Les notions que nous allons aborder nous seront utiles tout au long de ce tutoriel et constituent réellement la base du fonctionnement d'OpenGL. Vous aurez l'occasion de vous exercer à travers quelques exercices. Je ré-utiliserai ce procédé régulièrement car il n'y a rien de mieux que la pratique pour apprendre quelque chose.

Ceci étant dit, commençons dès maintenant ce nouveau chapitre. :)

Le fonctionnement d'OpenGL

Nous allons commencer ce chapitre par un peu de théorie. :p

Comment fonctionne OpenGL ?

Basiquement, OpenGL fonctionne avec un système de "points" que nous plaçons dans un monde 3D. Nous appelons un point, un vertex (au pluriel : vertices), en français on peut dire un sommet. La première chose à faire avec ces vertices, c'est leur donner une position.

Une position dans un monde 3D est constituée de 3 coordonnées: x (la largeur), y (la hauteur) et z (la profondeur). Chaque vertex doit avoir ses propres coordonnées. Les vertices sont à la base de tout, ils forment tout ce que vous voyez dans un jeu-vidéo : le héros, une arme, une voiture, des chaussettes ...

Une fois les vertices définis, on les relie entre eux pour former des formes géométriques simples : des carrés, des lignes, des triangles, ...

Voici comment OpenGL "voit" une map : une succession de vertices reliés entre eux pour former un terrain en mode filaire.

Maintenant que l'on a nos formes géométriques, il faut les "colorier", sinon nous n'aurions que des fils difformes qui ne ressembleraient à rien. Imaginez si on laissait tout comme ça, le jeu serait un peu particulier.

On peut distinguer deux formes de "coloriage": les textures et les couleurs (que nous spécifions nous-même). Les textures sont utilisées dans 99% des cas. En effet, nous ne spécifions que très rarement la couleur de nos objets directement, en général on le fait pour des tests ou dans un shader (retenez bien ce mot, nous verrons cela plus tard). Dans les premiers chapitres nous n'utiliserons pas les textures, nous accorderons un chapitre entier pour cela.

Ce qu'il faut retenir c'est qu'au final nous définissons les coordonnées de nos vertices (sommets) dans l'espace, puis nous les relions entre eux pour former une forme géométrique, enfin nous colorions cette forme pour avoir quelque chose de "consistant".

Nomenclature des fonctions OpenGL

Comme vous l'aurez remarqué avec la SDL, chaque fonction de la librairie commence par "SDL_" . Bonne nouvelle, avec OpenGL c'est pareil, chaque fonction commence par la même chose : "gl".

Autre point, il se peut que vous soyez surpris en voyant le nom de certaines fonctions se répéter plusieurs fois, c'est normal.

Prenons un exemple : glUniform2f(...) et glUniform2i(...)

Nous verrons l'utilité de ces fonctions plus tard. Vous voyez la différence ? Dans la première, la dernière lettre est un "f" et dans la deuxième c'est un "i".

En général, la dernière lettre d'une fonction OpenGL sert à indiquer le type du paramètre à envoyer :

  • i : integer (entier)

  • f : float (réel)

  • d : double (réel plus puissant)

  • ub : unsigned byte (octet entre 0 et 255)

  • ...

Selon le type de paramètre que vous enverrez il faudra utiliser la fonction correspondant au type de vos variables. :)

Vous remarquez aussi que l'avant-dernière lettre est un chiffre, ce chiffre peut lui aussi changer. En général, si le chiffre est 1 on envoie un seul paramètre, si c'est 2 on en envoie deux, ...

Autre point que vous remarquerez plus tard : les types de variables OpenGL. Vous tomberez souvent sur des fonctions demandant des paramètres de types GLfloat, GLbool, GLchar, ... Ce sont simplement des variables de type float, char, int, ... Le type GLbool ne pourra prendre que deux valeurs : soit GL_FALSE (faux) soit GL_TRUE (vrai).

Boucle principale

Comme avec la SDL, OpenGL fonctionne avec une boucle principale. Tous les calculs se feront dans cette boucle. De plus, à chaque affichage il va falloir effacer l'écran car la scène aura légèrement changée, puis ré-afficher chaque élément un à un. Voici la fonction permettant d'effacer l'écran :

glClear(GLbitfield mask)

Dans un premier temps on effacera uniquement ce qui se trouve à l'écran grâce au paramètre : GL_COLOR_BUFFER_BIT.

Avec la SDL, pour actualiser l'affichage on utilisait la fonction SDL_Flip(), mais avec OpenGL on utilisera la fonction :

SDL_GL_SwapWindow(SDL_Window* window);

Le paramètre window étant notre structure SDL_WindowID. ;)

Malheureusement il existe encore une différence entre Linux et Windows. Comme vous l'avez vu dans le chapitre précédent, pour utiliser OpenGL, Windows est obligé de passer par une autre librairie du nom de GLEW. Cette librairie va nous permettre d'utiliser les fonctions d'OpenGL 3. Mais comme toute librairie, il va falloir l'initialiser. Sous Windows vous devrez inclure l'en-tête suivant :

#ifdef WIN32

#include <GL/glew.h>

Pour initialiser la librairie GLEW, il faudra ajouter la fonction : glewInit(). Comme toute librairie, l'initialisation peut échouer, il faut donc tester son initialisation :

// On initialise GLEW

GLenum initialisationGLEW( glewInit() );

// Si l'initialisation a échouée :

if(initialisationGLEW != GLEW_OK)

{

    // On affiche l'erreur grâce à la fonction : glewGetErrorString(GLenum code)

    std::cout << "Erreur d'initialisation de GLEW : " << glewGetErrorString(initialisationGLEW) << std::endl;

    // On quitte la SDL

    SDL_GL_DeleteContext(contexteOpenGL);

    SDL_DestroyWindow(fenetre);

    SDL_Quit();

    return -1;

}

Voila pour Windows. Pour Linux c'est beaucoup plus simple, il suffit de placer une "define" spéciale pour indiquer que nous utiliserons les fonctions d'OpenGL 3 puis d'inclure le fichier d'en-tête "gl3.h" :

#define GL3_PROTOTYPES 1

#include <GL3/gl3.h>

Bien, récapitulons tout ceci :

#ifdef WIN32

#include <GL/glew.h>

#else

#define GL3_PROTOTYPES 1

#include <GL3/gl3.h>

#endif

#include <SDL2/SDL.h>

#include <iostream>

int main(int argc, char **argv)

{

    /* *** Création de la fenêtre SDL *** */

    #ifdef WIN32

        // On initialise GLEW

        GLenum initialisationGLEW( glewInit() );

        // Si l'initialisation a échouée :

        if(initialisationGLEW != GLEW_OK)

        {

            // On affiche l'erreur grâce à la fonction : glewGetErrorString(GLenum code)

            std::cout << "Erreur d'initialisation de GLEW : " << glewGetErrorString(initialisationGLEW) << std::endl;

            // On quitte la SDL

            SDL_GL_DeleteContext(contexteOpenGL);

            SDL_DestroyWindow(fenetre);

           SDL_Quit();

            return -1;

        }

    #endif

    // Boucle principale

    while(!terminer)

    {

        // Gestion des évènements

    SDL_WaitEvent(&evenements);

    if(evenements.window.event == SDL_WINDOWEVENT_CLOSE)

        terminer = true;

        // Nettoyage de l'écran

        glClear(GL_COLOR_BUFFER_BIT);

        // Actualisation de la fenêtre

        SDL_GL_SwapWindow(fenetre);

    }

    // On quitte la SDL

    SDL_GL_DeleteContext(contexteOpenGL);

    SDL_DestroyWindow(fenetre);

    SDL_Quit();

    return 0;

}

Repère et Origine

J'espère que vous connaissez la définition de ces deux termes. Un repère est un ensemble d'axes représentant au moins les axes X et Y, l'origine est le point de départ de ces axes.

En théorie voici ce que donne le repère d'OpenGL :

Mais dans un premier temps, nous utiliserons le repère par défaut d'OpenGL que voici :

L'origine du repère se trouve au centre de l'écran, et les coordonnées maximales affichables sont comprises entre (-1, -1) et (1, 1).

Nous utiliserons l'autre repère dès que nous y aurons inclus les matrices :) . De plus, nous serons dans un premier temps dans un monde 2D, nous ne ferons pas de cube ou autre forme complexe pour commencer. Nous verrons cela un peu plus tard. :p

Afficher un triangle

Afficher un triangle

Maintenant que le blabla théorique est terminé, nous pouvons reprendre la programmation. :magicien:

Dans le chapitre précédent, nous avons appris que tous les modèles 3D présents dans un jeu étaient constitués de sommets (vertices), puis qu'il fallait les relier pour former une surface. Et si on assemble ces formes, ça donne nos modèles 3D.

Notre premier exercice est simple : afficher un triangle. Pour pouvoir afficher n'importe quel modèle 3D il faut déjà spécifier ses vertices que nous allons ensuite donner à OpenGL. En général, on définie nos vertices dans un seul tableau, on place chaque coordonnée de vertex à la chaine comme ceci :

float vertices = {X_vertex_1, Y_vertex_1, Z_vertex_1,   X_vertex_2, Y_vertex_2, Z_vertex_2,   ...};

Dans un triangle il y a trois sommets, nous aurons donc 3 vertices.

float vertex1[] = {-0.5, -0.5};

float vertex2[] = {0.0, 0.5};

float vertex3[] = {0.5, -0.5};

N'oubliez pas que nous sommes en 2D pour l'instant, nous n'avons donc que les coordonnées x et y à définir. Ce début de code est bien mais si on utilise 3 tableaux, ça ne fonctionnera pas. Nous devons combiner les trois tableaux pour en former un seul :

float vertices[] = {-0.5, -0.5,   0.0, 0.5,   0.5, -0.5};

Maintenant, il faut envoyer ces coordonnées à OpenGL. Pour envoyer des informations nous avons besoin d'un tableau appelé "Vertex Attribut". Pour ceux qui connaissent les "Vertex Arrays", c'est la même chose ;) . Sauf que maintenant ce n'est plus une optimisation mais belle et bien la méthode de base pour envoyer nos infos à OpenGL.

Ces "Vertex Attributs" sont déjà inclus dans l'API, il suffit de lui donner des valeurs (nos coordonnées) puis de l'activer. Voici le prototype de la fonction gérant les Vertex Attributs :

void glVertexAttribPointer(GLuint index, GLuint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer);

  • index : c'est le numéro du tableau, son identifiant.

  • size : c'est le nombre de coordonnées par vertex. En 2D ce sera 2 et en 3D ce sera 3.

  • type : c'est le type de données (float, int, ...).

  • normalized : booléen permettant à OpenGL de normaliser les données (comme les vecteurs).

  • stride : paramètre spécial, on le mettra à zéro tout le temps, nous ne l'utiliserons pas.

  • pointer : c'est le pointeur sur nos données, ici nos coordonnées.

Voyons ce que ça donne avec les données de notre triangle :

glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);

Malheureusement, en l'état, notre tableau est inutilisable, il faut d'abord l'activer avec la fonction :

void glEnableVertexAttribArray(GLuint index);

Le paramètre index étant notre identifiant de tableau, ici ce sera 0.

Bien, maintenant OpenGL sait quels vertices il doit afficher, il ne manque plus qu'à lui dire ... ce qu'il doit faire de ces points. :p

Pour ça, on utilise une fonction (il y en a en fait deux, mais pour le début de ce tutoriel nous n'utiliserons que la première). Cette fonction permet de dire à OpenGL quelle forme afficher avec les points donnés précédemment. Voici le prototype de la fonction :

glDrawArrays(GLenum mode, GLint first, GLsizei count);

  • mode : C'est la forme finale (nous verrons cela juste après).

  • first : C'est l'indice de notre premier vertex à afficher(nous lui donnerons un int).

  • count : C'est le nombre de vertices à afficher depuis first (nous lui donnerons également un int).

Alors Attention ! :

  • Le paramètre first est un indice (comme un indice de tableau), si vous voulez utiliser le premier vertex vous lui donnerez la valeur "0" et pas "1".

  • Pour count, c'est le contraire. Lui il veut le nombre de vertices à utiliser depuis first. Si vous utilisez 4 vertices vous lui donnerez la valeur "4" et pas "3".

Exemple : J'ai 4 vertices et je veux afficher un carré. Le premier vertex sera le vertex 0 (le premier vertex), et la valeur de "count" sera 4 car j'ai besoin du vertex 0 + les 3 vertices qui le suivent.

Passons au paramètre le plus intéressant : mode.

Ce paramètre peut prendre plusieurs formes donc voici les principales :

Valeur | Définition
GL_TRIANGLES | Avec ce mode, chaque groupe de 3 vertices formera un triangle. C'est le mode le plus utilisé.
GL_POLYGON | Tous les vertices s'assemblent pour former un polygone de type convexe (Hexagone, Heptagone, ...).
GL_LINES | Chaque duo de vertices formera une ligne.
GL_TRIANGLE_STRIP | Ici les triangles s'assembleront. Les deux derniers vertices d'un triangle s'assembleront avec un 4ième vertex pour former un nouveau triangle.

Toutes les valeurs possibles ne sont pas représentées mais je vous expose ici les plus utilisées. ;)

Revenons à notre code, nous avons désormais les différents modes d'affichage. Le but de ce chapitre est d'afficher un triangle, notre mode d'affichage sera donc : GL_TRIANGLES, ce qui donne :

glDrawArrays(GL_TRIANGLES, 0, 3);

La valeur "0" pour commencer l'affichage par le premier vertex, et le "3" pour utiliser les 3 vertices dont le premier sera le vertex "0".

Récapitulons tout ça :

// Vertices et coordonnées

float vertices[] = {-0.5, -0.5,   0.0, 0.5,   0.5, -0.5};

// Boucle principale

while(!terminer)

{

    // Gestion des évènements

    ....

    // On remplie puis on active le tableau Vertex Attrib 0

    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);

    glEnableVertexAttribArray(0);

    // On affiche le triangle

    glDrawArrays(GL_TRIANGLES, 0, 3);

    // On désactive le tableau Vertex Attrib puisque l'on en a plus besoin

    glDisableVertexAttribArray(0);

    // Actualisation de la fenêtre

    ....

}

Vous avez dû remarquer la présence d'une nouvelle fonction : glDisableVertexAttribArray. Elle permet de désactiver le tableau Vertex Attrib utilisé, le paramètre est le même que celui de la fonction glEnableVertexAttribArray. On désactive le tableau juste après l'affichage des vertices.

Récapitulons tout avec le code SDL :

#ifdef WIN32

#include <GL/glew.h>

#else

#define GL3_PROTOTYPES 1

#include <GL3/gl3.h>

#endif

#include <SDL2/SDL.h>

#include <iostream>

int main(int argc, char **argv)

{

    // Notre fenêtre

    SDL_Window* fenetre(0);

    SDL_GLContext contexteOpenGL(0);

    SDL_Event evenements;

    bool terminer(false);

    // Initialisation de la SDL

    if(SDL_Init(SDL_INIT_VIDEO) < 0)

    {

        std::cout << "Erreur lors de l'initialisation de la SDL : " << SDL_GetError() << std::endl;

        SDL_Quit();

        return -1;

    }

    // Version d'OpenGL

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);

    // Double Buffer

    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);

    // Création de la fenêtre

    fenetre = SDL_CreateWindow("Test SDL 2.0", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL);

    if(fenetre == 0)

    {

        std::cout << "Erreur lors de la creation de la fenetre : " << SDL_GetError() << std::endl;

        SDL_Quit();

        return -1;

    }

    // Création du contexte OpenGL

    contexteOpenGL = SDL_GL_CreateContext(fenetre);

    if(contexteOpenGL == 0)

    {

        std::cout << SDL_GetError() << std::endl;

        SDL_DestroyWindow(fenetre);

        SDL_Quit();

        return -1;

    }

    #ifdef WIN32

        // On initialise GLEW

        GLenum initialisationGLEW( glewInit() );

        // Si l'initialisation a échouée :

        if(initialisationGLEW != GLEW_OK)

        {

            // On affiche l'erreur grâce à la fonction : glewGetErrorString(GLenum code)

            std::cout << "Erreur d'initialisation de GLEW : " << glewGetErrorString(initialisationGLEW) << std::endl;

            // On quitte la SDL

            SDL_GL_DeleteContext(contexteOpenGL);

            SDL_DestroyWindow(fenetre);

            SDL_Quit();

            return -1;

        }

    #endif

    // Vertices et coordonnées

    float vertices[] = {-0.5, -0.5,   0.0, 0.5,   0.5, -0.5};

    // Boucle principale

    while(!terminer)

    {

        // Gestion des évènements

        SDL_WaitEvent(&evenements);

        if(evenements.window.event == SDL_WINDOWEVENT_CLOSE)

            terminer = true;

        // Nettoyage de l'écran

        glClear(GL_COLOR_BUFFER_BIT);

        // On remplie puis on active le tableau Vertex Attrib 0

        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);

        glEnableVertexAttribArray(0);

        // On affiche le triangle

        glDrawArrays(GL_TRIANGLES, 0, 3);

        // On désactive le tableau Vertex Attrib puisque l'on n'en a plus besoin

        glDisableVertexAttribArray(0);

        // Actualisation de la fenêtre

        SDL_GL_SwapWindow(fenetre);

    }

    // On quitte la SDL

    SDL_GL_DeleteContext(contexteOpenGL);

    SDL_DestroyWindow(fenetre);

    SDL_Quit();

    return 0;

}

Si tout se passe bien, vous devriez avoir une belle fenêtre comme celle-ci :

Afficher plusieurs triangles

Les vertices

Comme nous l'avons vu dans le tableau précédemment, le paramètre GL_TRIANGLES dans la fonction glDrawArrays() permet d'afficher un triangle pour chaque triplet de vertices.
Pour le moment, nous n'utilisons que 3 vertices, donc nous n'avons au final qu'un seul triangle. Or si nous en utilisons 6 nous aurons alors deux triangles.
On peut même aller plus loin et prendre 60, 390, ou 3000 vertices pour en afficher plein ! C'est d'ailleurs ce que font les décors et les personnages dans les jeux-vidéo, ils ne savent utiliser que ça. :p

Enfin, prenons un exemple plus simple avant d'aller aussi loin. Si nous voulons afficher le rendu suivant ... :

... Nous devrons utiliser deux triplets de vertices.

Ce qu'il faut savoir, c'est qu'il ne faut surtout pas utiliser un tableau pour chaque triangle. On perdrait beaucoup trop de temps à tous les envoyer. A la place, nous devons inclure tous les vertices dans un seul et unique tableau :

// Vertices

float vertices[] = {0.0, 0.0,   0.5, 0.0,   0.0, 0.5,          // Triangle 1

                    -0.8, -0.8,   -0.3, -0.8,   -0.8, -0.3};   // Triangle 2

Toutes nos données sont regroupées dans un seul tableau. Ça ne nous fait qu'un seul envoi à faire c'est plus facile à gérer, ça arrange même OpenGL. ;)

L'affichage

Au niveau de l'affichage, le code reste identique à celui du triangle unique. C'est-à-dire qu'il faut utiliser les fonctions :

  • glVertexAttribPointer() : pour donner les vertices à OpenGL

  • glEnableVertexAttribArray() : pour activer le tableau Vertex Attrib

  • glDrawArrays() : pour afficher le tout

La seule différence notable va être la valeur du paramètre count (nombre de vertices à prendre en compte) de la fonction glDrawArrays(). Celui-ci était égal à 3 pour un seul triangle, nous la passerons désormais à 6 pour en afficher deux. L'appel à la fonction ressemblerait donc à ceci :

// Affichage des triangles

glDrawArrays(GL_TRIANGLES, 0, 6);

Ce qui donne le code source de la boucle principale suivant :

// Vertices

float vertices[] = {0.0, 0.0,   0.5, 0.0,   0.0, 0.5,          // Triangle 1

                    -0.8, -0.8,   -0.3, -0.8,   -0.8, -0.3};   // Triangle 2

// Boucle principale

while(!terminer)

{

    // Gestion des évènements

    SDL_WaitEvent(&evenements);

    if(evenements.window.event == SDL_WINDOWEVENT_CLOSE)

        terminer = true;

    // Nettoyage de l'écran

    glClear(GL_COLOR_BUFFER_BIT);

    // On remplie puis on active le tableau Vertex Attrib 0

    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);

    glEnableVertexAttribArray(0);

    // On affiche des triangles

    glDrawArrays(GL_TRIANGLES, 0, 6);

    // On désactive le tableau Vertex Attrib puisque l'on n'en a plus besoin

    glDisableVertexAttribArray(0);

    // Actualisation de la fenêtre

    SDL_GL_SwapWindow(fenetre);

}

Si vous compilez ce code, vous devriez obtenir :

Le tour est joué. :)

La classe SceneOpenGL

Je ne sais pas si vous l'avez remarqué, mais depuis le début du tutoriel nous n'avons pas codé une seule classe. À vrai dire c'est normal, nous n'en avions pas vraiment besoin jusqu'ici.
Cependant nos programmes vont commencer à se complexifier donc autant prendre les bonnes habitudes dès maintenant.

La classe SceneOpenGL

Le header

Pour le moment, nous n'aurons besoin que d'une seule classe dans nos programmes de test. Elle devra être capable de remplir plusieurs rôles :

  • Créer la fenêtre SDL et le contexte OpenGL.

  • Initialiser tous les objets d'une scène (personnages, caisses, sol, ... Nous verrons cela un peu plus tard ;) ).

  • Gérer l'interaction entre les objets tout au long du programme.

Pour remplir ces objetifs, nous allons créer plusieurs méthodes, mais avant cela nous devons déclarer la classe C++ qui s'occupera de tout ça. Cette classe s'appellera : SceneOpenGL. Commençons donc par créer deux fichiers SceneOpenGL.h et SceneOpenGL.cpp.

Une classe en C++ est composée de méthodes et d'attributs. Pour les méthodes, on voit déjà à peu près ce que l'on va faire. En revanche, on ne sait pas encore de quels attributs nous aurons besoin.

Si on regarde le début du code pour afficher le triangle blanc, nous voyons trois variables importantes :

// Variables

SDL_Window* fenetre(0);

SDL_GLContext contexteOpenGL(0);

SDL_Event evenements;

Le booléen terminer ne sera pas utilisé en tant qu'attribut mais en temps que simple variable dans une méthode.

On voit dans cette liste 3 variables correspondant :

  • À la fenêtre

  • Au contexte OpenGL

  • Aux évènements SDL

On rajoutera le préfixe "m_" à chacune de ces variables pour bien différencier les attributs des variables normales.

Ces 3 variables deviendront les attributs de notre classe. On en rajoutera même trois supplémentaires qui correspondront :

  • Au titre de la fenêtre.

  • À sa largeur

  • À sa hauteur

Si on résume tout ça, nous avons une classe SceneOpenGL avec 6 attributs. Le header ressemblera donc à ceci :

#ifndef DEF_SCENEOPENGL

#define DEF_SCENEOPENGL

#include <string>

class SceneOpenGL

{

    public:

    SceneOpenGL();

    ~SceneOpenGL();

    private:

    std::string m_titreFenetre;

    int m_largeurFenetre;

    int m_hauteurFenetre;

    SDL_Window* m_fenetre;

    SDL_GLContext m_contexteOpenGL;

    SDL_Event m_evenements;

};

#endif

Et voici donc le squelette de tous nos futurs programmes. :D

Bon pour le moment c'est un peu vide, on va habiller un peu tout ça. On va notamment ajouter deux éléments. Premièrement, il faut ajouter tous les includes concernant la SDL et OpenGL :

// Includes

#ifdef WIN32

#include <GL/glew.h>

#else

#define GL3_PROTOTYPES 1

#include <GL3/gl3.h>

#endif

#include <SDL2/SDL.h>

#include <iostream>

Deuxièmement, il faut modifier le constructeur de la classe pour prendre en compte les paramètres de création de la fenêtre (titre, largeur et hauteur) :

SceneOpenGL(std::string titreFenetre, int largeurFenetre, int hauteurFenetre);

Pour le moment, rien de bien compliqué.

Les méthodes

Si on reprend la liste des objectifs de la classe, on retrouve trois points importants :

  • Créer la fenêtre SDL et le contexte OpenGL.

  • Initialiser tous les objets d'une scène (personnages, caisses, sol, ...).

  • Gérer l'interaction entre les objets tout au long du programme.

Nous allons créer une méthode pour chaque point de cette liste.

La première méthode consistera donc à initialiser la fenêtre et le contexte OpenGL dans lequel nous allons évoluer :

bool initialiserFenetre();

Elle renverra un booléen pour confirmer ou non la création de la fenêtre. Nous mettrons à l'intérieur tout le code permettant de générer la fenêtre.

Pour le deuxième point, nous devrons initialiser tout ce qui concerne OpenGL (mis à part le contexte vu qu'il est créé juste avant). Pour le moment, nous n'avons que la librairie GLEW à initialiser.

Généralement, la fonction qui s'occupe d'initialiser OpenGL s'appelle initGL(), nous appellerons donc notre méthode de la même façon :

bool initGL();

Comme la méthode précédente, elle renverra un booléen pour savoir si l'initialisation s'est bien passée.

Pour le troisième et dernier point, nous devons gérer la boucle principale du programme. Nous créerons donc une méthode bouclePrincipale() qui s'occupera de gérer tout ça :

void bouclePrincipale();

Elle ne renverra aucune valeur.

Résumé du Header

Si on met tout ce que l'on vient de voir dans le header, on a : une classe SceneOpenGL, des includes pour gérer la SDL et OpenGL, 6 attributs et 3 méthodes :

#ifndef DEF_SCENEOPENGL

#define DEF_SCENEOPENGL

// Includes

#ifdef WIN32

#include <GL/glew.h>

#else

#define GL3_PROTOTYPES 1

#include <GL3/gl3.h>

#endif

#include <SDL2/SDL.h>

#include <iostream>

#include <string>

// Classe

class SceneOpenGL

{

    public:

    SceneOpenGL(std::string titreFenetre, int largeurFenetre, int hauteurFenetre);

    ~SceneOpenGL();

    bool initialiserFenetre();

    bool initGL();

    void bouclePrincipale();

    private:

    std::string m_titreFenetre;

    int m_largeurFenetre;

    int m_hauteurFenetre;

    SDL_Window* m_fenetre;

    SDL_GLContext m_contexteOpenGL;

    SDL_Event m_evenements;

};

#endif

Implémentation de la classe

Dans cette dernière sous-partie, on va se reposer un peu. En effet, on a déjà tout codé avant, on n'a plus qu'à jouer au puzzle en coupant notre code et en mettant les bons morceaux au bon endroit. :p

Constructeur et Destructeur

Pour le constructeur rien de plus simple, on initialise nos attributs sans oublier de passer les 3 paramètres du constructeur concernant la fenêtre :

SceneOpenGL::SceneOpenGL(std::string titreFenetre, int largeurFenetre, int hauteurFenetre) : m_titreFenetre(titreFenetre), m_largeurFenetre(largeurFenetre),

                                                                                m_hauteurFenetre(hauteurFenetre), m_fenetre(0), m_contexteOpenGL(0)

{

}

Pour le destructeur, on détruit simplement le contexte et la fenêtre, puis on quitte la librairie SDL :

SceneOpenGL::~SceneOpenGL()

{

    SDL_GL_DeleteContext(m_contexteOpenGL);

    SDL_DestroyWindow(m_fenetre);

    SDL_Quit();

}

N'oubliez pas de rajouter le prefixe "m_" aux attributs quand vous copiez le code.

Les méthodes initialiserFenetre() et initGL()

Ici on sait déjà ce que l'on va mettre. On va implémenter le code permettant de créer la fenêtre et le contexte OpenGL, nous connaissons ce code depuis le chapitre précédent. Pour migrer celui-ci on va :

  • Prendre tout le code gérant l'initialisation de la SDL et du contexte.

  • Remplacer les deux occurrences de fenetre par "m_fenetre" et de contexteOpenGL par "m_contexteOpenGL".

  • Remplacer les valeurs retournées par "true" pour 0 et "false" pour -1.

  • Remplacer le paramètre title de la fonction SDL_CreateWindow() par la chaine de caractères de l'attribut m_titreFenetre soit m_titreFenetre.c_str().

Encore une fois rien de bien compliqué.

En code, ça nous donne ceci :

bool SceneOpenGL::initialiserFenetre()

{

    // Initialisation de la SDL

    if(SDL_Init(SDL_INIT_VIDEO) < 0)

    {

        std::cout << "Erreur lors de l'initialisation de la SDL : " << SDL_GetError() << std::endl;

        SDL_Quit();

        return false;

    }

    // Version d'OpenGL

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);

    // Double Buffer

    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);

    // Création de la fenêtre

    m_fenetre = SDL_CreateWindow(m_titreFenetre.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, m_largeurFenetre, m_hauteurFenetre, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL);

    if(m_fenetre == 0)

    {

        std::cout << "Erreur lors de la creation de la fenetre : " << SDL_GetError() << std::endl;

        SDL_Quit();

        return false;

    }

    // Création du contexte OpenGL

    m_contexteOpenGL = SDL_GL_CreateContext(m_fenetre);

    if(m_contexteOpenGL == 0)

    {

        std::cout << SDL_GetError() << std::endl;

        SDL_DestroyWindow(m_fenetre);

        SDL_Quit();

        return false;

    }

    return true;

}

On passe maintenant à la méthode initGL(). Pour le moment, nous n'avons pas grand chose à mettre à l’intérieur mise à part l'initialisation de la librairie GLEW pour Windows.

Et comme précédemment, il va falloir modifier le nom des attributs fenetre et contexteOpenGL en leur ajoutant le prefix "m_". Nous rajouterons également deux return dans la méthode :

  • Un si l'initialisation échoue (donc return false).

  • Et l'autre pour indiquer que l'initialisation s'est bien déroulée (return true).

bool SceneOpenGL::initGL()

{

    #ifdef WIN32

        // On initialise GLEW

        GLenum initialisationGLEW( glewInit() );

        // Si l'initialisation a échoué :

        if(initialisationGLEW != GLEW_OK)

        {

            // On affiche l'erreur grâce à la fonction : glewGetErrorString(GLenum code)

            std::cout << "Erreur d'initialisation de GLEW : " << glewGetErrorString(initialisationGLEW) << std::endl;

            // On quitte la SDL

            SDL_GL_DeleteContext(m_contexteOpenGL);

            SDL_DestroyWindow(m_fenetre);

            SDL_Quit();

            return false;

        }

    #endif

    // Tout s'est bien passé, on retourne true

    return true;

}
La méthode bouclePrincipale()

Allez on passe à la dernière méthode, c'est dans celle-ci que va se passer la quasi totalité du programme.

Dans cette méthode, on commence par déclarer le booléen terminer que vous devez déjà connaitre. :p On ne l'a pas déclaré en temps qu'attribut car il ne sert que dans la boucle while.

Après ce booléen, on va en profiter pour déclarer notre fameux tableau de vertices. Dans le futur, nous ferons des objets spécialement dédiés pour afficher nos modèles. Mais pour le moment, nous n'avons que de simples vertices à afficher, on peut donc se passer de classe.

Voici donc le début de la méthode bouclePrincipale() :

void SceneOpenGL::bouclePrincipale()

{

    // Variables

    bool terminer(false);

    float vertices[] = {-0.5, -0.5,   0.0, 0.5,   0.5, -0.5};

}

Pour le reste de la méthode, il suffit simplement d'ajouter la boucle while que nous avons déjà codé. :)

Encore une fois, je me répète mais n'oubliez pas d'ajouter le préfixe "m_" aux attributs evenements et fenetre quand vous copiez le code :

void SceneOpenGL::bouclePrincipale()

{

    // Variables

    bool terminer(false);

    float vertices[] = {-0.5, -0.5,   0.0, 0.5,   0.5, -0.5};

    // Boucle principale

    while(!terminer)

    {

        // Gestion des évènements

        SDL_WaitEvent(&m_evenements);

        if(m_evenements.window.event == SDL_WINDOWEVENT_CLOSE)

            terminer = 1;

        // Nettoyage de l'écran

        glClear(GL_COLOR_BUFFER_BIT);

        // On remplie puis on active le tableau Vertex Attrib 0

        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);

        glEnableVertexAttribArray(0);

        // On affiche le triangle

        glDrawArrays(GL_TRIANGLES, 0, 3);

        // On désactive le tableau Vertex Attrib puisque l'on n'en a plus besoin

        glDisableVertexAttribArray(0);

        // Actualisation de la fenêtre

        SDL_GL_SwapWindow(m_fenetre);

    }

}
Le fichier main.cpp

Comme d'habitude en C++, la fonction main() sera la fonction la moins chargée du programme. Jusqu'à maintenant, nous codions tout à l'intérieur de cette fonction, ce qui la rendait un peu illisible.

Maintenant, nous allons juste déclarer un objet, l'initialiser et lancer sa boucle principale. Pour ça, il faut inclure le header de la classe SceneOpenGL dans le fichier main.cpp :

#include "SceneOpenGL.h"

Ensuite, on va déclarer un objet de type SceneOpenGL avec les bons paramètres :

int main(int argc, char **argv)

{

    // Création de la sène

    SceneOpenGL scene("Chapitre 3", 800, 600);

}

Maintenant, il faut appeler les deux méthodes qui permettent d'initialiser le programme correctement à savoir initialiserFenetre() et initGL(). De plus, il faut vérifier que ces méthodes retournent bien le booléen true et pas false. Si au moins une des deux initialisions échoue, alors on quitte le programme :

// Initialisation de la scène

if(scene.initialiserFenetre() == false)

    return -1;

if(scene.initGL() == false)

    return -1;

Enfin, on appelle la méthode bouclePrincipale() pour lancer la scène OpenGL :

// Boucle Principale

scene.bouclePrincipale();

N'oublions pas le return 0 lorsque l'on quittera le programme :

// Fin du programme

return 0;

Si on résume tout ça :

#include "SceneOpenGL.h"

int main(int argc, char **argv)

{

    // Création de la sène

    SceneOpenGL scene("Chapitre 3", 800, 600);

    // Initialisation de la scène

    if(scene.initialiserFenetre() == false)

    return -1;

    if(scene.initGL() == false)

    return -1;

    // Boucle Principale

    scene.bouclePrincipale();

    // Fin du programme

    return 0;

}

Et voilà ! Il ne vous reste plus qu'à compiler tout ça. Vous devriez avoir le même résultat qu'au dessus mais vous avez codée proprement une classe en C++ avec du code OpenGL à l'intérieur.

Nous ferons plein de classes au fur et à mesure de ce tuto, vous allez vite en prendre l'habitude. ;)

Télécharger : Code Source C++ du Chapitre 3

Exercices

Énoncés

Tout au long de ce tutoriel, je vous ferai faire quelques petits exercices pour que vous appliquiez ce que nous aurons vu dans les chapitres. Ce seront des exercices assez simples, il n'y aura rien de farfelu je vous rassure. Évidemment, vous avez le droit de vous aider du cours, je ne vous demande pas de tout retenir d'un coup à chaque fois. D'ailleurs, les solutions sont fournies juste après les énoncés, ne les regardez pas avant sinon ça n'a aucun intérêt. ;)

Exercice 1 : Avec les notions vues dans ce chapitre, affichez un triangle ayant les coordonnées présentes ci-dessous. Vous n'avez besoin de modifier que les vertices par rapport aux exemples du cours, inutile de toucher au reste.

Exercice 2 : Reprenez le même triangle que l'exercice précédent mais modifiez ses vertices pour l'inverser :

Exercice 3 : On passe un cran au-dessus maintenant. Je vous demande d'afficher la forme suivante en utilisant deux triangles distincts (donc pas avec le paramètre GL_TRIANGLE_STRIP) :

Solutions

Exercice 1 :

La seule chose à modifier ici c'est le tableau de vertices. L’énoncé demandait un triangle avec des coordonnées spécifiques, le tableau de valeurs ressemble donc à ceci :

float vertices[] = {-0.5, 0.0,   0.5, -0.5,   0.5, 0.5};

Vous remarquerez que j'ai délimité les coordonnées de façon à bien différencier les vertices. ;)

Je devrais en théorie m’arrêter là pour la correction, mais pour ceux qui le souhaitent je donne en détail le code source de l'affichage des vertices. Celui-ci ne change absolument pas par rapport aux exemples du cours, mais si ça peut vous aider à comprendre un peu mieux je vais le ré-expliquer rapidement.

Premièrement, on reprend le code de la boucle principale avec sa gestion d'évènements et son actualisation de fenêtre :

// Boucle principale

while(!terminer)

{

    // Gestion des évènements

    SDL_WaitEvent(&m_evenements);

    if(m_evenements.window.event == SDL_WINDOWEVENT_CLOSE)

        terminer = true;

    // Actualisation de la fenêtre

    SDL_GL_SwapWindow(m_fenetre);

}

Le code d'affichage consiste simplement à appeler les fonctions :

  • glClear() pour nettoyer ce qui était présent avant

  • glVertexAttribPointer() pour donner les vertices à OpenGL

  • glDrawArrays() pour les afficher

// Nettoyage de l'écran

glClear(GL_COLOR_BUFFER_BIT);

// Tableau Vertex Attrib 0 pour envoyer les vertices

glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);

// Affichage du triangle

glDrawArrays(GL_TRIANGLES, 0, 3);

Il ne faut bien sûr pas oublier d'activer le tableau Vertex Attrib au moment d'envoyer les vertices, puis de le désactiver quand on n'en a plus besoin :

// Nettoyage de l'écran

glClear(GL_COLOR_BUFFER_BIT);

// Tableau Vertex Attrib 0 pour envoyer les vertices

glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);

// Activation du tableau Vertex Attrib

glEnableVertexAttribArray(0);

// Affichage du triangle

glDrawArrays(GL_TRIANGLES, 0, 3);

// Désactivation du tableau Vertex Attrib

glDisableVertexAttribArray(0);

Ce qui donne le code source suivant pour la boucle principale :

// Vertices

float vertices[] = {-0.5, 0.0,   0.5, -0.5,   0.5, 0.5};

// Boucle principale

while(!terminer)

{

    // Gestion des évènements

    SDL_WaitEvent(&m_evenements);

    if(m_evenements.window.event == SDL_WINDOWEVENT_CLOSE)

        terminer = true;

    // Nettoyage de l'écran

    glClear(GL_COLOR_BUFFER_BIT);

    // Tableau Vertex Attrib 0 pour envoyer les vertices

    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);

    // Activation du tableau Vertex Attrib

    glEnableVertexAttribArray(0);

    // Affichage du triangle

    glDrawArrays(GL_TRIANGLES, 0, 3);

    // Désactivation du tableau Vertex Attrib

    glDisableVertexAttribArray(0);

    // Actualisation de la fenêtre

    SDL_GL_SwapWindow(m_fenetre);

}

Exercice terminé. :)

Exercice 2 :

Pour cet exercice, il suffit juste de modifier notre tableau de vertices. On prend donc notre schéma pour en tirer les coordonnées suivantes :

// Vertices

float vertices[] = {0.5, 0.0,   -0.5, -0.5,   -0.5, 0.5};

Exercice 3 :

Cet exercice est un poil plus compliqué que les deux autres mais il n'y a rien de dur si on regarde dans le fond. :)

La forme demandée est évidemment constituée de deux triangles, il faut donc commencer par déclarer deux tableaux de vertices qui contiendront les coordonnées de ces deux triangles :

// Vertices

float vertices[] = {-0.5, 0.0,   0.0, 1.0,   0.5, 0.0,          // Triangle 1

                    -0.5, 0.0,   0.0, -1.0,   0.5, 0.0};        // Triangle 2

Il faut évidemment modifier l'appel à la fonction glDrawArrays() pour qu'elle prenne en compte les 6 vertices et non uniquement 3 :

// Affichage du triangle

glDrawArrays(GL_TRIANGLES, 0, 6);

Le reste du code ne change pas.

Ah, nous avons enfin pu afficher quelque chose (même si ce n'est un simple triangle), nous avons enfin pu faire bosser notre carte graphique qui commençait légèrement à s'endormir. :p

Rappelez-vous bien de ce que nous avons vu ici, nous réutiliserons tout ça dans les futurs chapitres.


Introduction aux shaders

Ah c'est un vaste sujet que nous allons aborder aujourd'hui :) . Tellement vaste que nous allons le découper en 4 chapitres dont le premier sera celui-ci. Nous verrons les trois autres beaucoup plus tard, vous comprendrez pourquoi. Bref, il est temps de découvrir ce que sont ces mystérieux Shaders. :magicien:

Qu'est-ce qu'un Shader ?

Introduction

Commençons par un peu de théorie. ;)

Comme vous le savez, OpenGL est une librairie qui tire sa puissance de la carte graphique. Or lorsque nous programmons, nous n'avons pas accès à cette carte mais uniquement au processeur et à la RAM. Comment fait notre programme pour l'exploiter me direz-vous ?

En réalité, OpenGL va bidouiller pas mal de choses dans la carte lorsque nous appelons certaines fonctions comme glDrawArrays(). Voyons d’ailleurs ce qui se passe entre le moment où l’on appelle cette fonction et le moment où la scène s’affiche à l’écran :

  • Définition des coordonnées : Dans un premier temps, nous définissons les coordonnées de vertices. Ça on sait le faire grâce à la fonction glVertexAttribPointer().

  • Vertex Shader : Nous verrons cette étape dans quelques minutes. ;)

  • Pixelisation des triangles : C'est le moment où une forme géométrique est convertie en pixels.

  • Fragment Shader : Nous verrons également cela un peu plus loin.

  • Test de profondeur : Test permettant de savoir s'il faut afficher tel ou tel pixel. Cette notion sera développée un peu plus tard.

  • Affichage : C'est la sortie de la carte graphique, la plupart du temps ce sera notre écran.

Cette suite d’opérations constitue ce que l’on appelle: le pipeline 3D. Les parties qui nous intéressent dans ce chapitre sont : le Vertex Shader et le Fragment Shader.

Euh qu'est-ce que c'est que ça ?

Un shader est simplement un programme exécuté non pas par le processeur mais par la carte graphique. Il en existe deux types (enfin 3 mais seuls 2 sont réellement importants) :

  • Vertex Shader : C’est l’étape qui va nous permettre soit de valider les coordonnées de nos sommets, soit de les modifier. Cette étape prend un vertex à part pour travailler dessus. S’il y a 3 vertices (pour un triangle) alors le vertex shader sera exécuté 3 fois.

  • Fragment Shader (parfois appelé Pixel Shader) : C’est l’étape qui va définir la couleur de chaque pixel de la forme délimitée par les vertices. Par exemple, si vous avez défini un rectangle de 100 pixels par 50 pixels, alors le fragment shader se chargera de définir la couleur des 5000 pixels composant le rectangle.

Ne vous inquiétez pas, 5000 pixels c’est une promenade de santé pour la carte graphique :p . Elle est conçue pour faire ce genre d’opérations à répétition.

A quoi servent les shaders ?

Étant donné la complexité de cette notion, nous ne devrions étudier les shaders que beaucoup plus tard dans le tutoriel. Dans les anciennes versions d'OpenGL, ils étaient optionnels, c'était l'API elle-même qui se chargeait d'effectuer ces opérations. Mais avec la version 3.0, le groupe Khronos a voulu introduire une nouvelle philosophie : le tout shader. En clair, ils nous imposent leur utilisation.

Euh ... Pourquoi nous imposer un truc aussi compliqué alors que c'était géré automatiquement avant ?

Ce qu'il faut comprendre, c'est que les jeux-vidéo ont beaucoup évolué depuis qu'OpenGL existe. Il y a quelques années, il était plus simple de laisser l'API faire tout le boulot, les jeux ne prenaient pas énormément de ressources et surtout il y avait moins de choses à calculer. Mais de nos jours, les graphismes, le réalisme et la vitesse sont devenus les principales préoccupations des développeurs (et des joueurs ;) ). L'API n'est plus capable de gérer tout ça rapidement car elle le fait avec ses vielles méthodes (trop lentes pour les jeux d'aujourd'hui).

L'avantage d'une gestion personnalisée est que l'on peut faire ce que l'on veut et surtout on peut y mettre uniquement ce dont on a besoin, ce qui peut nous faire gagner parfois pas mal de vitesse.

En théorie lors du chapitre précédent, nous n'aurions pas dû afficher un triangle sans utiliser les shaders. Cependant, OpenGL nous autorise à afficher des petites formes blanches avec le repère de base sans avoir à les utiliser. Avouez tout de même qu'un jeu comme ça ce n'est pas passionnant. A moins de faire un Pong en 2D mais je pense honnêtement que ce n'est pas votre but. ;)

Exemples de Shaders

Ah c'est certainement la partie qui va le plus vous intéresser. :p

Les shaders ne sont pas uniquement des étapes embêtantes qui sont là pour vous compliquer la vie :colere2: . Leur première utilité vient du fait qu'ils permettent d'afficher nos objets dans un monde 2D ou 3D. En gros, voila ce qui se passe lorsque nous voulons afficher un objet :

  • On définit ses coordonnées dans l'espace.

  • L'objet est ensuite passé au Vertex Shader (n'oubliez pas que c'est un programme exécuté par la carte graphique).

  • Puis au Fragment Shader (c'est un programme aussi).

  • Il est maintenant prêt à être affiché.

Ça c'est la première utilité, passons maintenant à la deuxième qui est certainement la plus importante (et la plus intéressante :p ) : ils permettent de faire tous les effets 3D magnifiques que vous voyez dans un jeu-vidéo tels que : les lumières, l'eau, les ombres, les explosions ... Ce sont les shaders qui font d'un jeu un jeu plus réaliste.

Alors ne vous emballez pas, ce n'est pas maintenant que nous allons apprendre à faire tous ces effets mais nous y viendrons. ;)

Le principal problème avec les shaders est que lorsque l'on débute dans la programmation 3D, il est très difficile d'apprendre à les utiliser du fait de leur complexité. C'est pour cela que dans un premier temps, je vais vous fournir le code source pour la création des shaders. Nous consacrerons trois chapitres entiers pour apprendre à les gérer une fois que vous serez plus habitués avec OpenGL.

En bref ce qu’il faut retenir c’est que :

  • Un shader est un programme exécuté par la carte graphique.

  • Il en existe de deux types : les vertex et les fragment.

  • Chaque chose que nous voulons afficher passera d'abord entre les mains de ces deux shaders.

Utilisation des shaders

Passons maintenant à la partie programmation. :magicien:

Pour commencer, je vais vous demander de télécharger l’archive ci-dessous (pour Linux et Windows), elle contient un fichier « Shader.h », « Shader.cpp » et un dossier « Shaders » contenant plein de petits fichiers. Vous placerez tout ça dans le répertoire de chaque projet que vous ferez (donc dans chaque chapitre que nous ferons).

Télécharger : Code Source C++ Shaders - Windows & Linux

Une fois les fichiers ajoutés à votre dossier, il vous suffit simplement d’ajouter le header « Shader.h » et le fichier source « Shader.cpp » à votre projet.

N'oubliez pas d'inclure le header quand vous en avez besoin.

Comme vous le savez maintenant, un shader est un programme, différent d’un programme normal certes mais un programme quand même. Il doit donc respecter plusieurs règles :

  • Un code source

  • Une compilation

Nous ne verrons pas ces deux étapes maintenant mais sachez au moins que ce n’est pas si différent d'un programme normal. Les codes sources sont dans le dossier « Shaders » que vous devriez avoir placé dans le répertoire de votre projet.

Avant toute chose, pour pouvoir utiliser les shaders il va falloir utiliser la classe Shader dont voici le constructeur :

Shader(std::string vertexSource, std::string fragmentSource);

  • vertexSource : C’est le chemin du code source de notre Vertex Shader.

  • fragmentSource : C’est le chemin du code source de notre Fragment Shader.

Alors attention, appeler le constructeur ne suffit pas. Si vous n'appelez que lui, votre shader ne sera pas exploitable. Pour le rendre exploitable, il faut utiliser la méthode charger() qui permet en gros de lire les fichiers sources, de les compiler, ...

bool charger();

Cette méthode retourne un booléen pour savoir si la création du shader s'est bien passée.

On ne va pas vérifier la valeur retournée ici mais vous êtes libre de le faire si vous le voulez. Si un shader ne réussit pas à se charger, le programme ne va pas s'arrêter pour autant. OpenGL continuera de s'exécuter comme si le shader n'existait pas.

En bref, voici un petit exemple de création de shader :

// Création du shader

Shader shaderBasique("Shaders/basique_2D.vert", "Shaders/basique.frag");

shaderBasique.charger();

// Début de la bouble principale

while(!terminer)

{

    // Utilisation

}

Bien, passons à l'utilisation qui est ma foi assez simple puisque nous n'utilisons qu'une seule fonction :

glUseProgram(GLuint program) ;

Attention, cette fonction commence par le prefixe "gl", ce n'est donc pas une méthode de la classe Shader.

Elle prend un paramètre : l'ID d'une certain "program", nous lui donnerons l'attribut : " m_programID " de la classe Shader grâce à la méthode :

GLuint getProgramID() const;

Ne vous posez pas de question sur ça pour le moment ;)

Cette fonction a deux utilités :

  • Lorsqu'on lui donne l'attribut "m_programID", OpenGL va comprendre "Je prends le shader que tu me donnes pour l'utiliser dans mon pipeline".

  • Une fois qu'on a affiché ce qu'on voulait afficher, on va faire comprendre à OpenGL de ne plus utiliser le shader. Dans ce cas, le paramètre ne sera pas "m_programID" mais nous lui donnerons la valeur 0.

Cette fonction se place juste avant glDrawArrays() lorsque nous voulons activer notre shader, puis après avec le paramètre 0 pour le désactiver :

// Activation du shader

glUseProgram(shaderBasique.getProgramID());

    // Affichage du triangle

    glDrawArrays(GL_TRIANGLES, 0, 3);

// Désactivation du shader

glUseProgram(0);

Je vous conseille d'indenter le code qui se trouve entre les appels de la fonction glUseProgram(). Vous verrez que nous mettrons de plus en plus de chose à l'intérieur, l'indentation rendra le tout plus lisible.

Simple non ? :-° D'ailleurs on peut même intégrer, entre ces deux appels de la fonction glUseProgram(), le code relatif à l'envoi des vertices au tableau Vertex Attrib. En général, on fait cela pour bien différencier le code d'affichage du reste du programme, ce qui donnerait pour nous :

// Activation du shader

glUseProgram(shaderBasique.getProgramID());

    // On remplie puis on active le tableau Vertex Attrib 0

    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);

    glEnableVertexAttribArray(0);

    // Affichage du triangle

    glDrawArrays(GL_TRIANGLES, 0, 3);

    // On désactive le tableau Vertex Attrib puisque l'on n'en a plus besoin

    glDisableVertexAttribArray(0);

// Désactivation du shader

glUseProgram(0);

Dans le chapitre précédent, nous nous sommes permis de ne pas utiliser ces fameux shaders. Mais comme je vous l'ai dit tout à l'heure, on ne peut plus continuer ainsi. Reprenons notre ancien code pour y ajouter les fonctions que nous venons de voir :

void SceneOpenGL::bouclePrincipale()

{

    // Variables

    bool terminer(false);

    float vertices[] = {-0.5, -0.5,   0.0, 0.5,   0.5, -0.5};

    Shader shaderBasique("Shaders/basique_2D.vert", "Shaders/basique.frag");

    shaderBasique.charger();

    // Boucle principale

    while(!terminer)

    {

        // Gestion des évènements

        SDL_WaitEvent(&m_evenements);

        if(m_evenements.window.event == SDL_WINDOWEVENT_CLOSE)

            terminer = true;

        // Nettoyage de l'écran

        glClear(GL_COLOR_BUFFER_BIT);

        // Activation du shader

        glUseProgram(shaderBasique.getProgramID());

            // On remplie puis on active le tableau Vertex Attrib 0

            glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);

            glEnableVertexAttribArray(0);

            // Affichage du triangle

            glDrawArrays(GL_TRIANGLES, 0, 3);

            // On désactive le tableau Vertex Attrib puisque l'on n'en a plus besoin

            glDisableVertexAttribArray(0);

        // Désactivation du shader

        glUseProgram(0);

        // Actualisation de la fenêtre

        SDL_GL_SwapWindow(m_fenetre);

    }

}

Vous devriez vous retrouver avec une fenêtre comme celle-la :

Quoi mais c’est nul ! C’est la même chose que dans le chapitre précédent mais en plus compliqué.

Oui c'est la même chose mais c'est comme cela que les choses doivent être faites ;). Pour le moment vous ne voyez pas trop l'intérêt d'utiliser les shaders mais vous allez vite voir que c'est indispensable.

D'ailleurs pourquoi attendons-nous ? Voyons dès maintenant ce qu'ils ont à nous offrir. :magicien:

Un peu de couleurs

Un peu de couleurs

Il est maintenant temps de faire passer notre triangle à la couleur, je le trouve un peu trop pâlot. :p

Comment fonctionnent les couleurs avec OpenGL ?

La réponse est simple, vous savez qu'avec la SDL, pour créer un rectangle coloré il fallait utiliser cette fonction :

SDL_MapRGB(SDL_Surface *surface, Uint8 red, Uint8 green, Uint8 blue);

Les trois paramètres étaient les composantes RGB (Rouge, Vert, Bleu). Il suffisait de combiner ces trois couleurs pour en former une seule au final. Bonne nouvelle, avec OpenGL c'est pareil. Pour fabriquer une couleur vous devez donner :

  • Une quantité de rouge

  • Une quantité de vert

  • Une quantité de bleu

Si vous voulez faire des tests pour obtenir différentes couleurs, essayez la palette de couleurs Windows (ou équivalent sur Linux) :

Il y a deux façons de représenter les couleurs :

  • Soit avec des valeurs comprises entre 0 et 255 (la plus compréhensible)

  • Soit avec des valeurs comprises entre 0 et 1 (un peu difficile d'imaginer une couleur entre 0 et 1)

Malheureusement pour nous, nous allons devoir utiliser la seconde méthode. Mais pas de panique, nous allons utiliser une petite combine pour utiliser la première.

Jauger une couleur entre 0 et 255 est plus facile à comprendre, pour pouvoir utiliser cette méthode nous allons diviser la valeur de la couleur (par exemple : 128) par 255. De cette façon on se retrouve avec une valeur comprise entre 0 et 1 :

float rouge = 128.0/255.0; // = 0.50

float vert =  204.0/255.0; // = 0.80

float bleu =  36.0/255.0;  // = 0.141

La quantité de rouge sera de 128, le vert de 204 et le bleu de 36. De plus faites attention, nous travaillons avec des float donc n'oubliez pas de préciser les décimales même s'il n'y a en pas.

Lorsque vous voulez mettre une quantité au maximum (255), il est inutile de faire 255.0/255.0, mettez simplement 1.0. Même chose pour une quantité nulle (0.0)

Au niveau du shader, on va changer le shader basique par le shader couleur2D car ce premier ne faisait qu'afficher ce que nous lui donnions en blanc. Maintenant que nous voulons de la couleur, il faut charger un autre shader gérer la couleur :

// Shader

Shader shaderCouleur("Shaders/couleur2D.vert", "Shaders/couleur2D.frag");

shaderCouleur.charger();

Vu que nous avons changé le nom du shader ( shaderCouleur ), il faut effectuer le même changement de nom lorsque l'on récupère le programID :

// Activation du shader

glUseProgram(shaderCouleur.getProgramID());

    // Envoi des données et affichage

    ....

// Désactivation du shader

glUseProgram(0);

Au niveau du code d'affichage, vous connaissez déjà presque tout. Les fonctions utilisées sont les mêmes que celles des coordonnées de vertex. Les couleurs se gèrent également de la même façon : on place la valeur de chaque couleur (RGB) les unes à la suite des autres dans un tableau.

Le seul changement sera le numéro du tableau, au lieu de placer nos couleurs dans le tableau 0 nous les placerons dans le tableau 1. Le premier tableau (indice 0) servira à stocker tous nos vertices et le deuxième tableau (indice 1) servira à stocker nos couleurs.

// On définie les couleurs

float couleurs[] = {0.0, 204.0 / 255.0, 1.0,    0.0, 204.0 / 255.0, 1.0,    0.0, 204.0 / 255.0, 1.0};

while(...)

{

    ....

    // Activation du shader

    glUseProgram(shaderCouleur.getProgramID());

        // Envoi des vertices

        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);

        glEnableVertexAttribArray(0);

        // On rentre les couleurs dans le tableau Vertex Attrib 1

        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, couleurs);

        glEnableVertexAttribArray(1);

        ....

    // Désactivation du shader

    glUseProgram(0);

    ....

}

Faites attention au paramètre size du tableau Vertex Attrib, ici nous lui donnons 3 car une couleur est composée de 3 couleurs.

Voyons ce que donne toutes ces petites modifications sur un exemple concret :

void SceneOpenGL::bouclePrincipale()

{

    // Variables

    bool terminer(false);

    float vertices[] = {-0.5, -0.5,   0.0, 0.5,   0.5, -0.5};

    float couleurs[] = {0.0, 204.0 / 255.0, 1.0,    0.0, 204.0 / 255.0, 1.0,    0.0, 204.0 / 255.0, 1.0};

    // Shader

    Shader shaderCouleur("Shaders/couleur2D.vert", "Shaders/couleur2D.frag");

    shaderCouleur.charger();

    // Boucle principale

    while(!terminer)

    {

        // Gestion des évènements

        SDL_WaitEvent(&m_evenements);

        if(m_evenements.window.event == SDL_WINDOWEVENT_CLOSE)

            terminer = true;

        // Nettoyage de l'écran

        glClear(GL_COLOR_BUFFER_BIT);

        // Activation du shader

        glUseProgram(shaderCouleur.getProgramID());

            // Envoi des vertices

            glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);

            glEnableVertexAttribArray(0);

            // Envoi des couleurs

            glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, couleurs);

            glEnableVertexAttribArray(1);

            // Affichage du triangle

            glDrawArrays(GL_TRIANGLES, 0, 3);

            // Désactivation des tableaux Vertex Attrib

            glDisableVertexAttribArray(1);

            glDisableVertexAttribArray(0);

        // Désactivation du shader

        glUseProgram(0);

        // Actualisation de la fenêtre

        SDL_GL_SwapWindow(m_fenetre);

    }

}

Petit détail : Vous désactiverez vos tableaux dans l'ordre inverse où vous les avez activés. Par exemple : si j'active les tableaux dans l'ordre 1, 2, 3 alors je les désactiverai dans l'ordre 3, 2, 1.

Vous devriez obtenir un triangle coloré :

Vous commencez à voir l'intérêt des shaders ? Bon si vous n'êtes pas convaincus je vais vous montrer une autre façon de colorier notre triangle.

Si chaque sommet possède sa propre couleur alors OpenGL (avec l'aide des shaders) nous fera un joli petit dégradé entre les différentes couleurs. Prenons un exemple, nous allons définir une couleur différente pour chaque sommet :

// Remplaçons les couleurs par les suivantes ...

float couleurs[] = {1.0, 0.0, 0.0,  0.0, 1.0, 0.0,  0.0, 0.0, 1.0};

//... et voyons ce que ça donne;

Vous devriez avoir ceci :

Comme vous le constatez, les shaders ont calculé automatiquement la couleur de chaque pixel se trouvant entre les différents sommets.

Cette opération s’appelle l’interpolation. C’est-à-dire qu’OpenGL est capable de trouver la couleur de chaque pixel entre deux sommets ayant une couleur différente. Le gros avantage de l'interpolation c'est que nous ne nous en occupons pas :p . En effet, même si nous définissons nos shaders, nous ne nous occuperons pas de trouver la couleur de chaque pixel. Il nous suffira juste de donner notre code source pour seulement un et OpenGL se chargera de faire la même opération pour tous les autres. Magique n'est-ce pas ? :magicien:

Télécharger : Code Source C++ du Chapitre 4

Exercices

Énoncés

Au même titre que le chapitre précédent, vous avez maintenant le droit à votre petite série d'exercice. Votre objectif va être de colorier différents triangles avec une couleur spécifique.

Exercice 1 : Coloriez le triangle du haut avec les valeurs données ci-dessous :

  • rouge : 240.0

  • vert: 210.0

  • bleu : 23.0

Exercice 2 : Même consigne que précédemment mais avec les valeurs :

  • rouge : 230.0

  • vert : 0.0

  • bleu : 230.0

Exercice 3 : Coloriez le triangle uniquement avec la couleur vert (valeur 255.0). Simplifiez le tableau si possible.

Exercice 4 : Reprenez les 3 couleurs précédentes, au lieu de les appliquer au triangle appliquez en une pour chaque vertex. Le résultat final doit ressembler à ceci (ce n'est pas grave si les couleurs ne sont pas dans le même ordre) :

Exercice 5 : Le dernier exercice va allier ce que nous avons vu dans le chapitre précédent et celui-ci. L'objectif est d'afficher un losange multicolore avec les couleurs de l'exercice 4, le dernier vertex doit utiliser le bleu (255.0) :

Solutions

Exercice 1 :

Le but des exercices est de colorier un triangle, il n'y a donc que le tableau couleurs à modifier. Le reste du code ne change pas, on n'envoie toujours ce tableau à OpenGL grâce au Vertex Attrib 1. Pour ce premier exercice, il fallait trouver :

// Couleurs

float couleurs[] = {240.0 / 255.0, 210.0 / 255.0, 23.0 / 255.0,     // Vertex 1

                    240.0 / 255.0, 210.0 / 255.0, 23.0 / 255.0,     // Vertex 2

                    240.0 / 255.0, 210.0 / 255.0, 23.0 / 255.0};    // Vertex 3

Il ne fallait pas oublier de diviser les valeurs que je vous avais données par 255.0, sinon le code n'aurait pas fonctionné. ;)

Exercice 2 :

Le principe reste le même que le premier exercice, il suffit juste de modifier le tableau couleurs :

// Couleurs

float couleurs[] = {230.0 / 255.0, 0.0, 230.0 / 255.0,     // Vertex 1

                    230.0 / 255.0, 0.0, 230.0 / 255.0,     // Vertex 2

                    230.0 / 255.0, 0.0, 230.0 / 255.0};    // Vertex 3

Exercice 3 :

Encore une fois, on conserve le même principe que les exercices 1 et 2. La couleur demandée n'était que le vert ici, ce qui allège un peu notre tableau :

// Couleurs

float couleurs[] = {0.0, 255.0 / 255.0, 0.0,     // Vertex 1

                    0.0, 255.0 / 255.0, 0.0,     // Vertex 2

                    0.0, 255.0 / 255.0, 0.0};    // Vertex 3

Cependant il y avait un moyen de simplifier cette déclaration. En effet, je vous ai précisé dans le cours que l'on pouvait simplifier les divisions des couleurs brutes (rouge, vert et bleu). Donc au lieu de faire l'opération 255.0 / 255.0, on pouvait directement mettre la valeur 1.0. Ce qui allégeait encore plus le tableau final :

// Couleurs

float couleurs[] = {0.0, 1.0, 0.0,     // Vertex 1

                    0.0, 1.0, 0.0,     // Vertex 2

                    0.0, 1.0, 0.0};    // Vertex 3

Exercice 4 :

Le but de ce dernier exercice était bien évidemment d'appliquer chacune des couleurs mises en place précédemment sur un vertex en particulier. Nous avions déjà vu ce principe avec l'exemple du triangle multicolore :

float couleurs[] = {240.0 / 255.0, 210.0 / 255.0, 23.0 / 255.0,    // Vertex 1

                    230.0 / 255.0, 0.0, 230.0 / 255.0,             // Vertex 2

                    0.0, 1.0, 0.0};                                // Vertex 3

L'ordre des couleurs n'est pas important ici, ce n'est pas quelque chose de demandé.

Exercice 5 :

La seule difficulté ici était qu'il fallait reprendre la couleur de deux vertices pour le second triangle. En effet, ceux-ci sont doubler pour pouvoir afficher le triangle du bas, il fallait donc doubler leur couleur :

// Couleurs

float couleurs[] = {240.0 / 255.0, 210.0 / 255.0, 23.0 / 255.0,     // Vertex 1

                    230.0 / 255.0, 0.0, 230.0 / 255.0,              // Vertex 2

                    0.0, 1.0, 0.0,                                  // Vertex 3

                    240.0 / 255.0, 210.0 / 255.0, 23.0 / 255.0,     // Vertex 4 (Copie du 1)

                    0.0, 0.0, 1.0,                                  // Vertex 5

                    0.0, 1.0, 0.0};                                 // Vertex 6 (Copie du 3)

Il faut bien faire correspondre votre couleur avec votre vertex pour avoir un bon affichage. ;)

D'ailleurs, le tableau de vertices ne change absolument pas par rapport à avant.

// Vertices

float vertices[] = {-0.5, 0.0,   0.0, 1.0,   0.5, 0.0,          // Triangle 1

                    -0.5, 0.0,   0.0, -1.0,   0.5, 0.0};        // Triangle 2

Pensez à bien affecter la valeur 6 au paramètre count de la fonction glDrawArrays() pour afficher vos deux triangles, sinon elle n'en prendra en compte qu'un seul :

// Affichage des triangles

glDrawArrays(GL_TRIANGLES, 0, 6);

Ce chapitre (un peu compliqué je vous l'accorde) est enfin terminé. Vous savez maintenant ce que sont les shaders et à quoi ils servent. Je pense faire une partie entièrement consacrée aux effets assez sympas que l'on peut réaliser, mais bon ce n'est pas pour maintenant. ;)


Les matrices

Alors là, nous allons aborder un chapitre assez compliqué. Nous allons découvrir ce que sont les matrices avec OpenGL, leur utilité, etc... C'est un chapitre assez technique et surtout rempli de mathématiques :-° . Je vous conseille de bien le relire plusieurs fois jusqu'à que vous ayez tout compris, car les matrices sont indispensables avec OpenGL. ;)

Introduction

Lançons nous tout de suite dans le monde des mathématiques. Je vous rappelle que si vous voulez développer des jeux-vidéo vous ne pouvez pas éviter les maths, elles sont indispensables au bon fonctionnement du jeu. Les premières notions que nous allons apprendre sont les transformations.

Qu'est-ce qu'une transformation vous allez me dire ? Au lieu d'une définition barbante que nous n'allons point retenir ( :p ) je vais vous donner des exemples de transformations et vous comprendrez tout de suite ce que c'est :

  • Une translation : consiste à faire "glisser" un ensemble de points sur un "rail" (un vecteur).

  • Une rotation : consiste à faire pivoter un ensemble de points d'un angle Thêta par rapport à un point.

  • Une homothétie : consiste simplement à agrandir ou à réduire une forme géométrique.

Une transformation est donc grosso-modo une modification apportée à un ensemble de points ou repère dans un espace donné (soit 2D, soit 3D, etc ...). Les 3 transformations que vous voyez là sont les principales transformations utilisées dans OpenGL, en particulier la translation.

Ok, on sait ce qu'est une transformation maintenant, mais à quoi ça sert ?

Elles vont nous servir à placer tous nos objets (personnages, arbres, maisons, ...) dans notre monde 3D. A chaque fois que nous voudrons placer un objet nous utiliserons les transformations. Elles seront essentiellement utilisées sur le repère.

Les matrices sous OpenGL 2.1

Avant de parler du fonctionnement des matrices dans la version 3, je vais vous parler de celui des anciennes versions, ce sera plus facile à comprendre. De plus le principe ne change absolument pas d'une version à l'autre.
Tout d'abord, qu'est-ce qu'une matrice ? C'est un tableau de nombres ordonnés en lignes et en colonnes entourés par des parenthèses. Sa syntaxe est semblable à celle d'un vecteur mais avec plus de nombres :

Une matrice n'est pas forcément un tableau de 9 cases, elle peut en contenir jusqu'à l'infini. Cependant ce n'est pas qu'un simple tableau, c'est une sorte de super-vecteur qui permet de faire pas mal de choses intéressantes. Un vecteur est en général utilisé en 3D pour gérer les points, les directions, les normales... Les matrices permettent de faire bien plus que ça. Elles servent principalement à convertir des données géométriques en données numériques. Il est plus facile de travailler avec des nombres qu'avec des compas et des équerres placés sur notre écran. :p Voyons d'ailleurs pourquoi OpenGL a besoin des matrices :

  • Pour la projection : grâce à cela nous allons pourvoir "transformer" un monde 3D en un monde 2D (jusqu'à nouvel ordre, un écran ne dispose que de deux dimensions).

  • Pour les transformations : regroupant les transformations que je vous ai énumérées à l'instant.

Ce sont les besoins fondamentaux d'OpenGL pour utiliser la 3D. Dans les anciennes versions, les matrices étaient gérées automatiquement, on n'utilisait que quelques fonctions pour les créer et les utiliser. Depuis la version 3.1, ces fonctions sont supprimées, car l'approche des jeux d'aujourd'hui est différente. Heureusement pour nous, nous avons en notre possession une librairie mathématique du nom de GLM qui permet de gérer les matrices (et bien plus), mais avant de commencer à les manipuler nous allons faire un peu de théorie pour bien comprendre ce que nous faisons.

En définitif, nous aurons besoin de deux matrices pour coder avec OpenGL :

  • La matrice de projection : qui sert à transformer notre monde 3D en un monde 2D affichable sur l'écran.

  • La matrice modelview (ou visualisation de modèle): qui sera la matrice principale, c'est sur elle que nous allons appliquer nos transformations.

Chaque vertex sera multiplié par ces deux matrices pour pouvoir être affiché sur notre écran, le tout en nous donnant une impression de 3D (magique n'est-ce pas :magicien: ).

Partie théorique

Dans cette partie nous allons apprendre ce que sont les matrices et comment les utiliser pour nos programmes. Pour simplifier cet apprentissage, je ne vais vous montrer que la multiplication de matrices car c'est une notion que nous retrouverons assez souvent dans ce tuto. De plus, cela vous donnera une bonne idée des calculs matriciels sans pour autant voir les choses les plus compliquées (inversion, déterminant, etc.). :p

Matrice carrée

On commence tout de suite cette partie par les matrices carrées.

Une matrice carrée est une matrice ayant le même nombres de colonnes et de lignes :

Il y a 3 rangées de colonnes et 3 rangées de lignes, c'est donc ce que l'on appelle une matrice carrée. Son nom complet est d'ailleurs : matrice carrée d'ordre 3. Le chiffre à la fin permet de spécifier la taille. Si on avait mis le chiffre 4, alors la matrice aurait eu 4 colonnes et 4 lignes. Nous n'utiliserons que les matrices carrées dans OpenGL. Et tant mieux, car ça simplifie grandement les choses :-° .

Matrice d'identité

Sous ce nom barbare se cache en réalité la matrice la plus simple qu'il soit. Elle a la particularité d'avoir toutes ses valeurs égales à 0 hormis les valeurs de sa diagonale qui, elles, sont égales à 1. Quelle que soit la taille de la matrice, on retrouvera toujours cette particularité :

Si nous multiplions un vecteur par une telle matrice, le vecteur ne sera absolument pas changé :p :

Nous allons étudier la multiplication matricielle dans un instant. Mais vous pouvez déjà retenir ce principe : si un vecteur est multiplié par une matrice d'identité, alors il ne sera pas modifié.

Multiplication d'un vecteur par une matrice

Partie 1 : la vérification

Dans ce premier exemple, je vais prendre une matrice carrée d'ordre 3 pour simplifier les choses. Sachez cependant que le principe est le même quelle que soit la taille de la matrice.

Voyons comment arriver à ce résultat.
Premièrement, il faut vérifier que le nombre de colonnes de la matrice soit égal au nombre de coordonnées du vecteur. Si ce n'est pas le cas alors la multiplication est impossible.

La matrice possède 3 colonnes et le vecteur possède 3 coordonnées. La multiplication est donc possible.

Attention ! La matrice sera toujours à GAUCHE de la multiplication et le vecteur sera toujours à DROITE !

Prenons un autre exemple :

La matrice possède 2 colonnes et le vecteur 3 coordonnées. La multiplication est impossible.
Vous avez compris le principe ? Bien, passons à la suite.

Partie 2 : La multiplication

Attaquons la partie la plus intéressante :diable: . La multiplication d'une matrice et d'un vecteur s'effectue comme ceci : pour une seule coordonnée (x, y, ou z) du vecteur résultat, nous allons faire la somme des multiplications de chaque nombre d'une ligne de la matrice par chaque nombre correspondant du vecteur. Un peu barbant comme explication, rien ne vaut un bon exemple :

Commençons déjà par la première ligne, vous devriez comprendre le principe une fois cet exemple fini :

Il faut multiplier les nombres de même couleur sur le schéma, puis additionner les différents résultats. Une ligne de la matrice ne donnera qu'une seule coordonnée du vecteur résultat, ici la coordonnée x. Passons à la deuxième ligne :

Vous comprenez le principe ? La première ligne nous a donné la première coordonnée, la deuxième ligne nous donne la deuxième coordonnée. Nous multiplions chaque ligne de la matrice par toutes les valeurs du vecteur. Passons à la troisième ligne :

La troisième ligne de la matrice nous donne la troisième coordonnée. Si la matrice avait eu 4 lignes (et donc 4 colonnes) alors il y aurait eu une quatrième opération du même type pour la quatrième coordonnée. Si on récapitule tout ça, on a :

Vous avez compris ? Je vous donne un autre exemple pour comparer les différents résultats :

N'hésitez pas à bien relire et essayez de comprendre ces deux exemples. C'est la base de la multiplication matricielle.

Je vous rappelle que la matrice est toujours à gauche et le vecteur est toujours à droite.

Passons à un pseudo-exemple, vous n'avez pas oublié ce qu'est la matrice d'identité j'espère :p , on sait qu'un vecteur multiplié par cette matrice ne changera pas. Maintenant que l'on connait un peu la multiplication on va voir pourquoi le vecteur n'est pas modifié :

Oh magie, le résultat ne change pas. Vous comprenez pourquoi cette matrice est la plus simple à utiliser. ;)
Je vais maintenant vous donner un exemple de multiplication avec une matrice carrée d'ordre 4. Le principe ne change absolument pas, nous allons voir cet exemple étape par étape pour bien comprendre. Commençons :

La matrice possède 4 colonnes et le vecteur 4 lignes, la multiplication est possible. Maintenant on passe à la première étape :

Vous ne voyez qu'une seule différence : on ajoute à la somme une multiplication supplémentaire. En effet, n'oubliez pas que l'on multiplie les nombres de la matrice avec les nombres correspondants du vecteur. Pour la deuxième ligne c'est pareil :

Pour la troisième ligne ça ne change pas :

Ah, une quatrième ligne, eh bien oui c'est plus long :p . Mais ça ne change toujours pas :

Ce qui nous donne au final :

Vous voyez, le principe de la multiplication ne change absolument pas.

Piouf, cette partie était un peu difficile à comprendre. Je vais vous donner quelques exercices pour vous entrainer à la multiplication. Faites-les sérieusement, vous avez besoin de comprendre ce que l'on vient de faire pour comprendre la suite. Sinon vous serez complétement largués :( .

Exercices de multiplications

Exercice 1 :

Exercice 2 :

Exercice 3 :

Solutions

Exercice 1 :

Exercice 2 :

La multiplication est impossible, la matrice possède 3 colonnes alors que le vecteur possède 4 coordonnées.

Exercice 3 :

Multiplication de deux matrices

La multiplication de deux matrices peut sembler plus compliquée à première vue mais si vous avez compris ce que l'on a fait avant, alors vous savez déjà multiplier deux matrices.

Partie 1

Avant tout, pour pouvoir multiplier deux matrices carrées entre-elles il faut absolument que les deux matrices soient de la même taille (donc du même ordre), c'est-à-dire qu'elles doivent avoir le même nombre de lignes et le même nombre de colonnes.

Les deux matrices ont la même taille, on peut donc les multiplier.
Prenons un autre exemple :

Ici, les deux matrices n'ont pas la même taille nous ne pouvons pas les multiplier.
Il ne devrait y avoir rien de compliqué pour le moment. :)

Partie 2

Pour pouvoir multiplier deux matrices carrées, nous allons utiliser une petite combine. Nous allons couper la deuxième matrice en 3 vecteurs (ou 4 selon la taille), puis nous appliquerons la multiplication que nous avons vue à l'instant pour chacun de ces vecteurs. Prenons deux matrices cobayes :

Attention, l'ordre des matrices est important ! M1 x M2 ne sera pas forcément égal à M2 x M1. Faites attention à l'ordre de votre multiplication !

Bien, coupons la seconde matrice en 3 vecteurs :

Maintenant il nous suffit d'appliquer la multiplication d'une matrice et d'un vecteur pour chaque vecteur que nous venons de créer. Voici ce que ça donne pour le premier :

Au tour du deuxième :

Quand il y a un "0" dans une matrice ça nous facilite grandement le calcul :lol: . Bref, on fait la même chose avec le dernier vecteur :

Nous obtenons au final 3 vecteurs fraichement calculés. Il nous suffit ensuite de les assembler dans l'ordre de la division de la matrice !

Au final, nous obtenons :

Je vous rappelle encore une fois que l'ordre de la multiplication compte. M1 x M2 est différent de M2 x M1 !

Vous voyez, une fois que vous avez compris la première multiplication, vous savez déjà multiplier deux matrices.
?#8240;videmment, la taille de la matrice ne change toujours pas le principe, voyons ensemble un exemple de multiplication de deux matrices carrées d'ordre 4.

Coupons la seconde matrice en 4 vecteurs (et oui car c'est une matrice carrée d'ordre 4 et pas 3) :

Il nous suffit maintenant d'appliquer la multiplication "Matrice - Vecteur" sur les 4 vecteurs que nous venons de créer. Voici le premier résultat :

Puis le second :

Le troisième :

Et enfin le quatrième et dernier vecteur :

Maintenant on réunit les vecteurs résultats dans le bon ordre :

Voici donc le résultat final :

Vous remarquez que la multiplication se passe exactement de la même façon que l'on soit en présence de matrices à 3 colonnes ou à 4 colonnes ou même à 1000 colonnes :p . Passons maintenant à quelques exercices pour vous entrainer. C'est important je le répète, essayez de faire ces exercices sérieusement.

Exercices de multiplications

Exercice 1 :

Exercice 2 :

Exercice 3 :

Solutions

Exercice 1 :

La multiplication est impossible, la première matrice possède 4 lignes et 4 colonnes alors que la seconde matrice ne possède que 3 lignes et 3 colonnes.

Exercice 2 :

Exercice 3 :

Et voilà, la partie la plus compliquée de ce chapitre est enfin terminée ! La multiplication matricielle est une notion difficile à comprendre (même s'il en existe bien d'autres plus complexes) mais vous êtes maintenant capables de la maitriser. :D

Transformations au niveau matricielle

Le but de la partie précédente était d'apprendre à multiplier deux matrices. J'ai volontairement inclus des exercices avec des matrices carrées d'ordre 4 car OpenGL aura besoin la plupart du temps de ce genre de matrices (et heureusement !). Dans cette partie, nous allons faire passer les transformations de la géométrie à l'algèbre (les nombres).

Comme vous le savez les transformations sont des outils géométriques, pour calculer une rotation par exemple nous avons besoin d'un rapporteur. Or c'est un peu compliqué de poser notre rapporteur sur l'écran, surtout si l'angle se trouve derrière le dos d'un personnage :lol: . Pour pouvoir utiliser les transformations numériquement nous devons utiliser... les matrices. ;)

La translation

La translation permet de déplacer un ensemble de points ou une forme dans un espace donné. En gros, on prend un vecteur qui servira de "rail" puis on fera glisser nos points sur ce rail. La forme finale ne sera pas modifiée, elle sera juste déplacée. :)

Une translation en 3 dimensions se traduit par la matrice suivante :

  • X : C'est la coordonnée x du vecteur de translation.

  • Y : C'est la coordonnée y du vecteur de translation.

  • Z : C'est la coordonnée z du vecteur de translation.

Vous remarquerez que cette matrice ressemble beaucoup à la matrice d'identité, il n'y a que les coordonnées du vecteur en plus.

L'homothétie

Une homothétie permet d'agrandir ou de réduire une forme géométrique, voici la matrice correspondante :

  • X : C'est le facteur multiplicatif de l'axe x.

  • Y : C'est le facteur multiplicatif de l'axe y.

  • Z : C'est le facteur multiplicatif de l'axe z.

La rotation

Attention ! La matrice que vous allez voir est certainement la matrice la plus compliquée de tout le tutoriel :p . Cependant, il est inutile de la retenir, elle est trop complexe nous la verrons jamais directement :

Je vous avais dit que cette matrice était... particulière (pour vous dire, je ne la connais pas par c?#8220;ur moi-même). Grâce à elle, nous pouvons faire pivoter en ensemble de points d'un angle thêta autour d'un axe défini par les coordonnées (x, y, z). Nul besoin de retenir cette matrice, je le répète. ;)

Transformations au niveau géométrique

Durant l'introduction, je vous ai brièvement parlé de deux matrices : la projection (qui permet de convertir un monde 3D en un monde 2D affichable sur notre écran) et la modelview (qui permet de placer nos objets dans ce même monde 3D).

C'est sur cette dernière matrice que l'on effectuera toutes les transformations que l'on a vues, à savoir : la translation, la rotation et l'homothétie. Voyons d'ailleurs comment placer un objet dans un monde 3D :

  • On crée la matrice modelview (une seule fois pour tout le programme, pas une seule fois par objet).

  • Puis on effectue une transformation sur cette matrice (par exemple une rotation de 90° sur l'axe Z).

  • Enfin on dessine ce que l'on veut dessiner et vu que l'on a effectué une rotation de 90°, alors notre objet sera penché de 90°.

Voyons ce que cela donne si je fais pivoter un triangle de 90°...

Voici un triangle avant la rotation :

Et le revoilà après :

Vous voyez ce qui c'est passé ? Le triangle a pivoté de 90° grâce à une transformation.

La rotation se fait selon le sens trigonométrique et non selon le sens horaire.

Les Transformations sous OpenGL

Aaahh... on commence à relier OpenGL et les mathématiques (enfin !). Il est temps de voir le comportement des transformations dans un programme.

La première chose à savoir est que chaque transformation (rotation, ...) sera effectuée non pas sur un objet mais sur le REP?#710;RE du monde, c'est-à-dire que si l'on veut faire pivoter un objet de 120° alors il faudra faire pivoter non pas l'objet en lui-même mais son REP?#710;RE.

Par exemple, si vous voulez afficher un objet à 300 mètres d'un autre, vous n'allez pas ajouter 300 unités de longueur à chaque vertex de l'objet, autant garder les vertices déjà définis.

Hein ??? J'ai rien compris.

Bon, mettons que vous ayez un méchant soldat ennemi de 500 vertices. Vous n'allez pas modifier ses 500 vertices pour afficher un autre ennemi deux mètres plus loin. ?#8225;a serait trop couteux en ressources, surtout si vous affichez une armée de 200 soldats...

A la place, on modifiera la position du repère (qui est en fait la matrice modelview) pour chaque objet que l'on veut dessiner. Un personnage gardera ses 500 vertices intactes quelque soit sa position.

Vous voyez la différence entre modifier toute une scène de dizaines de milliers de vertices et modifier une matrice de 16 valeurs.

Illustrations

Je vais vous donner quelques exemples de transformations effectuées sur un repère, ce sera plus simple à comprendre si vous voyez ce que donne chaque transformation.

Le repère de base

Le repère de base est en fait un repère qui n'a pas encore été modifié, c'est sur celui-ci que l'on effectuera notre première transformation. Graphiquement, on représente un repère comme ceci :

Au niveau des matrices, ce repère correspond simplement à une matrice d'identité d'ordre 4 :

La translation

La translation est la transformation la plus simple :p . N'oubliez pas que c'est le REP?#710;RE qui est modifié. Ici, on translate le repère par rapport au vecteur V(2, 1), soit deux unités de longueur sur l'axe X et 1 unité sur l'axe Y :

Avec les matrices on multiplierait la matrice modelview par la matrice de translation suivante :

La rotation

Le principe ne change pas pour la rotation, on effectue la transformation sur le repère. Voici un exemple d'une rotation de 45° sur l'axe Z :

Pour la rotation, on multipliera la matrice modelview par la matrice de rotation (toujours aussi effrayante :diable: ) :

L'homothétie

En temps normal, avec une homothétie on modifie la taille d'une forme géométrique. Sauf qu'ici on modifie la taille du repère, donc on modifie la taille de chaque unité de longueur. Voici ce que donne une homothétie de coordonnées (2, 2) sur le repère :

Vous voyez que la taille du repère à changé, désormais si on fait une translation par un vecteur(2, 1) alors la translation sera plus grande et le repère se retrouvera plus loin.

Pour ce qui est de la matrice modelview, il suffira de la multiplier par la matrice suivante :

Attention, la coordonnée Z est mis à 1 volontairement. Nous expliquerons pourquoi un peu plus loin.

Les matrices et la boucle principale

Vous n'êtes pas sans savoir qu'un jeu se passe en grande partie dans ce que l'on appelle la boucle principale, c'est une boucle qui va se répéter indéfiniment jusqu'à ce que le joueur arrête de jouer (enfin pas vraiment, mais partons de ce principe).

Il faudra à chaque tour de boucle réinitialiser la matrice modelview pour ne pas qu'elle garde les traces de transformations du tour précédent. Si c'était le cas, le jeu ne se redessinerait jamais au même endroit et serait totalement difforme.

Voici ce qui se passera à chaque tour de boucle :

  • On replacera notre repère à sa position initiale, grâce à la fonction loadIdentity.

  • On effectuera une ou des transformation(s) pour afficher un objet grâce aux fonctions translate, ...

  • On répètera la deuxième étape jusqu'à que tous les objets du niveau soient affichés.

Toutes ces étapes se répèteront indéfiniment dans notre programme. En général, on affiche tous les objets d'un niveau 50 à 60 fois par seconde, imaginez le fourbi que l'ordinateur doit calculer. :p

Accumulation de transformations

Attention, de même que l'ordre des matrices dans la multiplication, l'ordre des transformations a une importance capitale ! Si vous effectuez une translation puis une rotation, vous n'obtiendrez pas la même chose que si vous faisiez une rotation et une translation.

Comme d'habitude prenons un petit exemple. Dans un premier temps, je vais faire une translation du repère par rapport au vecteur V(2, 1), puis une rotation de 90° sur l'axe Z (l'axe Z est en fait pointé vers nous, c'est pour ça que nous ne le voyons pas) :

Maintenant je vais l'inverse, une rotation de 90° puis une translation de vecteur (2, 1) :

Oh tiens ! Les repères ne sont pas les mêmes. Et oui, les repères sont différents car l'ordre des transformations est important. Faites donc bien attention à ce que vous voulez faire et à l'ordre dans lequel effectuer ces transformations. ;)

Enfin ce chapitre est terminé, il nous aura fallu du temps mais n?#8364;™oubliez pas que tout ce que je vous enseigne est nécessaire pour comprendre et utiliser OpenGL. Récapitulons ce que nous savons faire :

  • On sait afficher des vertices pour former des formes géométriques.

  • On sait utiliser les shaders pour leur donner un peu de couleurs (même si l?#8364;™on peut faire bien plus avec eux ;) ).

  • On sait utiliser des matrices pour effectuer des transformations.

Bonne nouvelle ! Nous connaissons tout ce qui est nécessaire pour ajouter une troisième dimension à nos programmes. Il est d'ailleurs temps mes amis, attaquons-nous à cette nouvelle dimension qui s'offre à nous ! :pirate:


La troisième dimension (Partie 1/2)

Le chapitre précédent était un peu compliqué. Mais grâce à lui, nous sommes maintenant capables d'utiliser les transformations pour façonner un monde 3D. Dans ce nouveau chapitre, nous allons mettre en commun ce que l'on sait sur OpenGL avec ce que l'on a appris concernant les matrices.

J'ai préféré diviser ce chapitre en deux parties, étant donné que nous allons aborder pas mal de nouvelles notions. Dans cette première partie, nous allons nous concentrer sur l'implémentation des matrices dans notre programme, puis dans la seconde partie, nous implémenterons la troisième dimension et surtout nous apprendrons à l'utiliser ! :p

La matrice de projection

Présentation

Dans le chapitre précédent, nous avons vu ce qu'étaient les matrices ainsi que leur fonctionnement. Nous nous sommes surtout concentrés sur la matrice modelview qui permet de placer nos objets dans un monde 3D. Les transformations que nous avons vues (translation, rotation et homothétie) sont appliquées sur elle.

Dans ce chapitre-la maintenant, nous allons nous concentrer sur une autre matrice : la matrice de projection.

Sa seule utilité est de permettre l'affichage d'un monde 3D sur notre écran qui lui ne possède que 2 dimensions. Cependant, vous vous imaginez bien que ce processus n'est pas aussi simple que cela, on a quand même besoin de quelques paramètres pour permettre une telle opération.

Pour expliquer son fonctionnement, je vais prendre une bonne vieille fonction connue des habitués d'OpenGL : la fonction gluPerspective().

Euh ouai, mais moi je viens ici pour apprendre OpenGL, je ne connais pas cette fonction. :colere2:

Ne vous inquiétez pas, je vais vous expliquer le fonctionnement de cette fonction en détails, et j'ai plutôt intérêt à le faire étant donné que nous allons la recoder entièrement. :p

Comme je vous l'ai dit dans les chapitres précédents, une bonne partie des anciennes fonctions d'OpenGL a disparu avec la nouvelle version. La fonction gluPerspective a elle aussi disparu (indirectement). Il nous faudra donc la coder de nos propres mains. Et pour vous faciliter la tâche, je vais vous expliquer en détails son fonctionnement.

Premièrement, voici le prototype de la fonction :

void gluPerspective(double angle, double ratio, double near, double far);

J'ai volontairement modifié le nom des paramètres pour une meilleure compréhension, cependant je garderai le nom original des fonctions recodées avec le préfixe (gl, glu, ...) en moins. ;)

Hum tout plein de paramètres intéressants [image] . Je vais commencer par les deux derniers paramètres : near et far.

Near

Le paramètre Near correspond à la distance entre votre écran et ce qui sera affiché.

La petite zone entre l'écran et la plaque est une zone où rien ne sera affiché. Même s'il y a un objet dans cet intervalle il ne sera pas affiché.

Pourquoi me direz-vous ? Simplement parce que les mathématiques nous l'imposent, il nous faut obligatoirement une zone non-affichable pour que le calcul de la projection puisse s'effectuer. Il se passe la même chose avec la division par zéro, c'est impossible. Il faut obligatoirement que le dénominateur soit au moins un peu plus grand (ou plus petit mais on s'en moque) que zéro.

Le paramètre near représente donc ce petit intervalle nécessaire au calcul de la projection.

Far

Le paramètre far est un peu plus simple, on peut dire que c'est l'inverse de near.

C'est également une distance entre votre écran et ce qui sera affiché. La différence est que tout objet se trouvant au delà de cette distance ne sera pas affiché.

Au final, pour qu'un objet puisse s'afficher sur l'écran, il faut qu'il se situe entre les zones near et far, sinon il ne sera pas affiché.

Ratio

Je pense que vous avez tous entendu parler des télévisions 4/3 (quatre tiers) et 16/9 (seize neuvièmes). Le ratio est le rapport entre la longueur de la télé et sa hauteur. Dans la plupart des cas nous avons soit un ratio de 4/3 soit un ratio de 16/9, voire 16/10 pour les jeux-vidéo.

Pour nous, le ratio s'appliquera à la fenêtre SDL que l'on a codée. Dans le chapitre 3, nous avions créé une fenêtre de 800 pixels par 600 pixels. Le ratio sera donc de 800/600, et si on simplifie la fraction on a un ratio de 4/3.

Dans les jeux-vidéo, on propose généralement plusieurs modes d'affichage afin de s'adapter à l'écran du joueur. En effet, tous les joueurs n'ont pas forcément le même écran. Nous donnerons au final comme paramètre la division entre la longueur de la fenêtre SDL et sa hauteur.

Angle

Ce paramètre est le plus spécial de la fonction. C'est en fait l'angle de vue avec lequel nous allons voir la scène. Plus cet angle sera petit, plus on aura l'impression de faire un effet de zoom sur la scène. Et à l'inverse, plus il sera grand, plus on aura l'impression que la scène s'éloigne et se déforme.

Voici trois exemples de la même scène avec trois angles de vue différents :

Merci à Kayl pour sa scène de test.
Dans la première image, on utilise un angle de 70°, dans la deuxième un angle de 30° et dans la troisième un angle de 100°.

L'avantage d'un angle plus petit est que l'on a l'impression de faire un zoom de jumelle ou de sniper ;) . A l'inverse, un angle plus grand créera une ambiance plus particulière, mais ce genre d'angle s'utilise rarement.

En général, l'angle de vision "normal" est de 70° (70 degrés) et c'est d'ailleurs ce que l'on mettra dans notre code.

Utilisation de la librairie GLM

Dans les précédentes versions d'OpenGL, il existait une multitude de fonctionnalités mathématiques qui étaient gérées par OpenGL même, les matrices en faisait évidemment partie. Cependant, ces fonctionnalités sont maintenant supprimées et il faut trouver un autre moyen de gérer nos matrices nous-même.

Heureusement pour nous, il existe une librairie qui s'appelle GLM (pour OpenGLMathematics). Vous l'avez déjà téléchargée normalement donc vous n'avez rien à faire. Cette librairie nous permet d'utiliser les matrices, et bien plus encore, sans avoir à nous soucier de tout programmer à la main. Elle inclut même certaines fonctions dépréciées comme gluPerspective(). :)

Nous allons donc utiliser GLM pour simuler toutes les fonctions dont nous aurons besoin.

Utilisation de la projection

Inclusion des matrices

Toutes ces belles matrices (que vous adorez j'en suis sûr :p ) ne nous servent pas à grand chose pour l'instant. Nous allons maintenant les intégrer dans notre code OpenGL pour enfin voir ce qu'elles ont véritablement dans le ventre.

Reprenons le code de la boucle principale que nous avons laissé au chapitre 4 :

void SceneOpenGL::bouclePrincipale()

{

    // Variables

    bool terminer(false);

    float vertices[] = {-0.5, -0.5,   0.0, 0.5,   0.5, -0.5};

    float couleurs[] = {1.0, 0.0, 0.0,  0.0, 1.0, 0.0,  0.0, 0.0, 1.0};

    // Shader

    Shader shaderCouleur("Shaders/couleur2D.vert", "Shaders/couleur2D.frag");

    shaderCouleur.charger();

    // Boucle principale

    while(!terminer)

    {

        /* ***** Code d'affichage ***** */

    }

}

Je n'ai pas mis tout le code, seule cette partie nous intéresse pour le moment.

Les en-têtes de GLM

Vous savez maintenant que l'on a besoin de deux matrices pour faire fonctionner un jeu 3D : la matrice de projection et la modelview. Nous allons donc les déclarer dans notre code grâce à librairie GLM.

Pour commencer, veuillez inclure les en-têtes suivants dans le fichier SceneOpenGL.h qui permettent à notre programme d'utiliser les matrices :

// Includes OpenGL

....

// Includes GLM

#include <glm/glm.hpp>

#include <glm/gtx/transform.hpp>

#include <glm/gtc/type_ptr.hpp>

// Autres includes

#include <SDL2/SDL.h>

#include <iostream>

#include <string>

#include "Shader.h"

La première inclusion permet d'utiliser les fonctionnalités principales de GLM, la deuxième les transformations et la dernière permet de récupérer les valeurs des matrices (nous verrons cela dans un instant).

Elle est bizarre ton extension c'est marqué .hpp c'est normal ?

Oui tout à fait. :)

La particularité de GLM c'est qu'elle est codée directement dans les headers, il n'y a pas de librairie pré-compilée comme la SDL par exemple. Grâce à ce système, GLM est compatible avec toutes les plateformes et il n'y a pas besoin de fournir de fichier spécifique pour Linux, Windows, etc. Ni même pour compilateur (.a, .dll, etc.)

Inclusion des matrices

Après toute cette théorie nous pouvons enfin coder nos premières matrices. L'objectif est de créer deux matrices du nom de projection et modelview.

Pour cela, nous allons déclarer deux objets de type mat4 , pour matrice carrée d'ordre 4 . Il faut utiliser le namespace glm en même temps que la déclaration :

// Matrices projection et modelview

glm::mat4 projection;

glm::mat4 modelview;

Tous les objets et méthodes de GLM doivent utiliser le namespace glm . Pour éviter d'avoir à le taper à chaque fois, ajoutez la ligne de code suivante dans chaque classe où vous utiliser les matrices :

// Permet d'éviter la ré-écriture du namespace glm::

using namespace glm;

Ajoutez cette ligne dans le fichier .cpp et non .h faites attention.

Avec cette ligne de code, la déclaration des matrices devient :

// Matrices projection et modelview

mat4 projection;

mat4 modelview;

Maintenant que les matrices sont déclarées, nous allons pouvoir les initialiser.

La matrice projection

Occupons-nous d’abord de la projection. Dans la partie précédente, nous avons parlé d'une ancienne fonction du nom de gluPerspective(). Celle-ci a disparu avec OpenGL 3 mais GLM l'a gentiment recoder pour nous sous la forme d'une méthode, voici son prototype :

mat4 perspective(double angle, double ratio, double near, double far);

On remarque qu'elle contient exactement les mêmes paramètres, pratique n'est-ce pas ? La seule différence est qu'elle renvoie un objet de type mat4 , cet objet doit être affecté à la matrice projection.

Nous appellerons cette méthode avec les paramètres suivants :

  • Un angle de 70° (qui correspond à un angle de vision normal).

  • Un ratio en fonction de la taille de la fenêtre, ici un ratio de m_largeurFenetre / m_hauteurFenetre.

  • Le paramètre near avec une valeur de 1.

  • Le paramètre far avec une valeur de 100.

Voici son appel :

// Matrices

mat4 projection;

mat4 modelview;

// Initialisation

projection = perspective(70.0, (double) m_largeurFenetre / m_hauteurFenetre, 1.0, 100.0);

N'oubliez pas de faire un cast pour le ratio, sinon vous n'aurez que la partie entière de la division.

La matrice modelview

Pour la matrice modelview c'est un peu plus simple. Pour l'initialiser, nous allons leur donner les valeurs d'une matrice d'identité (avec les 1 en diagonale). Elle ne doit pas avoir que des valeurs nulles car si nous multiplions des vecteurs par des 0, il risquerait d'y avoir un gros problème avec notre scène finale. :-°

Pour cela, nous allons utiliser un constructeur qui ne demande qu'une seule valeur : la valeur 1.0.

// Matrices

mat4 projection;

mat4 modelview;

// Initialisation

projection = perspective(70.0, (double) m_largeurFenetre / m_hauteurFenetre, 1.0, 100.0);

modelview = mat4(1.0);

Le constructeur sait tout seul qu'il doit utiliser une matrice d’identité, cela éviter d'avoir à écrire toutes les valeurs à la main pour mettre des 1.0 en diagonal. :p

La boucle principale

Vous vous souvenez qu'au début du chapitre je vous ai parlé du comportement des matrices avec la boucle principale du programme ? Je vous avais dit qu'à chaque tour de boucle, il fallait réinitialiser la matrice modelview pour ne pas avoir les anciennes valeurs du tour précédent.

Cette réinitialisation se fait exactement de la même façon que l'initialisation. C'est-à-dire que l'on affecte le résultat du constructeur mat4(1.0) à la matrice modelview. Nous devons faire ceci juste après le nettoyage de l'écran :

while(!terminer)

{

    // Gestion des évènements

    SDL_WaitEvent(&m_evenements);

    if(m_evenements.window.event == SDL_WINDOWEVENT_CLOSE)

        terminer = true;

    // Nettoyage de l'écran

    glClear(GL_COLOR_BUFFER_BIT);

    // Réinitialisation de la matrice modelview

    modelview = mat4(1.0);

}

Interaction entre le programme et les matrices

Notre code OpenGL devient de plus en plus beau :D . Mais les matrices ne nous servent toujours pas à grand chose elles n'interagissent toujours pas avec notre programme.

Je ne vais pas m'étaler sur le sujet pour le moment, mais sachez que l'interaction entre les matrices et le programme se fait dans le shader. C'est lui qui va faire tous les calculs pour projeter le monde 3D sur notre écran.

Pour le moment, le shader que nous avons chargé (couleur2D) est incapable de gérer la projection, il faut en charger un autre. Si vous n'avez rien modifié dans le dossier "Shaders" du chapitre 4, vous devriez trouver des codes sources qui s'appellent couleur3D.vert et couleur3D.frag(à ne pas confondre avec couleur2D). Si vous ne trouvez pas ces fichiers, re-téléchargez le dossier complet depuis le chapitre 4.

À la différence du premier shader, couleur3D est, lui, capable de gérer la projection. Nous allons donc le charger à la place de son prédécesseur :

// Shader gérant la couleur et les matrices

Shader shaderCouleur("Shaders/couleur3D.vert", "Shaders/couleur3D.frag");

shaderCouleur.charger();

Ne vous inquiétez pas si le fragment shader ne change pas, c'est normal. ;)

Le shader n'est pas le seul à devoir changer, les vertices doivent elles aussi subir une modification. Il faut leur ajouter une troisième dimension pour les rendre compatibles avec la projection.

Ça veut dire que nos triangles seront en 3D ?

Oui et non ... En fait, ils sont en 3D mais pour le moment nous sommes mal placés pour les voir de cette façon. Nous verrons dans la deuxième partie de ce chapitre comment "bien se placer". Ne vous inquiétez pas ça arrivera très vite. ;)

Bref au final, il suffit d'ajouter la coordonnée Z à tous les vertices pour qu'ils soient en 3D. Nous mettrons la valeur -1 pour le moment :

float vertices[] = {-0.5, -0.5, -1.0,   0.0, 0.5, -1.0,   0.5, -0.5, -1.0};

Vu que nous ajoutons une coordonnée à nos vertices, il va falloir modifier un paramètre dans la fonction glVertexAttribPointer. Le paramètre size permet de spécifier la taille d'un vertex, donc son nombre de coordonnées. On le passe désormais à 3 car nous avons 3 coordonnées :

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertices);

Revenons à notre shader, vu que c'est lui qui gère les calculs de projection, il va falloir lui envoyer les matrices que nous avons déclarées pour qu'il puisse travailler correctement. L'envoi de matrice au shader se fait avec cette fonction :

glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);

  • location : permet de savoir où envoyer les matrices à l'intérieur même du shader.

  • count : permet de savoir combien de matrice on envoie. On n'enverra qu'une seule matrice à la fois.

  • transpose : booléen qui permet d'inverser ou non la matrice qu'on envoie. Dans notre cas, on lui donnera toujours la valeur GL_FALSE.

  • value : est un pointeur sur le tableau de valeurs de la matrice. Nous utiliserons la méthode value_ptr() de la librairie GLM

Je vous expliquerai en détails le fonctionnement de cette fonction dans le chapitre sur les shaders. Sachez juste qu'elle nous permet d'envoyer nos matrices au shader.

Nous lui enverrons ces valeurs :

  • location : glGetUniformLocation(shaderCouleur.getProgramID(), "Le_nom_de_la_matrice" ). Oui c'est bien une fonction (on la verra également dans le chapitre sur les shaders). Le paramètre en rouge est une chaine de caractères, nous lui donnerons la valeur "modelview" et "projection".

  • count : 1 (le chiffre 1) pour une matrice.

  • transpose : GL_FALSE.

  • value : Nous lui donnerons le résultat de la méthode value_ptr () de nos objets mat4.

Grâce à cette fonction, notre shader va pouvoir travailler avec les matrices que nous avons déclarées dans le main.

Nous appellerons cette fonction deux fois vu que nous avons deux matrices à envoyer. Ces appels se feront juste avant une fonction que vous connaissez bien : glDrawArrays().

Dans notre cas, nous ferons ces appels toujours de la même façon :

// On envoie les matrices au shader

glUniformMatrix4fv(glGetUniformLocation(shaderCouleur.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));

glUniformMatrix4fv(glGetUniformLocation(shaderCouleur.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));

Si on les intègre au code d'affichage ça donne ceci :

// On spécifie quel shader utiliser

glUseProgram(shaderCouleur.getProgramID());

    // Envoi des vertices et des couleurs

    ....

    // On envoie les matrices au shader

    glUniformMatrix4fv(glGetUniformLocation(shaderCouleur.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));

    glUniformMatrix4fv(glGetUniformLocation(shaderCouleur.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));

    // On affiche le polygone

    glDrawArrays(GL_TRIANGLES, 0, 3);

    // Désactivation des Vertex Attrib

    ....

// On n'utilise plus le shader

glUseProgram(0);

Il faut envoyer les matrices au shader APRÈS avoir fait toutes les transformations ! Si vous les envoyez et que vous faites des transformations après, alors elles ne seront pas prises en compte.
De même, faites attention à les envoyer APRÈS la fonction glUseProgram(), sinon vous les enverrez dans le vide. ;)

Je pense vous avoir parlé de tout, compilons tout ça pour voir que ça donne :

void SceneOpenGL::bouclePrincipale()

{

    // Variables

    bool terminer(false);

    float vertices[] = {-0.5, -0.5, -1.0,   0.0, 0.5, -1.0,   0.5, -0.5, -1.0};

    float couleurs[] = {1.0, 0.0, 0.0,  0.0, 1.0, 0.0,  0.0, 0.0, 1.0};

    // Shader

    Shader shaderCouleur("Shaders/couleur3D.vert", "Shaders/couleur3D.frag");

    shaderCouleur.charger();

    // Matrices

    mat4 projection;

    mat4 modelview;

    projection = perspective(70.0, (double) m_largeurFenetre / m_hauteurFenetre, 1.0, 100.0);

    modelview = mat4(1.0);

    // Boucle principale

    while(!terminer)

    {

        // Gestion des évènements

        SDL_WaitEvent(&m_evenements);

        if(m_evenements.window.event == SDL_WINDOWEVENT_CLOSE)

            terminer = true;

        // Nettoyage de l'écran

        glClear(GL_COLOR_BUFFER_BIT);

        // Réinitialisation de la matrice modelview

        modelview = mat4(1.0);

        // On spécifie quel shader utiliser

        glUseProgram(shaderCouleur.getProgramID());

            // On remplie puis on active le tableau Vertex Attrib 0

            glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertices);

            glEnableVertexAttribArray(0);

            // Même chose avec le tableau Vertex Attrib 1

            glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, couleurs);

            glEnableVertexAttribArray(1);

            // On envoie les matrices au shader

            glUniformMatrix4fv(glGetUniformLocation(shaderCouleur.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));

            glUniformMatrix4fv(glGetUniformLocation(shaderCouleur.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));

            // On affiche le polygone

            glDrawArrays(GL_TRIANGLES, 0, 3);

            // On désactive les tableaux Vertex Attrib puisque l'on n'en a plus besoin

            glDisableVertexAttribArray(1);

            glDisableVertexAttribArray(0);

        // On n'utilise plus le shader

        glUseProgram(0);

        // Actualisation de la fenêtre

        SDL_GL_SwapWindow(m_fenetre);

    }

}

Vous devriez avoir cette fenêtre :

C'est tout ? C'est pratiquement la même chose qu'avant. :(

À première vue oui, il ne se passe rien de plus sauf peut-être une légère déformation de notre triangle. Mais sachez que dans votre carte graphique il se passe pas mal de choses. De plus, nous pouvons maintenant utiliser les transformations dans nos programmes et ce sont justement elles qui forment la base d'un jeu-vidéo. Sans transformations, nous serions encore en train de jouer au Pong. :lol:

Exemples de transformations

Introduction

On va se reposer un peu dans cette partie, il n'y a rien de nouveau, on utilisera juste ce que l'on a déjà codé.

Tout d'abord, on va changer un peu nos vertices pour avoir un triangle plus petit. Sinon nous ne pourrions pas vraiment voir ce que donnent les transformations :

float vertices[] = {0.0, 0.0, -1.0,  0.5, 0.0, -1.0,  0.0, 0.5, -1.0};

D'origine, voici la position du triangle :

Les transformations

La translation

Prenons un vecteur V de coordonnées (0.4, 0.0, 0.0) pour effectuer une translation du repère (matrice modelview) sur 0.4 unité sur l'axe X. Normalement, le triangle devrait se retrouver sur la gauche.

Pour faire une translation avec GLM, nous allons utiliser la méthode translate() dont voici le prototype :

mat4 translate(mat4 matrice, vec3 translation);

  • mat4 : matrice qui sera multipliée par la matrice de translation. Il s'agit ici de modelview

  • translation : objet vecteur à 3 coordonnées. Il correspond ici un point dans l'espace avec 3 coordonnées (x, y et z)

La méthode renvoie la matrice donnée en paramètre avec la translation ajoutée.

Pour contenter l'objet vec3 , nous allons juste appeler le constructeur vec3 avec les coordonnées de la translation que l'on veut faire, ici (0.4, 0.0, 0.0).

L'appel à la méthode ressemble au final à ceci :

// Translation

modelview = translate(modelview, vec3(0.4, 0.0, 0.0));

On identifie bien ce que fait cette ligne de code. On appelle la méthode translate() qui va modifier la matrice modelview à l'aide du vecteur de coordonnées (0.4, 0.0, 0.0).

Nous devons inclure cette ligne juste avant l'envoi des matrices au shader :

// On spécifie quel shader utiliser

glUseProgram(shaderCouleur.getProgramID());

    // Remplissage des tableaux Vertex Attrib 0 et 1

    ....

    // Translation

    modelview = translate(modelview, vec3(0.4, 0.0, 0.0));

    // On envoie les matrices au shader

    glUniformMatrix4fv(glGetUniformLocation(shaderCouleur.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));

    glUniformMatrix4fv(glGetUniformLocation(shaderCouleur.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));

    // Affichage

    ....

// On n'utilise plus le shader

glUseProgram(0);

Ce qui nous donne :

Le triangle ne connait aucun changement, nous n'avons pas modifié ses coordonnées. En revanche, le repère lui a changé. On l'a translaté selon le vecteur V, le triangle se trouve donc un peu plus loin.

Bon, sur une si petite forme on ne voit pas trop l'intérêt, mais sur un personnage de plus de 1000 vectices on voit la différence croyez-moi. :p

La rotation

Nous allons maintenant faire pivoter le triangle d'un angle de 60° selon l'axe Z. L'axe Z est pointé vers nous mais nous ne le voyons pas, nous verrons cela dans la deuxième partie du chapitre.

Nous utiliserons pour cela la méthode GLMrotate() :

mat4 rotate(mat4 matrice, double angle, vec3 axis);

Les paramètres sont sensiblement les mêmes :

  • matrice : matrice qui sera multipliée par la rotation. Il s'agit ici de modelview

  • angle : angle de la rotation

  • axis : vecteur représentant l'axe de la rotation

Pour effectuer une rotation de 60° sur l'axe Z, il suffit donc d'appeler la méthode rotate() de cette façon :

// Rotation

modelview = rotate(modelview, 60.0f, vec3(0.0, 0.0, 1.0));

Attention, cette méthode est capricieuse et demande une variable float . Il faut donc ajouter le suffit f à l'angle dans tous les cas.

On inclut cette ligne juste avant d'envoyer les matrices au shader :

// On spécifie quel shader utiliser

glUseProgram(shaderCouleur.getProgramID());

    // Remplissage des tableaux Vertex Attrib 0 et 1

    ....

    // Rotation

    modelview = rotate(modelview, 60.0f, vec3(0.0, 0.0, 1.0));

    // On envoie les matrices au shader

    glUniformMatrix4fv(glGetUniformLocation(shaderCouleur.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));

    glUniformMatrix4fv(glGetUniformLocation(shaderCouleur.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));

    // Affichage

    ....

// On n'utilise plus le shader

glUseProgram(0);

Hop, le triangle a pivoté de 60° (dans le sens trigonométrique). Encore une fois, on modifie le repère, on ne touche pas aux coordonnées du triangle.

L'homothétie

Allez encore une petit exemple. Nous allons faire une homothétie en multipliant par 2 les unités de mesure du repère. 1 unité sera plus longue, donc le triangle paraitra plus gros.

On appellera pour cela la méthode scale() :

mat4 scale(mat4 matrice, vec3 factors);

  • matrice : matrice qui sera multipliée par la rotation. Il s'agit ici de modelview

  • factors : vecteur contenant les 3 facteurs de redimensionnement pour les X, Y et Z

On appellera cette méthode ainsi pour agrandir le repère de 2 unités sur les axes X et Y. L'axe Z n'est pas encore visible pour nous, on le laisse donc comme ça.

// Homothétie

modelview = scale(modelview, vec3(2, 2, 1));

Et comme d'habitude, on l'appelle juste avant d'envoyer les matrices au shader :

// On spécifie quel shader utiliser

glUseProgram(shaderCouleur.getProgramID());

    // Remplissage des tableaux Vertex Attrib 0 et 1

    ....

    // Homothétie

    modelview = scale(modelview, vec3(2, 2, 1));

    // On envoie les matrices au shader

    glUniformMatrix4fv(glGetUniformLocation(shaderCouleur.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));

    glUniformMatrix4fv(glGetUniformLocation(shaderCouleur.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));

    // Affichage

    ....

// On n'utilise plus le shader

glUseProgram(0);

Le triangle est tellement gros qu'il sort de la fenêtre :lol: . Bref vous avez compris le principe.

Je vais même vous montrer un truc sympa avec les homothéties, on va inverser le triangle. C'est une technique utilisée dans les jeux-vidéo pour les effets de reflets (comme un effet de miroir, d'eau, ...) :

// Inversion du repère

modelview = scale(modelview, vec3(1, -1, 1));

Les axes X et Z ne sont pas modifiés, ils sont multipliés par 1. Mais l'axe Y lui est inversé, voici ce que ça donne :

Ordre des transformations

Je vous ai dit au tout début que l'ordre des transformations était important. Vous pouvez désormais le constater par vous-même. Tout d'abord, on va translater notre triangle selon le vecteur V de coordonnées (0.4, 0.0, 0.0) puis on va le faire pivoter d'un angle de 60° sur l'axe Z :

// Translation puis rotation

modelview = translate(modelview, vec3(0.4, 0, 0));

modelview = rotate(modelview, 60.0f, vec3(0, 0, 1));

Voici le rendu :

Maintenant, on fait l'inverse :

// Rotation puis translation

modelview = rotate(modelview, 60.0f, vec3(0, 0, 1));

modelview = translate(modelview, vec3(0.4, 0, 0));

Rendu :

On n'obtient pas la même chose dans les deux exemples. Faites attention à l'ordre des transformations c'est important. ;)

Multi-affichage

Courage on aborde le dernier point :p . Dans le deuxième chapitre nous avons vu comment afficher plusieurs triangles sans les transformations. Maintenant que nous les avons, on va voir comment se faciliter la vie.

Si vous affichez plusieurs fois la même chose, pas besoin de remplacer les valeurs du tableau car les valeurs sont toutes les mêmes :p . On ne fait qu'appliquer les transformations sur le repère puis on ré-affiche le triangle. Bien sûr, si on change les valeurs de la matrice modelview alors il faut la ré-envoyer au shader :

// On spécifie quel shader utiliser

glUseProgram(shaderCouleur.getProgramID());

    // On translate le premier triangle

    modelview = translate(modelview, vec3(0.4, 0, 0));

    // On envoie les matrices au shader

    glUniformMatrix4fv(glGetUniformLocation(shaderCouleur.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));

    glUniformMatrix4fv(glGetUniformLocation(shaderCouleur.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));

    /* ***** Affichage du premier triangle ***** */

    glDrawArrays(GL_TRIANGLES, 0, 3);

    // On fait pivoter le deuxième triangle puis on le translate

    modelview = rotate(modelview, 60.0f, vec3(0, 0, 1));

    modelview = translate(modelview, vec3(0.4, 0, 0));

    // On envoie une deuxième fois les matrices au shader

    glUniformMatrix4fv(glGetUniformLocation(shaderCouleur.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));

    glUniformMatrix4fv(glGetUniformLocation(shaderCouleur.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));

    /* ***** Affichage du deuxième triangle ***** */

    glDrawArrays(GL_TRIANGLES, 0, 3);

// On n'utilise plus le shader

glUseProgram(0);

Il est inutile d'activer le shader plusieurs fois si vous l'utilisez sur plusieurs polygones. Une seule fois suffit et n'oubliez pas de le désactiver après le dernier affichage.

Je vous conseille de faire des petits tests avec les transformations, entrainez-vous avec avant de passer à la deuxième partie de ce chapitre. ;)

Télécharger : Code Source C++ du Chapitre 6

Nous arrivons à la fin de cette première partie. Nous avons vu pas mal de notions et il est important que vous les compreniez. Encore une fois, amusez-vous à faire des transformations pour vous familiariser avec OpenGL. Si vous vous sentez prêts, passez à la deuxième partie (qui est plus facile à comprendre que cette partie ;) ).


La troisième dimension (Partie 2/2)

Dans cette deuxième partie, nous allons enfin faire ce que vous attendez tous : de la 3D. :D Cette partie sera plus facile que la première, il y aura de nouvelles notions à apprendre mais rien de bien compliqué. Encore une fois, si vous n'avez pas tout compris jusque là, je vous conseille de relire à tête reposée les chapitres précédents.

La caméra

Introduction

Dans le chapitre précédent, nous avons appris à faire interagir les matrices avec OpenGL et nous avons par la même occasion créé la matrice de projection. La bonne nouvelle, c'est que l'on connait déjà presque tout pour faire de la 3D. En effet, la troisième dimension est déjà implémentée dans nos programmes, nous l'avons vu en ajoutant la coordonnée Z à nos vertices. Seulement, nous sommes mal placés pour voir ce rendu en relief. Pour régler ce problème, il va falloir placer une chose indispensable à OpenGL : la caméra.

Eh oui, :p OpenGL fonctionne avec une caméra, exactement comme les films. Il faut donc placer une caméra qui va fixer un point pour que le spectateur (ou le joueur dans notre cas) puisse voir la scène.

Dans les versions précédentes d'OpenGL, la caméra était gérée par la même librairie que celle qui gérait la projection : la librairie GLU. Mais comme vous vous en douter, nous allons utiliser GLM pour la remplacer. :)

La méthode lookAt

L'ancienne fonction

Avant d'utiliser la librairie GLM, nous allons étudier l'ancienne fonction GLU qui gérait la caméra. Celle-ci permettait de placer la caméra au niveau d'un point dans l'espace. Ce point de vue nous permet de voir une scène en 3 dimensions au lieu des 2 dimensions dont nous avons l'habitude depuis le début du tuto.

La fonction en question s'appelle gluLookAt() et voici son prototype :

void gluLookAt(double eyeX, double eyeY, double eyeZ, double centerX, double centerY, double centerZ, double upX, double upY, double upZ);

Hola y'a trop de paramètres. :o

Si on les prend à part oui, mais vous remarquerez que les noms se ressemblent plus ou moins. En fait, il n'y a que 3 paramètres. Si on les prend par groupe de 3, on se retrouve avec 3 vecteurs bien distincts :

  • Le vecteur eye (œil) qui est un vecteur permetant de placer la caméra.

  • La vecteur center (centre) qui est le point que la caméra doit fixer. Il y a 3 coordonnées, le point se trouve donc dans un espace 3D.

  • Le vecteur axe qui est la verticale du repère.

Petite précision pour le dernier vecteur. En théorie, l'axe vertical est l'axe Y mais dans pas mal de jeux-vidéo on prend souvent l'axe Z. Personnellement, je fais de la résistance et je préfère utiliser l'axe Y comme axe vertical comme on nous l'a toujours appris depuis le collège. ;)

La nouvelle méthode

Pour remplacer cette ancienne fonction, nous allons utiliser une méthode de la librairie GLM qui s'appelle lookAt(). Son prototype est un peu plus compact que la fonction GLU :

mat4 lookAt(vec3 eye, vec3 center, vec3 up);

  • eye : vecteur permettant de placer la caméra

  • center : vecteur permettant d'indiquer le point fixé

  • up : vecteur représentant la verticale du repère

Vous remarquez que la méthode prend bien les 9 paramètres de gluLookAt(), elle les place juste dans 3 objets de type vec3 . ;)

Par ailleurs, elle renvoie une matrice toute neuve, elle ne modifie donc pas le contenu d'une matrice existante comme le font les méthodes de transformations.

Utilisation

Grâce à cette méthode, nous pouvons tout de même utiliser notre caméra.

On va d'ailleurs faire un petit test dès maintenant. Vous vous souvenez des deux triangles du chapitre précédent ? On va voir ce que ça donne si on adopte un "point de vue" en 3 dimensions. Bon je vous préviens, avec des triangles 2D ça va être moche mais ce sera déjà notre premier pas dans la 3D. ;)

Nous allons placer notre caméra au point de coordonnées (1, 1, 1) et celle-ci fixera le centre du repère, donc le point de coordonnées (0, 0, 0). Enfin, nous utiliserons l'axe Y comme axe vertical (vous pouvez utiliser celui que vous voulez). L'appel à la méthode lookAt() sera donc :

// Placement de la caméra
modelview = lookAt(vec3(1, 1, 1), vec3(0, 0, 0), vec3(0, 1, 0));

Nous ajoutons cette ligne de code juste après avoir ré-initialisé la matrice modelview :

// Boucle principale
while(!terminer)
{
    /* Gestion des évènements .... */
    // Nettoyage de la fenêtre
    glClear(GL_COLOR_BUFFER_BIT);
    // Ré-initialisation de la matrice et placement de la caméra
    modelview = mat4(1.0);
    modelview = lookAt(vec3(1, 1, 1), vec3(0, 0, 0), vec3(0, 1, 0));
    /* Rendu ... */
}

D'ailleurs, vous pouvez maintenant supprimer la ligne qui ré-initialise la matrice modelview avec les valeurs d'une matrice d'identité car la méthode lookAt() écrase complétement son ancien contenu et fait donc office de ré-initialisation. La ligne est donc inutile. :)

// Boucle principale
while(!terminer)
{
    /* Gestion des évènements .... */
    // Nettoyage de la fenêtre
    glClear(GL_COLOR_BUFFER_BIT);
    // Placement de la caméra
    modelview = lookAt(vec3(1, 1, 1), vec3(0, 0, 0), vec3(0, 1, 0));
    /* Rendu ... */
}

Voici ce que vous devriez obtenir :

Bon je vous avais prévenu c'est laid. :p Et pourtant on est bien en 3D :

Avec des triangles 2D on n'ira pas très loin mais ça va vite changer. Commençons enfin la partie la plus intéressante. :diable:

Affichage d'un Cube

Introduction

Cette fois ça y est, enfin de la 3D ! :D

La première bonne nouvelle est que l'on va se débarrasser enfin du repère que l'on a utilisé jusqu'à maintenant :

Nous allons désormais utiliser celui-ci :

Nos polygones ne seront plus limités à des coordonnées comprises entre 0 et 1.

Concrètement, qu'est-ce qu'il faut pour passer à la 3D ?

Vous ne vous en êtes peut-être pas rendu compte, mais depuis le début du tutoriel, vous avez déjà appris pas mal de choses. Petit à petit, vous avez appris tout ce qui est nécessaire pour commencer la programmation 3D :

  • Un shader

  • Des matrices

  • La projection

  • Une caméra

En réunissant intelligemment tout ça, on peut intégrer une troisième dimension à nos programmes. ;)

Affichage d'un cube

La partie théorique

On va commencer par un exercice simple qui réunira tout ce que l'on connait déjà ainsi que ce que l'on va voir maintenant. A la fin, on sera en mesure d'afficher notre premier modèle 3D : un cube (en couleur s'il vous plaît ;) ).

Avant de s'attaquer à la programmation, il est essentiel de faire un peu de théorie en voyant de quoi est composé un cube. Il suffit d'ouvrir un manuel de géométrie pour y lire une des propriétés principales du cube : "un cube est composé de 8 sommets".

Les chiffres en parenthèses représentent les coordonnées de chaque sommet. Une arrête mesure donc 2 unités.

Hum intéressant, on retrouve le mot sommet ( = vertex). Il y a 8 sommets, nous aurons donc besoin de 8 vertices :

Bien évidemment, il faudra les dédoubler pour afficher toutes nos faces comme nous le faisions avec le losange par exemple. Nous allons les étudier ensemble une par une en expliquant bien les étapes nécessaires.

D'ailleurs en parlant de ça, dans les anciennes versions d'OpenGL, on utilisait une primitive spécifique pour afficher un carré (comme GL_TRIANGLES pour les triangles) que l'on utilisait avec la fonction glDrawArrays(). Cependant, cette primitive a également été supprimée au même titre que les fonctions lentes vu que les cartes graphiques ne savent gérer nativement que des triangles.

Il existe heureusement une petite combine pour afficher des carrés sans cette primitive : il suffit de coller deux triangles rectangles entre eux :

Ne vous inquiétez pas, ce n'est pas plus lent à l'affichage même si on affiche deux choses au lieu d'une. Pour vous dire, que ce soit des sphères, des cubes ou des personnages, absolument tous les modèles 3D ne sont composés uniquement que de triangles. ;)

La première face

Allez on attaque la partie programmation. :D

Pour le moment, on va tout coder dans la boucle principale, ce sera plus simple à comprendre. Ensuite, nous migrerons proprement le code dans une classe dédiée.

On va commencer notre cube en affichant sa première face (celle du fond) :

Pour cela, nous aurons besoin de 6 sommets vu que nous avons besoin de deux triangles pour faire un carré. Si on regarde le schéma ci-dessus, on remarque que l'on peut faire un triangle avec les vertices 0, 1 et 2, et un autre avec les vertices 2, 3 et 0. Le tableau dont nous avons besoin ressemblera donc à ceci :

void SceneOpenGL::bouclePrincipale()
{
    // Vertices
    float vertices[] = {-1.0, -1.0, -1.0,   1.0, -1.0, -1.0,   1.0, 1.0, -1.0,     // Triangle 1
                        -1.0, -1.0, -1.0,   -1.0, 1.0, -1.0,   1.0, 1.0, -1.0};    // Triangle 2
}

Avant d'intégrer ce tableau, nous allons reprendre ensemble la boucle principale pour voir ce que donnerait le nouvel affichage. On commence par évidemment par déclarer les matrices projection et modelview :

void SceneOpenGL::bouclePrincipale()
{
    // Booléen terminer
    bool terminer(false);
    // Matrices
    mat4 projection;
    mat4 modelview;
    projection = perspective(70.0, (double) m_largeurFenetre / m_hauteurFenetre, 1.0, 100.0);
    modelview = mat4(1.0);
    // Boucle principale
    while(!terminer)
    {
        /* Rendu */
    }
}

Nous pouvons maintenant intégrer le tableau de vertices que l'on a trouvé juste avant. On le place après la déclaration des matrices :

void SceneOpenGL::bouclePrincipale()
{
    // Booléen terminer
    bool terminer(false);
    // Matrices
    mat4 projection;
    mat4 modelview;
    projection = perspective(70.0, (double) m_largeurFenetre / m_hauteurFenetre, 1.0, 100.0);
    modelview = mat4(1.0);
    // Vertices
    float vertices[] = {-1.0, -1.0, -1.0,   1.0, -1.0, -1.0,   1.0, 1.0, -1.0,     // Triangle 1
                        -1.0, -1.0, -1.0,   -1.0, 1.0, -1.0,   1.0, 1.0, -1.0};    // Triangle 2
    // Boucle principale
    while(!terminer)
    {
        /* Rendu */
    }
}

Pour admirer le rendu final il ne manque plus qu'à colorier les deux triangles. Nous utiliserons le rouge vu qu'elle est présente dans le schéma un peu plus haut.

Le tableau à utiliser pour cela doit permettre d'affecter une couleur pour chaque vertex. Nous en avons 6 pour le moment donc nous aurons besoin de 6 couleurs :

// Couleurs
float couleurs[] = {1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,           // Triangle 1
                    1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0};          // Triangle 2

On en profite au passage pour déclarer le shader qui permettra de colorier nos faces :

// Couleurs
float couleurs[] = {1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,           // Triangle 1
                    1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0};          // Triangle 2
// Shader
Shader shaderCouleur("Shaders/couleur3D.vert", "Shaders/couleur3D.frag");
shaderCouleur.charger();

Une fois toutes ces déclarations faites, nous devrons nous occuper de la caméra. Nous devons la déclarer juste avant la boucle principale et la placer à chaque tour de boucle au point de coordonnées (0, 0, 1). Elle sera en mode ' affichage 2D ' temporairement pour nous permettre de voir le carré correctement :

// Boucle principale
while(!terminer)
{
    // Gestion des évènements
    SDL_WaitEvent(&m_evenements);
    if(m_evenements.window.event == SDL_WINDOWEVENT_CLOSE)
        terminer = true;
    // Nettoyage de l'écran
    glClear(GL_COLOR_BUFFER_BIT);
    // Placement de la caméra
    modelview = lookAt(vec3(0, 0, 1), vec3(0, 0, 0), vec3(0, 1, 0));
    // Rendu
    ....
    // Actualisation de la fenêtre
    SDL_GL_SwapWindow(m_fenetre);
}

Pour le rendu en lui-même, il n'y a pas de grand changement à faire. On commence par activer le shader puis on envoie nos données aux tableaux Vertex Attrib, on sait le faire depuis un moment grâce à la fonction glVertexAttribPointer() :

// Activation du shader
glUseProgram(shaderCouleur.getProgramID());
    // Envoi des vertices
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertices);
    glEnableVertexAttribArray(0);
    // Envoi de la couleur
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, couleurs);
    glEnableVertexAttribArray(1);
    ....
// Désactivation du shader
glUseProgram(0);

Il ne nous reste plus qu'à envoyer les matrices projection et modelview au shader à l'aide des grosses fonctions. On pensera également à afficher le rendu grâce à la fonction glDrawArrays() et à désactiver les tableaux Vertex Attrib :

// Envoi des matrices
glUniformMatrix4fv(glGetUniformLocation(shaderCouleur.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));
glUniformMatrix4fv(glGetUniformLocation(shaderCouleur.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));
// Rendu
glDrawArrays(GL_TRIANGLES, 0, 6);
// Désactivation des tableaux
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(0);

N'oubliez pas de mettre le paramètre count à 6 pour afficher les deux triangles.

Si on résume tout ça :

void SceneOpenGL::bouclePrincipale()
{
    // Booléen terminer
    bool terminer(false);
    // Matrices
    mat4 projection;
    mat4 modelview;
    projection = perspective(70.0, (double) m_largeurFenetre / m_hauteurFenetre, 1.0, 100.0);
    modelview = mat4(1.0);
    // Vertices
    float vertices[] = {-1.0, -1.0, -1.0,   1.0, -1.0, -1.0,   1.0, 1.0, -1.0,     // Triangle 1
                        -1.0, -1.0, -1.0,   -1.0, 1.0, -1.0,   1.0, 1.0, -1.0};    // Triangle 2
    // Couleurs
    float couleurs[] = {1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,           // Triangle 1
                        1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0};          // Triangle 2
    // Shader
    Shader shaderCouleur("Shaders/couleur3D.vert", "Shaders/couleur3D.frag");
    shaderCouleur.charger();
    // Boucle principale
    while(!terminer)
    {
        // Gestion des évènements
        SDL_WaitEvent(&m_evenements);
        if(m_evenements.window.event == SDL_WINDOWEVENT_CLOSE)
            terminer = true;
        // Nettoyage de l'écran
        glClear(GL_COLOR_BUFFER_BIT);
        // Placement de la caméra
        modelview = lookAt(vec3(0, 0, 1), vec3(0, 0, 0), vec3(0, 1, 0));
        // Activation du shader
        glUseProgram(shaderCouleur.getProgramID());
            // Envoi des vertices
            glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertices);
            glEnableVertexAttribArray(0);
            // Envoi de la couleur
            glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, couleurs);
            glEnableVertexAttribArray(1);
            // Envoi des matrices
            glUniformMatrix4fv(glGetUniformLocation(shaderCouleur.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));
            glUniformMatrix4fv(glGetUniformLocation(shaderCouleur.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));
            // Rendu
            glDrawArrays(GL_TRIANGLES, 0, 6);
            // Désactivation des tableaux
            glDisableVertexAttribArray(1);
            glDisableVertexAttribArray(0);
        // Désactivation du shader
        glUseProgram(0);
        // Actualisation de la fenêtre
        SDL_GL_SwapWindow(m_fenetre);
    }
}

Si vous compilez ce code, vous devriez obtenir votre premier carré avec OpenGL :

Notre première face du cube est maintenant prête. :D

La deuxième face

Passons maintenant à la deuxième face du cube.

Pour commencer, on va placer la caméra un peu différemment de façon à voir la scène en 3 dimensions. Nous la positionnerons au point de coordonnées (3, 3, 3) et la ferons fixer l'origine du repère (0, 0, 0) :

// Placement de la caméra
modelview = lookAt(vec3(3, 3, 3), vec3(0, 0, 0), vec3(0, 1, 0));

La deuxième face que nous devons afficher doit ressembler à ceci :

On remarque qu'il faut prendre les vertices 5, 1 et 2 pour afficher le premier triangle, puis les vertices 2, 6 et 5 pour le second. Si on fait correspondre leurs coordonnées avec le schéma du début, on trouve les deux triangles suivants :

// Vertices
float vertices[] = {-1.0, -1.0, -1.0,   1.0, -1.0, -1.0,   1.0, 1.0, -1.0,     // Face 1
                    -1.0, -1.0, -1.0,   -1.0, 1.0, -1.0,   1.0, 1.0, -1.0,     // Face 1
                    1.0, -1.0, 1.0,   1.0, -1.0, -1.0,   1.0, 1.0, -1.0,       // Face 2
                    1.0, -1.0, 1.0,   1.0, 1.0, 1.0,   1.0, 1.0, -1.0};        // Face 2

J'ai volontairement aéré un peu le tableau pour le rendre plus lisible.

Évidemment, si nous utilisons de nouveaux vertices il faut leur associer une couleur. On ajoute donc 6 triplets au tableau couleurs spécialement pour eux. Nous utiliserons le vert pour différencier les deux faces :

// Couleurs
float couleurs[] = {1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,           // Face 1
                    1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,           // Face 1
                    0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,           // Face 2
                    0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0};          // Face 2

Pour afficher la nouvelle face, il suffit de modifier le fameux paramètre count pour qu'il prenne en compte les 6 nouveaux sommets. On lui donne donc la valeur 6 + 6 = 12 :

// Affichage des triangles
glDrawArrays(GL_TRIANGLES, 0, 12);

Si vous compilez tout ça, vous devriez obtenir :

On a maintenant la deuxième face, et en 3D s'il vous plaît. ;)

La troisième face

Allez, on continue avec la troisième face. On commence par définir les sommets dont nous aurons besoin :

Comme d'habitude, on fait correspondre ces sommets avec leurs coordonnées pour trouver le tableau suivant :

// Vertices
float vertices[] = {-1.0, -1.0, -1.0,   1.0, -1.0, -1.0,   1.0, 1.0, -1.0,     // Face 1
                    -1.0, -1.0, -1.0,   -1.0, 1.0, -1.0,   1.0, 1.0, -1.0,     // Face 1
                    1.0, -1.0, 1.0,   1.0, -1.0, -1.0,   1.0, 1.0, -1.0,       // Face 2
                    1.0, -1.0, 1.0,   1.0, 1.0, 1.0,   1.0, 1.0, -1.0,         // Face 2
                    -1.0, -1.0, 1.0,   1.0, -1.0, 1.0,   1.0, -1.0, -1.0,      // Face 3
                    -1.0, -1.0, 1.0,   -1.0, -1.0, -1.0,   1.0, -1.0, -1.0};   // Face 3

Ensuite, on fait correspondre une nouvelle couleur aux deux nouveaux triangles formés par les vertices. On utilisera le bleu comme sur le schéma :

// Couleurs
float couleurs[] = {1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,           // Face 1
                    1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,           // Face 1
                    0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,           // Face 2
                    0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,           // Face 2
                    0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,           // Face 3
                    0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0};          // Face 3

Pour finir, on doit modifier une fois de plus le paramètre count pour prendre en compte les nouveaux vertices. Sa valeur passe de 12 à 18 :

// Affichage des triangles
glDrawArrays(GL_TRIANGLES, 0, 18);

En compilant le nouveau code, on obtient :

Pourquoi ça s'affiche comme ça ? Y'a un bug ?

Non, ce n'est pas un bug, et vous allez vite comprendre pourquoi la face ne s'affiche pas correctement.

Le Depth Buffer

C'est la première fois que nous avons un problème d'affichage, et c'est tout à fait normal puisqu'avant nous n'avions jamais eu de formes superposées l'une sur l'autre. Ici, la face bleue et la face verte se superposent, et pour OpenGL c'est un problème car il ne sait pas quelle forme doit être visible et quelle forme doit être cachée. N'oubliez pas qu'un ordinateur est très bête, il ne sait rien faire à part calculer.

Le Depth Buffer (ou Tampon de profondeur ) est ce qui va permettre à OpenGL de comprendre ce qu'il doit afficher et ce qu'il doit masquer. Si un pixel de modèle se trouve derrière un autre alors le Depth Buffer indiquera à OpenGL : "N'affiche pas ce pixel, mais affiche celui-ci car il est devant".

Je vous avais déjà parlé brièvement de cette notion dans le chapitre sur les shaders, notamment avec ce schéma :

La dernière étape du pipeline 3D était le Test de profondeur. C'est justement là qu'intervient le Depth Buffer. Heureusement pour nous, il ne faudra pas gérer ce tampon par nous-même, cette fonctionnalité n'a pas été supprimée avec la nouvelle version d'OpenGL. :) Avant que l'API puisse se servir de ce tampon, il faut l'activer grâce à la fonction glEnable().

void glEnable(GLenum cap);

Nous reverrons plusieurs fois cette fonction qui permet d'activer certaines fonctionnalités d'OpenGL. Le paramètre cap est justement la fonctionnalité à activer. Pour le Depth Buffer , on lui donne le paramètre GL_DEPTH_TEST. On va donc appeler la fonction comme ceci dans la méthode initGL () juste après l'initialisation de la librairie GLEW :

bool SceneOpenGL::initGL()
{
    #ifdef WIN32
        /* ***** Initialisation de la librairie GLEW ***** */
    #endif
    // Activation du Depth Buffer
    glEnable(GL_DEPTH_TEST);
    // Tout s'est bien passé, on retourne true
    return true;

Même si vous n'êtes pas sous Windows, vous devez initialiser le Depth Buffer !

A chaque tour de boucle, il faudra (comme avec les couleurs et la matrice modelview) ré-initialiser le Depth Buffer afin de vider toute traces de l'affichage précédent. Pour ça, il suffit d'ajouter un paramètre à une fonction que l'on utilise déjà : glClear(). Rappelez-vous que cette fonction permet de vider les buffers qu'on lui donne en paramètre.

Pour le moment, on ne lui donne que le paramètre GL_COLOR_BUFFER_BIT pour effacer ce qui se trouve à l'écran. Maintenant, on va ajouter le paramètre GL_DEPTH_BUFFER_BIT pour effacer le Depth Buffer :

// Nettoyage de la fenêtre et du Depth Buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

Maintenant que l'on sait ce qu'est le Depth Buffer, on peut régler notre problème d'affichage. Après avoir placé la fonction glEnable(), vous devriez obtenir ceci :

Les trois dernières faces

Courage nous sommes presque au bout, il ne reste plus qu'à afficher les 3 dernières faces. Et je fais bien de vous dire courage car je vais vous demander de terminer les trois dernières faces tous seuls. :p

Je vais vous donner les schémas contenant les vertices et la couleur dont vous aurez besoin pour chaque face, pour le reste ce sera à vous de le faire. Il n'y a rien de compliqué en plus, nous avons déjà fait la moité du cube ensemble et vous n'aurez pas de surprise d'affichage car nous savons maintenant gérer le Depth Buffer .

Les tableaux finaux peuvent vous paraitre gros et moches, c'est tout à fait normal. Pour vous dire, les vertices de personnages ou de décors sont infiniment plus moches au vu de leur nombre de données. :lol: Mais l'avantage avec eux c'est que nous n'avons pas à les coder à la main, nous ne les voyons même pas d'ailleurs. Mais bon, ça ça sera pour plus tard. Pour le moment, je vous demande de finir notre fameux cube à l'aide des schémas suivants :

Allez hop à votre clavier !

.....

On passe à la correction. Le principe reste le même, il faut juste faire correspondre les vertices et les couleurs. Voici ce que donne les tableaux finaux :

Vos vertices peuvent parfaitement être déclarés dans un ordre différent de celui que je donne. Ce n'est pas grave du moment que vous affichez des carrés correctement :

float vertices[] = {-1.0, -1.0, -1.0,   1.0, -1.0, -1.0,   1.0, 1.0, -1.0,     // Face 1
                    -1.0, -1.0, -1.0,   -1.0, 1.0, -1.0,   1.0, 1.0, -1.0,     // Face 1
                    1.0, -1.0, 1.0,   1.0, -1.0, -1.0,   1.0, 1.0, -1.0,       // Face 2
                    1.0, -1.0, 1.0,   1.0, 1.0, 1.0,   1.0, 1.0, -1.0,         // Face 2
                    -1.0, -1.0, 1.0,   1.0, -1.0, 1.0,   1.0, -1.0, -1.0,      // Face 3
                    -1.0, -1.0, 1.0,   -1.0, -1.0, -1.0,   1.0, -1.0, -1.0,    // Face 3
                    -1.0, -1.0, 1.0,   1.0, -1.0, 1.0,   1.0, 1.0, 1.0,        // Face 4
                    -1.0, -1.0, 1.0,   -1.0, 1.0, 1.0,   1.0, 1.0, 1.0,        // Face 4
                    -1.0, -1.0, -1.0,   -1.0, -1.0, 1.0,   -1.0, 1.0, 1.0,     // Face 5
                    -1.0, -1.0, -1.0,   -1.0, 1.0, -1.0,   -1.0, 1.0, 1.0,     // Face 5
                    -1.0, 1.0, 1.0,   1.0, 1.0, 1.0,   1.0, 1.0, -1.0,         // Face 6
                    -1.0, 1.0, 1.0,   -1.0, 1.0, -1.0,   1.0, 1.0, -1.0};      // Face 6

L'ordre des couleurs peut, lui aussi, être différent. Ce qui compte, c'est que les lignes de couleur correspondent à leurs lignes de vertex :

// Couleurs
float couleurs[] = {1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,           // Face 1
                    1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,           // Face 1
                    0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,           // Face 2
                    0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,           // Face 2
                    0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,           // Face 3
                    0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,           // Face 3
                    1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,           // Face 4
                    1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,           // Face 4
                    0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,           // Face 5
                    0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,           // Face 5
                    0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,           // Face 6
                    0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0};          // Face 6

Au niveau de la fonction glDrawArrays(), vous devriez avoir la valeur du paramètre count à 36 :

// Affichage des triangles
glDrawArrays(GL_TRIANGLES, 0, 36);

A la fin, vous devriez avoir votre premier modèle 3D !

Magnifique n'est-ce pas ?

La classe Cube

La classe Cube

Le header

Après tous les efforts que nous avons fournis dans la partie précédente, nous avons enfin pu afficher notre premier modèle 3D. :D
Vous avez remarqué que, mises à part les matrices, le processus était le même que pour les modèles 2D. C'est-dire-à qu'il nous a suffi d'activer un shader, d'envoyer les données aux tableaux Vertex Attrib, puis d'afficher le tout avec glDrawArrays().

Ce que nous allons faire maintenant va nous permettre de nettoyer un peu la boucle principale. En effet, comme je vous l'ai précisé dans la correction du cube, les tableaux de vertices et de couleurs sont assez indigestes. Il serait donc judicieux de créer une classe dédiée au cube de façon à enfermer ces lignes de code à l'intérieur. Nous gagnerions en lisibilité et de plus, nous pourrions créer des cubes à l'infini en seulement quelques lignes de code !

Notre nouvel objectif va donc être la création d'une classe Cube dont on se servira pour la suite du tutoriel.

Et nous allons commencer tout de suite par le header. Celui-ci sera placé dans un fichier que nous appellerons Cube.h et devra contenir la déclaration de la classe ainsi que les inclusions nécessaires pour OpenGL, les shaders et les matrices :

#ifndef DEF_CUBE
#define DEF_CUBE
// Includes OpenGL
#ifdef WIN32
#include <GL/glew.h>
#else
#define GL3_PROTOTYPES 1
#include <GL3/gl3.h>
#endif
// Includes GLM
#include <glm/glm.hpp>
#include <glm/gtx/transform.hpp>
#include <glm/gtc/type_ptr.hpp>
// Includes
#include "Shader.h"
// Classe Cube
class Cube
{
    public:
    private:
};
#endif

N'oubliez pas les en-têtes de GLM. ;)

Au niveau des attributs, cette classe devra contenir tous les éléments dont nous avons eu besoin pour afficher notre modèle, à savoir :

  • Un objet de type Shader

  • Un tableau flottant de vertices

  • Un tableau flottant de couleurs

Les matrices ne font pas partie de cette liste car nous n'en avons besoin qu'au moment de l'affichage, inutile donc de créer des attributs pour elles. Nous les enverrons en tant que paramètres dans une méthode.

Si on rassemble tout ça, on trouve :

// Attributs
Shader m_shader;
float m_vertices[108];
float m_couleurs[108];

La taille 108 des deux tableaux vient de la multiplication du nombre de vertices nécessaires pour un cube ( 36 ) par leur nombre de coordonnées ( 3 ), ce qui fait 36 vertices x 3 coordonnées = 108 cases.

Passons maintenant au constructeur, celui-ci aura besoin de trois paramètres : la taille du cube que l'on veut afficher ainsi que les deux codes sources du shader à utiliser.

Nous n'avons pas intégré la possibilité de choisir les dimensions avant afin d'éviter d’alourdir le code qui était déjà assez dense. Mais vu qu'à présent nous codons une classe, il serait quand même plus agréable de pouvoir créer des cubes de n'importe quelle taille en modifiant simplement une seule valeur. :)

Nous prendrons une variable de type float pour gérer cette taille. Quant aux autres paramètres, vous savez déjà que ce seront des string :

Cube(float taille, std::string const vertexShader, std::string const fragmentShader);

On profite de ce passage pour déclarer le destructeur de la classe :

~Cube();

Après tous ces petits ajouts, nous nous retrouvons devant le header complet de la classe Cube :

#ifndef DEF_CUBE
#define DEF_CUBE
// Includes OpenGL
#ifdef WIN32
#include <GL/glew.h>
#else
#define GL3_PROTOTYPES 1
#include <GL3/gl3.h>
#endif
// Includes GLM
#include <glm/glm.hpp>
#include <glm/gtx/transform.hpp>
#include <glm/gtc/type_ptr.hpp>
// Includes
#include "Shader.h"
// Classe Cube
class Cube
{
    public:
    Cube(float taille, std::string const vertexShader, std::string const fragmentShader);
    ~Cube();
    private:
    Shader m_shader;
    float m_vertices[108];
    float m_couleurs[108];
};
#endif
Le constructeur

Passons maintenant à l'implémentation de la classe avec en premier lieu le constructeur.

Celui-ci débute avec l'initialisation des attributs. Nous en avons 3 mais seul le shader peut vraiment être initialisé ici car les vertices et les couleurs sont des tableaux, nous ne pouvons donc pas le faire directement après les deux points ":". Nous lui donnons les deux codes sources reçus en paramètres :

Cube::Cube(float taille, std::string const vertexShader, std::string const fragmentShader) : m_shader(vertexShader, fragmentShader)
{
}

Ce qui se trouve entre les accolades commence également par le shader car nous devons appeler sa méthode charger() de façon à le charger complétement :

Cube::Cube(float taille, std::string const vertexShader, std::string const fragmentShader) : m_shader(vertexShader, fragmentShader)
{
    // Chargement du shader
    m_shader.charger();
}

Le shader est maintenant initialisé et prêt à l'emploi.

On passe maintenant au plus délicat : l'initialisation des tableaux de vertices et de couleurs. Il y a deux manières de faire en C++ :

  • Soit on initialise leurs valeurs une par une (donc on initialise séparément les 24 valeurs d'un tableau de 24 cases par exemple).

  • Soit on déclare des tableaux temporaires contenant les valeurs désirées, puis on utilise des boucles pour les affecter aux vrais tableaux.

Si on utilise la première méthode, il nous faudrait 108 lignes de code juste pour initialiser les vertices, et le double si on s'occupe aussi des couleurs. Avouez tout de même que c'est méchamment fastidieux, surtout si on doit le faire deux fois. Je pense donc que vous serez d'accord pour utiliser la seconde méthode. :lol:

Nous devons donc utiliser un tableau temporaire qui va contenir tous les vertices du cube, nous l'appellerons verticesTmp[] :

// Vertices temporaires
float verticesTmp[] = {-1.0, -1.0, -1.0,   1.0, -1.0, -1.0,   1.0, 1.0, -1.0,     // Face 1
                       -1.0, -1.0, -1.0,   -1.0, 1.0, -1.0,   1.0, 1.0, -1.0,     // Face 1
                       1.0, -1.0, 1.0,   1.0, -1.0, -1.0,   1.0, 1.0, -1.0,       // Face 2
                       1.0, -1.0, 1.0,   1.0, 1.0, 1.0,   1.0, 1.0, -1.0,         // Face 2
                       -1.0, -1.0, 1.0,   1.0, -1.0, 1.0,   1.0, -1.0, -1.0,      // Face 3
                       -1.0, -1.0, 1.0,   -1.0, -1.0, -1.0,   1.0, -1.0, -1.0,    // Face 3
                       -1.0, -1.0, 1.0,   1.0, -1.0, 1.0,   1.0, 1.0, 1.0,        // Face 4
                       -1.0, -1.0, 1.0,   -1.0, 1.0, 1.0,   1.0, 1.0, 1.0,        // Face 4
                       -1.0, -1.0, -1.0,   -1.0, -1.0, 1.0,   -1.0, 1.0, 1.0,     // Face 5
                       -1.0, -1.0, -1.0,   -1.0, 1.0, -1.0,   -1.0, 1.0, 1.0,     // Face 5
                       -1.0, 1.0, 1.0,   1.0, 1.0, 1.0,   1.0, 1.0, -1.0,         // Face 6
                       -1.0, 1.0, 1.0,   -1.0, 1.0, -1.0,   1.0, 1.0, -1.0};      // Face 6

Les vertices en l'état n'ont que bien peu d'intérêt car ils ne prennent pas en compte le paramètre taille du constructeur. Pour régler ce problème, nous allons simplement remplacer toutes les occurrences de la valeur 1.0 par le paramètre taille lui-même. C'est un peu long à faire mais la fonctionnalité "Find and Replace" de votre IDE devrait vous faciliter un peu la tâche. ;)

Le tableau remanié devrait ressembler à celui-ci :

// Vertices temporaires
float verticesTmp[] = {-taille, -taille, -taille,   taille, -taille, -taille,   taille, taille, -taille,     // Face 1
                       -taille, -taille, -taille,   -taille, taille, -taille,   taille, taille, -taille,     // Face 1
                       taille, -taille, taille,   taille, -taille, -taille,   taille, taille, -taille,       // Face 2
                       taille, -taille, taille,   taille, taille, taille,   taille, taille, -taille,         // Face 2
                       -taille, -taille, taille,   taille, -taille, taille,   taille, -taille, -taille,      // Face 3
                       -taille, -taille, taille,   -taille, -taille, -taille,   taille, -taille, -taille,    // Face 3
                       -taille, -taille, taille,   taille, -taille, taille,   taille, taille, taille,        // Face 4
                       -taille, -taille, taille,   -taille, taille, taille,   taille, taille, taille,        // Face 4
                       -taille, -taille, -taille,   -taille, -taille, taille,   -taille, taille, taille,     // Face 5
                       -taille, -taille, -taille,   -taille, taille, -taille,   -taille, taille, taille,     // Face 5
                       -taille, taille, taille,   taille, taille, taille,   taille, taille, -taille,         // Face 6
                       -taille, taille, taille,   -taille, taille, -taille,   taille, taille, -taille};      // Face 6

Il reste encore une petite modification à faire. Si on regarde de plus près nos données, on remarque que les vertices vont de -taille à +taille. Cet intervalle fait que notre cube est multiplié par 2. :(

Pour éviter cela, il faut diviser le paramètre taille par 2 avant de remplir le tableau. Ainsi, notre cube qui devait être multiplié par 2 ne le sera plus :

// Division du paramètre taille
taille /= 2;
// Vertices temporaires
float verticesTmp[] = {-taille, -taille, -taille,   taille, -taille, -taille,   taille, taille, -taille,     // Face 1
                       -taille, -taille, -taille,   -taille, taille, -taille,   taille, taille, -taille,     // Face 1
                       taille, -taille, taille,   taille, -taille, -taille,   taille, taille, -taille,       // Face 2
                       taille, -taille, taille,   taille, taille, taille,   taille, taille, -taille,         // Face 2
                       -taille, -taille, taille,   taille, -taille, taille,   taille, -taille, -taille,      // Face 3
                       -taille, -taille, taille,   -taille, -taille, -taille,   taille, -taille, -taille,    // Face 3
                       -taille, -taille, taille,   taille, -taille, taille,   taille, taille, taille,        // Face 4
                       -taille, -taille, taille,   -taille, taille, taille,   taille, taille, taille,        // Face 4
                       -taille, -taille, -taille,   -taille, -taille, taille,   -taille, taille, taille,     // Face 5
                       -taille, -taille, -taille,   -taille, taille, -taille,   -taille, taille, taille,     // Face 5
                       -taille, taille, taille,   taille, taille, taille,   taille, taille, -taille,         // Face 6
                       -taille, taille, taille,   -taille, taille, -taille,   taille, taille, -taille};      // Face 6

Piouf le plus dur est derrière nous. :)

Le tableau de couleurs quant à lui est plus simple à faire puisqu'il suffit juste de reprendre celui que nous utilisions avant. Nous modifierons juste son nom en l'appelant couleursTmp vu qu'il s'agit de données temporaires :

// Couleurs temporaires
float couleursTmp[] = {1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,           // Face 1
                       1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,           // Face 1
                       0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,           // Face 2
                       0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,           // Face 2
                       0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,           // Face 3
                       0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,           // Face 3
                       1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,           // Face 4
                       1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,           // Face 4
                       0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,           // Face 5
                       0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,           // Face 5
                       0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,           // Face 6
                       0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0};          // Face 6

Maintenant que nos données sont déclarées, il ne manque plus qu'à les transférer dans nos attributs.

Pour cela, nous allons utiliser une boucle qui va s'exécuter 108 fois, ce qui permettra donc de copier non seulement les coordonnées des sommets mais aussi les composantes des couleurs. En effet, les tableaux font tous les deux la même taille, on peut alors n'utiliser qu'une seule boucle. :)

// Copie des valeurs dans les tableaux finaux
for(int i(0); i < 108; i++)
{
    m_vertices[i] = verticesTmp[i];
    m_couleurs[i] = couleursTmp[i];
}

Si on réunit tous les bouts de code :

Cube::Cube(float taille, std::string const vertexShader, std::string const fragmentShader) : m_shader(vertexShader, fragmentShader)
{
    // Chargement du shader
    m_shader.charger();
    // Division de la taille
    taille /= 2;
    // Vertices temporaires
    float verticesTmp[] = {-taille, -taille, -taille,   taille, -taille, -taille,   taille, taille, -taille,     // Face 1
                           -taille, -taille, -taille,   -taille, taille, -taille,   taille, taille, -taille,     // Face 1
                           taille, -taille, taille,   taille, -taille, -taille,   taille, taille, -taille,       // Face 2
                           taille, -taille, taille,   taille, taille, taille,   taille, taille, -taille,         // Face 2
                           -taille, -taille, taille,   taille, -taille, taille,   taille, -taille, -taille,      // Face 3
                           -taille, -taille, taille,   -taille, -taille, -taille,   taille, -taille, -taille,    // Face 3
                           -taille, -taille, taille,   taille, -taille, taille,   taille, taille, taille,        // Face 4
                           -taille, -taille, taille,   -taille, taille, taille,   taille, taille, taille,        // Face 4
                           -taille, -taille, -taille,   -taille, -taille, taille,   -taille, taille, taille,     // Face 5
                           -taille, -taille, -taille,   -taille, taille, -taille,   -taille, taille, taille,     // Face 5
                           -taille, taille, taille,   taille, taille, taille,   taille, taille, -taille,         // Face 6
                           -taille, taille, taille,   -taille, taille, -taille,   taille, taille, -taille};      // Face 6
    // Couleurs temporaires
    float couleursTmp[] = {1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,           // Face 1
                           1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,           // Face 1
                           0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,           // Face 2
                           0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,           // Face 2
                           0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,           // Face 3
                           0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,           // Face 3
                           1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,           // Face 4
                           1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,           // Face 4
                           0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,           // Face 5
                           0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,           // Face 5
                           0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,           // Face 6
                           0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0};          // Face 6
    // Copie des valeurs dans les tableaux finaux
    for(int i(0); i < 108; i++)
    {
        m_vertices[i] = verticesTmp[i];
        m_couleurs[i] = couleursTmp[i];
    }
}
Le destructeur

Comme vous le savez déjà, un destructeur est une méthode appelée au moment de la destruction de l'objet. Il permet de libérer la mémoire prise par l'objet au cours de sa vie, en particulier la mémoire allouée dynamiquement.

Heureusement pour nous, dans notre cas nous ne faisons aucune allocation dynamique. :p Le destructeur va donc être vide :

Cube::~Cube()
{
}

L'objet de type Shader sera désalloué automatiquement vu que c'est un objet à part, inutile de tenter de le faire manuellement.

La méthode afficher

Comme son nom l'indique, la méthode afficher () va nous permettre ... d'afficher notre cube. Son prototype est assez simple :

void afficher(glm::mat4 &projection, glm::mat4 &modelview);

Elle prend en paramètre une référence sur les deux matrices que l'on connait si bien maintenant. Elles sont indispensables pour afficher un modèle 3D. Dans cette méthode, nous en avons besoin pour les envoyer au shader.

Son implémentation va être ultra simple pour nous : il suffit juste de copier le code contenu entre l'activation et la désactivation du shader. Ce qui comprend :

  • Le shader évidemment

  • L'envoi des matrices

  • L'utilisation des tableaux Vertex Attrib

  • L'appel à la fonction glUseProgram()

Le code à copier est le suivant :

// Activation du shader
glUseProgram(shaderCouleur.getProgramID());
    // Envoi des vertices
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertices);
    glEnableVertexAttribArray(0);
    // Envoi de la couleur
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, couleurs);
    glEnableVertexAttribArray(1);
    // Envoi des matrices
    glUniformMatrix4fv(glGetUniformLocation(shaderCouleur.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));
    glUniformMatrix4fv(glGetUniformLocation(shaderCouleur.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));
    // Rendu
    glDrawArrays(GL_TRIANGLES, 0, 36);
    // Désactivation des tableaux
    glDisableVertexAttribArray(1);
    glDisableVertexAttribArray(0);
// Désactivation du shader
glUseProgram(0);

Avant d'aller plus loin, il nous faut modifier le nom des variables anciennement utilisées. Ainsi :

  • L'objet shaderCouleur devient m_shader

  • Le tableau vertices devient m_vertices

  • Le tableau couleurs devient m_couleurs

Une fois le nom des variables modifié, on se retrouve avec la méthode afficher() suivante :

void Cube::afficher(glm::mat4 &projection, glm::mat4 &modelview)
{
    // Activation du shader
    glUseProgram(m_shader.getProgramID());
        // Envoi des vertices
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, m_vertices);
        glEnableVertexAttribArray(0);
        // Envoi de la couleur
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, m_couleurs);
        glEnableVertexAttribArray(1);
        // Envoi des matrices
        glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));
        glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));
        // Rendu
        glDrawArrays(GL_TRIANGLES, 0, 36);
        // Désactivation des tableaux
        glDisableVertexAttribArray(1);
        glDisableVertexAttribArray(0);
    // Désactivation du shader
    glUseProgram(0);
}

N'oubliez pas d'inclure la ligne "using namespace glm;" au début de votre fichier .cpp de façon à pouvoir vous passer du préfixe glm::.

J'adore quand le copier-coller fonctionne aussi facilement. Nous avions déjà fait le plus gros avant, il ne nous restait plus qu'à adapter le nom des attributs.

La boucle principale

Il ne reste plus qu'une seule chose à faire : déclarer un objet de type Cube et utiliser sa méthode afficher () dans la boucle principale. On efface donc tout ce qu'on à fait avant (vertices, couleurs, affichage, ...). On ne doit garder que ceci :

void SceneOpenGL::bouclePrincipale()
{
    // Variable
    bool terminer(false);
    // Matrices
    mat4 projection;
    mat4 modelview;
    projection = perspective(70.0, (double) m_largeurFenetre / m_hauteurFenetre, 1.0, 100.0);
    modelview = mat4(1.0);
    // Boucle principale
    while(!terminer)
    {
        // Gestion des évènements
        SDL_WaitEvent(&m_evenements);
        if(m_evenements.window.event == SDL_WINDOWEVENT_CLOSE)
            terminer = true;
        // Nettoyage de l'écran
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        // Placement de la caméra
        modelview = lookAt(vec3(3, 3, 3), vec3(0, 0, 0), vec3(0, 1, 0));

        // Rendu (Rien pour le moment)
        ....
        // Actualisation de la fenêtre
        SDL_GL_SwapWindow(m_fenetre);
    }
}

Pensez dès maintenant à ajouter le header "Cube.h" dans la classe SceneOpenGL .

Ensuite, on déclare notre objet de type Cube qui sera initialisé automatiquement avec le constructeur. Nous lui donnerons la valeur 2.0 pour le paramètre taille (ou une autre qui vous plaira :p ) ainsi que les string "Shaders/couleur3D.vert" et "Shaders/couleur3D.frag" pour le shader.

void SceneOpenGL::bouclePrincipale()
{
    // Variable
    bool terminer(false);
    // Matrices
    mat4 projection;
    mat4 modelview;
    projection = perspective(70.0, (double) m_largeurFenetre / m_hauteurFenetre, 1.0, 100.0);
    modelview = mat4(1.0);
    // Déclaration d'un objet Cube
    Cube cube(2.0, "Shaders/couleur3D.vert", "Shaders/couleur3D.frag");
    // Boucle principale
    while(!terminer)
    {
        ....
    }
}

Enfin, on utilise la méthode afficher () dans la boucle principale en donnant les matrices en paramètres :

while(!terminer)
{
    // Gestion des évènements
    SDL_WaitEvent(&m_evenements);
    if(m_evenements.window.event == SDL_WINDOWEVENT_CLOSE)
        terminer = true;
    // Nettoyage de l'écran
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    // Placement de la caméra
    modelview = lookAt(vec3(3, 3, 3), vec3(0, 0, 0), vec3(0, 1, 0));
    // Affichage du cube
    cube.afficher(projection, modelview);
    // Actualisation de la fenêtre
    SDL_GL_SwapWindow(m_fenetre);
}

Si vous compilez tout ça, vous obtiendrez le même résultat que tout à l'heure mais cette fois-ci, vous avez un véritable objet C++ permettant d'afficher un cube.

Pseudo-animation

Attention, cette sous-partie s'appelle bien " pseudo-animation " et pas animation tout court. On va apprendre à faire pivoter notre cube pour voir toutes ses faces, et ce grâce à la méthode rotate() de la librairie GLM.

Le principe est simple : on incrémente un angle à chaque tour de boucle puis on fait pivoter le cube avec cet angle qui change sans arrêt donnant ainsi une impression de mouvement. Pour le moment, il faudra bouger la souris dans la fenêtre SDL pour constater la rotation puisque nous utilisons la fonction SDL_WaitEvent() qui bloque le programme quand il n'y a pas d'évènements.

On commence par déclarer un angle de type float que l'on incrémentera à chaque tour boucle :

void SceneOpenGL::bouclePrincipale()
{
    ....
    // Variable angle
    float angle(0.0);
    // Boucle principale
    while(!terminer)
    {
        ....
    }
}

Ensuite, on incrémente l'angle de rotation à chaque tour de boucle. Petite précision, l'angle atteindra forcément les 360° vu qu'on l'incrémente sans arrêt. A chaque fois qu'il atteindra 360°, il faudra donc le remettre à zéro. Il est inutile d'avoir un angle incompréhensible de 1604°. :lol:

// Incrémentation de l'angle
angle += 4.0;
if(angle >= 360.0)
    angle -= 360.0;

Une fois l'angle défini, il suffit de faire pivoter le repère en appelant la méthode rotate () de la matrice modelview :

// Rotation du repère
modelview = rotate(modelview, angle, vec3(0, 1, 0));

Si on place ce code au bon endroit :

while(!terminer)
{
    // Gestion des évènements
    ....
    // Placement de la caméra
    modelview = lookAt(vec3(3, 3, 3), vec3(0, 0, 0), vec3(0, 1, 0));
    // Incrémentation de l'angle
    angle += 4.0;
    if(angle >= 360.0)
        angle -= 360.0;
    // Rotation du repère
    modelview = rotate(modelview, angle, vec3(0, 1, 0));
    // Affichage du cube
    cube.afficher(projection, modelview);
    // Fin de la boucle
    ....
}

Avec ce code, votre cube devrait tourner sur lui-même. :p

Le Frame Rate

Calcul du FPS

Je profite de ce chapitre pour vous introduire une nouvelle notion : celle du Frame Rate.

Le Frame Rate est le nombre de fois par seconde où la boucle principale est exécutée. En France, on prend généralement une valeur 50 fps (Frames Per Second ou Frames par seconde). Pour le moment, notre jeu fonctionne avec 0 fps étant donné que l'on utilise une fonction qui bloque le programme : SDL_WaitEvent(). La boucle ne s'exécute que si on fait quelque chose, sinon le jeu est bloqué.

Or, dans un jeu-vidéo, si on ne touche pas à la souris il se passe quand même quelque chose. Nous allons régler ce problème en introduisant la notion de frame rate. La première chose à faire est de changer la fonction SDL_WaitEvent() par la fonction SDL_PollEvent() qui, elle, ne bloque pas le programme :

SDL_PollEvent(&m_evenements);

L'utilisation de cette fonction va cependant nous poser un problème : le CPU va être totalement surchargé. Heureusement, le frame rate est là pour nous aider. Grâce à lui, nous n'exécuterons pas la boucle principale des centaines de fois par seconde mais uniquement quelques dizaines de fois, ce qui est pour le CPU largement calculable. :) Pour imposer cette limitation, il suffit de bloquer le programme quelques millisecondes à un certain moment. Ce petit intervalle dépend du nombre de FPS que l'on souhaite afficher.

Pour calculer ce temps de blocage, il suffit de diviser 1000 millisecondes (soit une seconde) par le nombre de FPS que l'on veut. Par exemple, si on veut 50 FPS il faut faire : 1000 / 50 = 20 millisecondes.

Pour 50 FPS, la boucle principale devra mettre 20 millisecondes à s'exécuter, quitte à mettre le programme en pause jusqu'à atteindre cet intervalle de 20 ms.

Programmation

La théorie c'est bien mais il faut maintenant adapter la notion de FPS dans notre code. Le principe est simple : on calcule le temps écoulé entre le début et la fin de la boucle. Si ce temps est inférieur à 20 ms alors on met en pause le programme jusqu'à atteindre les 20 ms.

L'implémentation se fait en plusieurs étapes :

  • À chaque tour de boucle, on enregistre le temps où on commence la boucle.

  • Puis on enregistre le temps une fois qu'elle est terminée.

  • On soustrait le temps enregistré au début par le temps de fin de boucle.

  • Si ce temps est inférieur à 20 ms, alors on met en pause le programme jusqu'à atteindre 20 ms.

Pour capturer le temps, on utilise une fonction de la SDL : SDL_GetTicks(). Elle retourne le temps actuel de l'ordinateur dans une structure de type Uint32 :

Uint32 SDL_GetTicks(void);

Une autre fonction qui va nous être utile est la fonction SDL_Delay(). Cette fonction va nous permettre de mettre en pause le programme lorsque nous en aurons besoin. Elle prend un seul paramètre : le temps en millisecondes durant lequel elle va bloquer le programme :

void SDL_Delay(Uint32 ms);

Bref, commençons déjà par déclarer nos variables juste après le booléen terminer de la méthode bouclePrincipale () :

void SceneOpenGL::bouclePrincipale()
{
    // Variables relatives à la boucle
    bool terminer(false);
    unsigned int frameRate (1000 / 50);
    Uint32 debutBoucle(0), finBoucle(0), tempsEcoule(0);
    /* **** Reste du code **** */
}

N'oubliez pas que le temps de blocage est égal à : 1000 / Frame Rate.

Occupons-nous maintenant de la limitation. La première chose à faire est de déterminer le temps où la boucle commence :

// Boucle principale
while(!terminer)
{
    // On définit le temps de début de boucle
    debutBoucle = SDL_GetTicks();
    /* ***** Boucle Principale ***** */
}

Ensuite, il faut déterminer le temps qu'a mis la boucle pour s'exécuter. Pour ça, on enregistre le temps de fin de boucle que l'on soustrait par le temps du début :

while(!terminer)
{
    /* ***** Boucle Principale ***** */
    // Calcul du temps écoulé
    finBoucle = SDL_GetTicks();
    tempsEcoule = finBoucle - debutBoucle;
}

Enfin, si le temps est inférieur à 20 ms, on met en pause le programme jusqu'à atteindre les 20 ms :

// Si nécessaire, on met en pause le programme
if(tempsEcoule < frameRate)
    SDL_Delay(frameRate - tempsEcoule);

Si on résume tout ça :

void SceneOpenGL::bouclePrincipale()
{
    // Variables relatives à la boucle
    bool terminer(false);
    unsigned int frameRate (1000 / 50);
    Uint32 debutBoucle(0), finBoucle(0), tempsEcoule(0);
    // Boucle principale
    while(!terminer)
    {
        // On définit le temps de début de boucle
        debutBoucle = SDL_GetTicks();
        // Gestion des évènements
        SDL_PollEvent(&m_evenements);
        if(m_evenements.window.event == SDL_WINDOWEVENT_CLOSE)
            terminer = true;

        /* ***** Rendu OpenGL ***** */

        // Calcul du temps écoulé
        finBoucle = SDL_GetTicks();
        tempsEcoule = finBoucle - debutBoucle;
        // Si nécessaire, on met en pause le programme
        if(tempsEcoule < frameRate)
            SDL_Delay(frameRate - tempsEcoule);
    }
}

Avec ce code, la boucle principale ne s'exécutera que 50 fois par seconde. Si vous observez l'activité de votre processeur, vous remarquerez que le programme ne le monopolise pas malgré l'utilisation de la fonction SDL_PollEvent(). Maintenant, nous ne sommes plus obligés de toucher la souris pour voir quelque chose bouger à l'écran. Essayez avec votre cube, il tourne même si vous ne faites rien. :p

Télécharger : Code Source C++ du Chapitre 7

Et voilà, nous avons fait nos premiers pas dans la 3D. Nous avons vu les principes de base et nous sommes maintenant capables d'afficher des polygones en 3 dimensions avec OpenGL. :p
Dans le chapitre suivant, nous allons nous reposer un peu et étudier quelques points divers dont je n'ai pas encore parlés. Ce chapitre sera un peu plus court que les autres mais tout aussi important. ;)


Push et Pop

Les chapitres les plus compliqués de la partie 1 sont enfin derrière nous, les suivants le seront moins. Aujourd'hui, on va s'intéresser aux piles de matrices.

Retenez bien ce que nous allons voir, et profitez de ce chapitre assez soft pour vous reposer. :magicien:

La problématique

Introduction

Je profite de cette partie vous faire un aparté sur la matrice modelview. En effet, pour le moment il y a un petit problème avec notre façon de l'utiliser, nous allons étudier un cas pour voir ce qui ne va pas. Jusqu'à maintenant, nous ne nous en sommes pas vraiment rendus compte car nos modèles 3D sont assez simples. Mais maintenant que nous avons fait nos premiers pas dans la 3D, il vaut mieux prendre les bonnes habitudes dès le début. :)

Un problème bien ennuyeux ...

Pour comprendre le problème, nous allons revenir un peu sur les transformations. Vous savez maintenant que chaque transformation que vous faites sera appliquée sur le repère de votre espace 3D et pas seulement sur l'objet que vous voulez afficher. Si vous faites une translation puis une rotation c'est le repère entier qui va être modifié.

Imaginez que vous vouliez afficher un toit pour une maison. Il faudra tout d'abord se placer en haut de la maison, puis faire une rotation pour que le toit soit légèrement penché et afficher le tout.

Maintenant si vous voulez afficher le jardin, comment faites-vous pour revenir en bas de la maison ? Comment faites-vous pour annuler la translation et la rotation qui permettaient d'arriver jusqu'au toit ?

Pour ça, nous avons trois solutions :

  • Refaire la rotation et la translation dans le sens inverse.

  • Réinitialiser totalement la matrice modelview, puis la caméra et enfin se replacer en bas de la maison.

  • Sauvegarder la matrice, afficher le toit en la modifiant, puis annuler les transformations en la restaurant à son état sauvegardé.

La première solution peut vous paraitre la plus simple à mettre en œuvre et sur un exemple comme celui-ci on peut le penser. Mais imaginez une maison de 500 vertices, l'afficher nécessitera plusieurs transformations. Vous pensez vraiment refaire toutes ces transformations dans le sens inverse pour retrouver votre position ?

La réponse est bien sûr non , d'une part parce que se rappeler de toutes les transformations est trop fastidieux et d'autre part parce que ça vous couterait trop de ressources pour refaire tout dans le sens inverse. Surtout que votre maison sera affichée 50 fois par seconde, imaginez le fourbi. :o

La deuxième solution nous pose les mêmes problèmes, réinitialiser la matrice et se replacer nous fait perdre du temps et des ressources.

Nous utiliserons donc la troisième solution qui nous permet de sauvegarder la matrice quand nous en avons besoin. De cette façon, on peut modifier le repère à volonté sans être obligé de retenir sur toutes les transformations. Une fois que nous voudrons revenir à la position sauvegardée, il suffira simplement de restaurer la sauvegarde du repère. :D

Les piles

Avec OpenGL 2

Dans les précédentes versions d'OpenGL, il existait deux fonctions qui permettaient de sauvegarder et de restaurer les matrices facilement. Celles-ci s’appelaient :

  • glPush() : pour la sauvegarde

  • glPop() : pour la restauration

Ces deux fonctions fonctionnaient sur un système de pile qui permettait de stocker les sauvegardes les unes sur les autres.

Le principe d'une pile en programmation est d'entasser des variables ou des objets de même type les uns sur les autres comme une pile d'assiettes. Le but de la fonction glPush() était justement d'empiler des matrices entre elles pour former une pile de matrices :

On utilisait généralement les piles sur la matrice modelview , étant donné que c'est elle la plus utilisée. Elles étaient basées sur le principe LIFO (Last In First Out) , littéralement sur le principe du dernier arrivé premier sorti. C'est-à-dire que la dernière sauvegarde était la première restaurée.

Prenons l'exemple des assiettes. Si vous empilez 18 assiettes et que vous voulez laver celle la plus en dessous. Il faudra d'abord laver les 17 premières assiettes qui sont au dessus. En revanche, si vous voulez laver la dernière arrivée, elle se trouvera en haut de la pile (l'endroit le plus simple à accéder).

Ok on sait ce qu'est une pile maintenant. :) Mais à quoi ça pouvait bien servir ?

Bonne question, les piles permettaient de sauvegarder la matrice modelview à un état donné pour pouvoir la restaurer plus tard. L'intérêt principal était de pouvoir empiler des matrices pour avoir un système de restauration assez simple : la dernière sauvegarde était la première restaurée.

De cette façon, nous n'avions plus besoin de réinitialiser la matrice modelview ou de retenir toutes les transformations pour savoir où se trouvait le repère.

Avec OpenGL 3

Comme beaucoup d'autres fonctions, OpenGL a déprécié l'utilisation de glPush() et glPop() mais bon il n'y a rien de surprenant là-dedans. En revanche, ce qui est surprenant c'est que la librairie GLM n'inclut pas de méthodes de substitution à ces fonctions. Nous ne pouvons donc pas utiliser les piles de matrices.

Ceci est dû au fait que les matrices sont maintenant des objets au sens propre du terme. C'est-à-dire que nous pouvons les manipuler, utiliser l'allocation dynamique dessus, intégrer la POO, etc. L'usage des piles n'est donc plus utile. C'est assez perturbant pour ceux qui les ont toujours utilisées mais lorsque l'on connait la puissance du C++, on se rend compte que ce n'est pas une si mauvaise idée que ça. :)

Au final, nous allons utiliser une autre manière de faire pour sauvegarder nos matrices. Les piles ne sont plus indispensables mais les sauvegardes, elles, le sont toujours. On ne peut pas résoudre notre problème de toit sinon. :p

Sauvegarde et Restauration

Substitution aux piles

Comme nous l'avons vu à l'instant, les piles ne sont maintenant inutilisables, cependant les sauvegardes doivent quand même être faites.

Pour les faire, nous allons utiliser une des propriétés magiques du C++ : l'opérateur =. En effet, lorsque cet opérateur est surchargé, il permet de pouvoir copier un objet dans un autre objet. C'est ainsi que l'on peut, par exemple, copier deux voitures sans problème :

// Copie d'une voiture

Voiture maCopie = voitureOriginale;

Sous réserve que la classe Voiture inclut la surcharge de l'opérateur =. ;)

Pour notre problème de matrices, nous allons faire exactement la même chose :

  • Pour la sauvegarde : nous allons copier une matrice dans un objet sauvegarde

  • Pour la restauration : nous allons faire l'inverse et copier la sauvegarde dans la matrice originale

En code, cela donnerait :

// Sauvegarde de la matrice

mat4 sauvegardeModelview = modelview;

// Restauration de la matrice

modelview = sauvegardeModelview;

Facile non ? Je dirais même que c'est plus facile à comprendre que les piles. :p

Quand sauvegarder ?

Le problème des transformations

Maintenant que nous avons une méthode de substitution aux piles, nous allons pouvoir sauvegarder nos matrices dans nos programmes. En théorie, nous devrions faire cela à chaque fois que l'on fait une transformation. Par exemple, le cube du chapitre du chapitre précédent utilise une transformation, et plus précisément une rotation, qui nous permet de faire une pseudo-animation. Nous devons donc utiliser la sauvegarde de matrice ici.

Pourquoi me direz-vous ? Simplement parce que le prochain modèle que nous voudrons afficher (comme un autre cube) sera automatiquement affecté par la rotation. Ce qui fait qu'au lieu de faire pivoter le cube initial, nous les ferons pivoter tous les deux. Essayez ce code pour voir :

// Rotation du repère

modelview = rotate(modelview, angle, vec3(0, 1, 0));

// Affichage du premier cube

cube.afficher(projection, modelview);

// Affichage du second cube un peu plus loin

modelview = translate(modelview, vec3(10, 0, 0));

cube.afficher(projection, modelview);

Reculez votre caméra avant en la plaçant au point de coordonnées suivantes :

// Placement de la caméra

modelview = lookAt(vec3(6, 6, 6), vec3(3, 0, 0), vec3(0, 1, 0));

Compilez pour voir.

Vous verrez que les deux cubes sont affectés par la rotation.

Dans un jeu-vidéo, ça serait assez problématique si une simple rotation de caméra faisait pivoter tout un bâtiment. :p

Pour éviter ça, il faut sauvegarder l'état de la matrice modelview avant la rotation, puis la restaurer une fois le modèle affiché :

// Sauvegarde de la matrice modelview

mat4 sauvegardeModelview = modelview;

    // Rotation du repère

    modelview = rotate(modelview, angle, vec3(0, 1, 0));

    // Affichage du premier cube

    cube.afficher(projection, modelview);

// Restauration de la matrice

modelview = sauvegardeModelview;

// Affichage du second cube plus loin

modelview = translate(modelview, vec3(10, 0, 0));

cube.afficher(projection, modelview);

Si vous essayez ce code, vous remarquerez que le second cube ne pivote plus. La rotation n'est valable que pour le premier car la matrice avait été sauvegardée avant.

Un autre exemple

Bien entendu, le système de sauvegarde/restauration ne doit pas être utilisé seulement pour les rotations mais bien pour toutes les transformations (translation et homothétie comprises).

Ce qui fait que le code précédent est encore incomplet car la translation du second cube affectera les objets affichés après. Il faut donc réutiliser la sauvegarde de matrice :

// Affichage du premier cube

....

// Sauvegarde de la matrice modelview

mat4 sauvegardeModelview = modelview;

    // Affichage du second cube plus loin

    modelview = translate(modelview, vec3(2, 0, 0));

    cube.afficher(projection, modelview);

// Restauration de la matrice

modelview = sauvegardeModelview;
Le cas des objets proches

Ce système de sauvegarde nous permet de revenir à chaque fois au centre du repère. Nos modèles sont donc placés sans erreur vu que nous partons toujours du point de coordonnées (0, 0, 0). Aucune transformation ne les affecte.

Le seul cas où vous pourrez vous permettre de ne pas restaurer la matrice immédiatement est le cas où des objets se situeraient près les uns des autres. Par exemple, imaginez que votre cube devienne une caisse et que vous souhaitez en afficher plusieurs côte à côte. Vous n'allez pas sauvegarder votre matrice à chaque fois pour revenir quasiment au même point après.

Si elles sont assez proches, vous pourrez les afficher les unes à la suite des autres sans problème et ce même si vous utilisez d'autres transformations :

// Sauvegarde de la matrice modelview

mat4 sauvegardeModelview = modelview;

    // Affichage du premier cube (au centre du repère)

    cube.afficher(projection, modelview);

    // Affichage du deuxième cube

    modelview = translate(modelview, vec3(3, 0, 0));

    cube.afficher(projection, modelview);

    // Affichage du troisième cube

    modelview = translate(modelview, vec3(3, 0, 0));

    cube.afficher(projection, modelview);

// Restauration de la matrice

modelview = sauvegardeModelview;

Vous voyez ici que les cubes sont assez proches, il est donc inutile de revenir au centre du repère pour repartir afficher la caisse suivante.

Ce qu'il faut retenir

Pour résumer tout ce blabla un peu confus :

  • Vous devez sauvegarder et restaurer la matrice modelview à chaque fois que vous faites une transformation

  • Si vous affichez des objets assez proches, ne restaurez votre matrice qu'une fois toutes les transformations faites. Ceci afin d’économiser un peu de temps de calcul

  • Si vous ne faites pas de transformation, ne sauvegardez rien

C'est tout ce qu'il faut retenir dans ce chapitre. :)

Télécharger : Code Source C++ du Chapitre 8

Nous voici à la fin de ce chapitre. Nous avons appris à coder et à utiliser les sauvegardes de matrice. C'était un chapitre un peu technique mais il concernait une notion indispensable de la programmation avec OpenGL. N'oubliez pas qu'à partir de maintenant, nous utiliserons toujours les piles de matrices pour afficher nos modèles. ;)

Pour la suite du tuto je vous propose de vous reposer encore un peu, le prochain chapitre que nous allons aborder sera assez facile à comprendre. Il concernera les évènements avec la SDL 2.0. :)


Les évènements avec la SDL 2.0

Depuis le début du tutoriel, les évènements sont gérés automatiquement et sans contrôle sur le clavier et la souris. Nous pourrions continuer à coder ainsi, sans se préoccuper de rien mais on va profiter de ce petit chapitre pour créer une classe à part entière qui s'occupera de gérer tout ça pour nous. Avec elle, nous pourrons retrouver facilement les évènements qui nous intéressent comme les touches ou les boutons de souris qui sont utilisés, ...

Bien entendu, notre code devra respecter la règle de l'encapsulation. Il faudra donc faire attention à protéger les attributs pour ne pas accéder directement aux évènements.

Différences entre la SDL 1.2 et 2.0

Nous avons déjà eu l’occasion de constater les différences qu'ils existaient entre l'ancienne et la nouvelle version de la SDL, notamment lors de la création de fenêtre. Nous allons voir maintenant celles qui concernent les évènements car ils sont indispensables au développement d'un jeu vidéo. D'ailleurs, on les a déjà utilisés lorsque nous voulions savoir si une fenêtre devait se fermer ou pas.

En effet, à chaque tour de la boucle principale, on vérifie si l'évènement SDL_WINDOWEVENT_CLOSE est déclenché, ce qui nous permet de continuer ou d’arrêter la boucle :

// Boucle principale

bool terminer(false);

while(!terminer)

{

    // Gestion des évènements

    SDL_PollEvent(&m_evenements);

    if(m_evenements.window.event == SDL_WINDOWEVENT_CLOSE)

        terminer = true;

}

Ce bout de code est pratique mais il ne nous permet pas de gérer la pression des touches. On pourrait par exemple appuyer sur la touche ECHAP pour terminer la boucle, ou utiliser un de souris.

Si vous vous rappelez du chapitre sur les évènements de la SDL 1.2 dans le cours de M@téo, vous vous souvenez surement de la façon de gérer les touches du clavier. Par exemple, pour gérer la pression des touches T ou ECHAP il fallait faire ceci :

// Structure

SDL_Event evenements;

// Récupération d'un évènement

SDL_PollEvent(&evenements);

// Switch sur le type d'évènement

switch(evenements.type)

{

    case SDL_KEYDOWN:

        // Gestion des touches

        switch(evenements.key.keysym.sym)

        {

            case SDLK_T:

                ....

            break;

            case SDLK_ESCAPE:

                ....

            break;

        }

    break;

}

Voilà comment on gérait les évènements avec la SDL 1.2, et la bonne nouvelle c'est qu'avec la version 2.0 ça se passe de la même façon. :p

Seulement deux choses vont être modifiées :

  • Premièrement, on ne vérifie plus le champ sym, mais le champ scancode. Ce qui donne au final : m_evenements.key.keysym.scancode.

  • Ensuite, le nom des touches ne commencent plus par SDLK_* mais par SDL_SCANCODE_*. Ce qui nous donne ici : SDL_SCANCODE_T et SDL_SCANCODE_ESCAPE.

Si on modifie le code précédent on trouve :

// Structure

SDL_Event evenements;

// Récupération d'un évènement

SDL_PollEvent(&evenements);

// Switch sur le type d'évènement

switch(evenements.type)

{

    case SDL_KEYDOWN:

        // Gestion des touches

        switch(evenements.key.keysym.scancode)

        {

            case SDL_SCANCODE_T:

                ....

            break;

            case SDL_SCANCODE_ESCAPE:

                ....

            break;

        }

    break;

}

Aucun gros changement. ;)

Je ne peux vous parler plus explicitement des scancodes mais sachez simplement que mis à part le nom des constantes, le code restera le même pour nous. Si on veut aller plus loin au niveau du fonctionnement de la SDL on remarque que les scancodes sont une modification majeur de la librairie car on change de norme pour identifier les touches en interne. Mais bon dans le fond on s'en moque un peu. :p

Concernant la souris, il n'y a rien à signaler. Il n'y a pas de grands changements par rapport à la SDL 1.2. La gestion se fera de la même façon pour nous.

La classe Input

La classe Input

Maintenant que nous avons vu les différences qu'il existait entre les deux versions de la SDL, nous allons passer à l'implémentation d'une classe qui gèrera tous les évènements toute seule. :)

Ça veut dire qu'on va remplacer la structure SDL_Event par une classe ?

Alors oui et non, car la structure SDL_Event existera toujours quelque part. Mais au lieu de l'utiliser dans la classe SceneOpenGL, on l’utilisera dans une classe à part. Grâce à ça, les évènements seront protégés par la règle de l'encapsulation et seront aussi plus simples à utiliser. Seul l'appel à une méthode nous permettra de savoir si telle ou telle touche est enfoncée. :)

On appellera cette classe : la classe Input.

En informatique, les inputs sont des systèmes qui permettent d'apporter à l'ordinateur des actions venant de l'utilisateur. Elles peuvent prendre la forme d'un mouvement de souris, d'une pression sur une touche du clavier, d'un geste de doigt sur un écran tactile, ... D'où le nom Input pour la classe qui gèrera tous ces évènements. :)

Voici sa déclaration :

#ifndef DEF_INPUT

#define DEF_INPUT

// Include

#include <SDL2/SDL.h>

// Classe

class Input

{

    public:

    Input();

    ~Input();

    private:

};

#endif
Les attributs

La classe Input possèdera plusieurs attributs :

  • Une structure SDL_Event : pour récupérer les évènements SDL

  • Un tableau de booléens m_touches[] : regroupant toutes les touches du clavier

  • Un tableau de booléens m_boutonsSouris[] : regroupant tous les boutons de la souris

  • Deux entiers (int) : représentant la position (x, y) du pointeur de la souris

  • Deux entiers : représentant la position relative (x, y) du pointeur

  • Un booléenm_terminer : qui permettra se savoir quand la fenêtre devra être fermer

On va directement s’intéresser aux tableaux de booléens. Chaque case de ces tableaux pourra prendre deux valeurs : si une touche, ou un bouton, est enfoncé(e) elle prendra la valeur true, dans le cas contraire ce sera la valeur false. Au début du programme, toutes les cases sont initialisées à false, vu que l'on appuie sur aucune des touches.

Pour définir la taille de ces tableaux on pourrait croire qu'il faille utiliser l'allocation dynamique - vu qu'il existe plusieurs types de claviers - mais pas du tout. En effet, la SDL gère toute seule les différents claviers. Elle est capable de nous fournir une seule et unique constante (SDL_NUM_SCANCODES) représentant le nombre maximal de touches d'un clavier, et par conséquent la taille du tableau m_touches[]. Grâce à elle, on pourra gérer toutes les touches automatiquement. ;)

Pour la souris, ce sera un peu différent. Il n'existe pas de constante que l'on peut donner au tableau. En revanche, on peut savoir que la SDL ne peut gérer que 7 boutons pour la souris. On donnera donc une taille de 7 cases au tableau m_boutonsSouris[].

En réalité, on donnera une valeur de 8 car l'indice du premier bouton de la souris commence à 1 et pas à 0. Il faut donc prévoir une case supplémentaire.

Si on regroupe les attributs on a :

#ifndef DEF_INPUT

#define DEF_INPUT

// Include

#include <SDL2/SDL.h>

// Classe

class Input

{

    public:

    Input();

    ~Input();

    private:

    SDL_Event m_evenements;

    bool m_touches[SDL_NUM_SCANCODES];

    bool m_boutonsSouris[8];

    int m_x;

    int m_y;

    int m_xRel;

    int m_yRel;

    bool m_terminer;

};

#endif

Avec ces attributs, on pourra récupérer tous les évènements dont on aura besoin. On verra en dernière partie de ce chapitre quelques méthodes qui nous permettront d'accéder à ces attributs.

Le constructeur et le destructeur

Maintenant qu'on a défini la classe Input, on doit s'occuper d'initialiser ses attributs avec le constructeur. Dans cette classe, on aura besoin que d'un constructeur : la constructeur par défaut.

Input();

Il s'occupera de mettre tous les attributs soit à 0 soit à false.

Son implémentation commence par l'initialisation de tous les attributs (sauf les tableaux) :

Input::Input() : m_x(0), m_y(0), m_xRel(0), m_yRel(0), m_terminer(false)

{

}

Pour initialiser les tableaux de bool, il faudra juste faire une boucle pour affecter la valeur false aux différentes cases :

Input::Input() : m_x(0), m_y(0), m_xRel(0), m_yRel(0), m_terminer(false)

{

    // Initialisation du tableau m_touches[]

    for(int i(0); i < SDL_NUM_SCANCODES; i++)

        m_touches[i] = false;

    // Initialisation du tableau m_boutonsSouris[]

    for(int i(0); i < 8; i++)

        m_boutonsSouris[i] = false;

}

Voilà pour le constructeur. :)

On profite également de cette partie pour créer le destructeur. Même si on ne met rien dedans, on l'implémente quand même. C'est une bonne habitude à prendre. ;)

Input::~Input()

{

}
La boucle d'évènements

Pour la partie qui va suivre, je vais vous demander toute votre attention. Nous allons voir la notion la plus importante du chapitre : la boucle d'évènements.

Euh c'est quoi cette boucle ? Ça a un rapport avec les évènements SDL ?

Oui tout à fait. Il s'agit d'une boucle qui récupère tous les évènements à un moment donné, quelque soit leur nature (touche de clavier, mouvement de souris, ...).

Si je vous parle de cette boucle, c'est parce qu'à l'heure actuelle nous avons un gros problème : nous ne sommes pas capables de gérer plusieurs évènements à la fois. Je vais vous donner un exemple pour que vous compreniez bien le problème.

Dans votre vie, vous avez probablement déjà joué à un jeu-vidéo, comme Mario par exemple. Les jeux Mario sont en général des jeux de plateformes où notre petit plombier doit sauter et avancer sur des plateformes, tuyaux et autres éléments de l'environnement.

La plupart du temps, sans même vous en rendre compte, vous appuyez sur plusieurs touches (ou boutons) en même temps. Par exemple, un personnage saute et avance en même temps, il se passe deux actions : sauter et avancer. On peut même rajouter des actions à ça : tirer, sprinter, ... Dans ce cas, le programme doit gérer 4 évènements en même temps.

A l'heure actuelle, notre problème c'est qu'avec la SDL nous ne pouvons gérer qu'un seul et unique évènement à la fois. Si nous programmions un Mario avec la SDL, le personnage ne pourrait même pas avancer et sauter en même, ce qui est un peu contraignant ... :p

Le problème vient de la SDL alors ? Elle est un peu nulle cette librairie :(

Non pas du tout, car ça ne vient pas vraiment d'elle mais de notre code. Vous vous souvenez du switch au début du chapitre ? Celui-ci :

// Structure

SDL_Event evenements;

// Attente d'un évènement

SDL_PollEvent(&evenements);

// Switch sur le type d'évènement

switch(evenements.type)

{

    case SDL_KEYDOWN:

        // Gestion des touches

        switch(evenements.key.keysym.scancode)

        {

            case SDL_SCANCODE_T:

                ....

            break;

            case SDL_SCANCODE_ESCAPE:

                ....

            break;

        }

    break;

}

Avec ce code, on ne peut gérer qu'un seul évènement par tour de boucle OpenGL.

Pour régler ce problème, on va se servir d'une petite astuce de la fonction SDL_PollEvent(). En effet, si on s’intéresse à son prototype on peut remarquer une chose :

int SDL_PollEvent(SDL_Event *event);

La fonction SDL_PollEvent() renvoie une valeur (un int).

On ne s'est jamais servi de cette valeur (on ne savait même pas qu'elle existait d'ailleurs) et pourtant c'est elle qui va régler notre problème. Cet integer retourné peut prendre deux valeurs :

  • Soit 1 : ce qui veut dire qu'il reste encore des évènements à capturer dans la file d'attente

  • Soit 0 : ce qui veut dire qu'il n'y en a plus

Pour capturer tous les évènements, il suffit de piéger la fonction SDL_PollEvent() dans une boucle. Tant qu'il reste quelque chose à récupérer dans la file d'attente, la fonction retourne 1, donc on continue de récupérer les évènements jusqu'à qu'il n'y en ait plus.
Grâce à cette astuce, on sera capable de gérer plusieurs touches/boutons en même temps. Le petit Mario de tout à l'heure pourra donc avancer, sauter, sprinter, tirer des boules de feu ... et tout ça en même temps. ;)
D'où la boucle d'évènements dont je vous ai parlée tout à l'heure. :p

Au niveau du code, il suffit d'enfermer la fonction SDL_PollEvent() dans une boucle while. Tant que la fonction retourne 1 on continue la boucle :

// Structure

SDL_Event evenements;

// Boucle d'évènements

while(SDL_PollEvent(&evenements) == 1)

{

    // Switch sur le type d'évènement

    switch(evenements.type)

    {

        case SDL_KEYDOWN:

            // Gestion des touches

            switch(evenements.key.keysym.scancode)

            {

                case SDL_SCANCODE_T:

                    ....

                break;

                case SDL_SCANCODE_ESCAPE:

                    ....

                break;

            }

        break;

    }

}

On peut même simplifier le while en enlevant la condition ' == 1 ' :

// Boucle d'évènements

while(SDL_PollEvent(&evenements))

{

    ...

}

Vous savez maintenant ce qu'est la boucle d'évènements. C'est une boucle qui permet de capturer toutes les actions qui concernent le clavier, la souris, ... en même temps.

Nous allons maintenant revenir à la classe Input pour intégrer cette boucle dans une méthode. :)

La méthode updateEvenements()

La méthode qui va implémenter cette boucle s'appellera updateEvenements(). Voici son prototype :

void updateEvenements();

Commençons cette méthode en codant la fameuse boucle while qui prendra en "condition" la valeur retournée par la fonction SDL_PollEvent().

D'ailleurs, on donnera à cette dernière l'adresse de l'attribut m_evenements - car oui la structure SDL_Event existe toujours quelque part, elle n'a pas disparue. :p

void Input::updateEvenements()

{

    // Boucle d'évènements

    while(SDL_PollEvent(&m_evenements))

    {

    }

}

La suite du code ne changera pas beaucoup par rapport à la SDL 1.2. On met en place un gros switch qui va tester la valeur du champ m_evenements.type . On commencera par gérer les conditions relatives au clavier.

Deux cas doivent être gérés par le clavier :

  • Lorsqu'une touche est enfoncée, l'évènement SDL_KEYDOWN est déclenché (même chose qu'avec la SDL 1.2).

  • Lorsqu'une touche est relâchée, l'évènement SDL_KEYUP est déclenché (même chose aussi)

Dans les deux cas, on actualisera l'état de la touche correspondante dans le tableau m_touches[]. D'ailleurs, on utilisera le champ m_evenements.key.keysym.scancode qui nous servira d'indice pour retrouver la bonne case :

// Actualisation de l'état de la touche

m_touches[m_evenements.key.keysym.scancode] = true;

Nous avons vu que le champ scancode remplaçait désormais le champ sym. ;)

Le gros avantage du tableau de booléens c'est que, quelque soit la touche enfoncée, nous n'avons besoin que d'une seule ligne de code pour actualiser son état. Que ce soit la touche A, B, C, ESPACE, ... une seule ligne suffit. :D

En définitif, pour gérer les touches du clavier nous devons :

  • Tester le champ m_evenements.type pour savoir si une touche a été enfoncée ou relâchée

  • Actualiser la touche dans le tableau de booléens avec une valeur true ou un false

void Input::updateEvenements()

{

    // Boucle d'évènements

    while(SDL_PollEvent(&m_evenements))

    {

        // Switch sur le type d'évènement

        switch(m_evenements.type)

        {

            // Cas d'une touche enfoncée

            case SDL_KEYDOWN:

                m_touches[m_evenements.key.keysym.scancode] = true;

            break;

            // Cas d'une touche relâchée

            case SDL_KEYUP:

                m_touches[m_evenements.key.keysym.scancode] = false;

            break;

            default:

            break;

        }

    }

}

Grâce à ce code, toutes les touches du clavier peuvent être mises à jour en même temps (enfin, en une boucle :p ).

Gestion de la souris

Nous avons déjà fait la plus grosse part du travail, on sait désormais gérer toutes les touches du clavier. Nous allons maintenant passer à la gestion de la souris.

La bonne nouvelle, c'est que les boutons de la souris se gèrent de la même façon que les touches du clavier. Il suffit de rajouter deux cases au switch : un pour les boutons qui seront pressés (ce case utilisera la constante SDL_MOUSEBUTTONDOWN) et un autre pour les boutons qui seront relâchés (il utilisera la constante SDL_MOUSEBUTTONUP).

Pour récupérer l'indice du bouton dans le tableau m_boutonsSouris[], nous n'utiliserons pas le champ m_evenements.key.keysym.scancode, car ce champ ne concerne uniquement que le clavier. A la place, nous utiliserons le champ evenements.button.button qui lui est réservé à la souris.

On ajoute donc les deux cases suivants au switch :

// Cas de pression sur un bouton de la souris

case SDL_MOUSEBUTTONDOWN:

    m_boutonsSouris[m_evenements.button.button] = true;

break;

// Cas du relâchement d'un bouton de la souris

case SDL_MOUSEBUTTONUP:

    m_boutonsSouris[m_evenements.button.button] = false;

break;

Ça c'était la partie des boutons. Maintenant il faut s'occuper des mouvements de la souris.

Lorsque la souris est en mouvement, un évènement est déclenché dans la SDL. Cet évènement va permettre de mettre à jour les coordonnées (x, y) du pointeur ainsi que ses coordonnées relatives. Nous allons pouvoir détecter ces mouvements grâce à la constante SDL_MOUSEMOTION. Lorsque cet évènement sera déclenché, on mettra à jour les attributs qui concernent les coordonnées. On prendra les nouvelles valeurs dans le champ m_evenements.motion :

// Cas d'un mouvement de souris

case SDL_MOUSEMOTION:

    m_x = m_evenements.motion.x;

    m_y = m_evenements.motion.y;

    m_xRel = m_evenements.motion.xrel;

    m_yRel = m_evenements.motion.yrel;

break;
Fermeture de la fenêtre

Il ne reste plus qu'un seul évènement à gérer dans cette boucle : le cas de la fermeture de la fenêtre (la croix rouge en haut à droite sous Windows). Depuis le début du tutoriel, on utilise cet évènement dans la boucle principale d'OpenGL pour savoir si on doit quitter le programme :

if(m_evenements.window.event == SDL_WINDOWEVENT_CLOSE)

    terminer = true;

Il faut maintenant enlever ce code pour le migrer dans la méthode updateEvenements(). N'oubliez pas que c'est elle et uniquement elle qui doit mettre à jour tous les évènements.

Nous allons donc ajouter un nouveau case dans le switch pour gérer cette fermeture. On utilisera la constante SDL_WINDOWSEVENT pour savoir si cet évènement a été déclenché. N'oubliez pas de modifier la variable terminer en m_terminer, car on met à jour non plus une variable mais un attribut :

// Cas de la fermeture de la fenêtre

case SDL_WINDOWEVENT:

    if(m_evenements.window.event == SDL_WINDOWEVENT_CLOSE)

        m_terminer = true;

break;

Si réunie tous ces cases dans la méthode, on trouve :

void Input::updateEvenements()

{

    // Boucle d'évènements

    while(SDL_PollEvent(&m_evenements))

    {

        // Switch sur le type d'évènement

        switch(m_evenements.type)

        {

            // Cas d'une touche enfoncée

            case SDL_KEYDOWN:

                m_touches[m_evenements.key.keysym.scancode] = true;

            break;

            // Cas d'une touche relâchée

            case SDL_KEYUP:

                m_touches[m_evenements.key.keysym.scancode] = false;

            break;

            // Cas de pression sur un bouton de la souris

            case SDL_MOUSEBUTTONDOWN:

                m_boutonsSouris[m_evenements.button.button] = true;

            break;

            // Cas du relâchement d'un bouton de la souris

            case SDL_MOUSEBUTTONUP:

                m_boutonsSouris[m_evenements.button.button] = false;

            break;

            // Cas d'un mouvement de souris

            case SDL_MOUSEMOTION:

                m_x = m_evenements.motion.x;

                m_y = m_evenements.motion.y;

                m_xRel = m_evenements.motion.xrel;

                m_yRel = m_evenements.motion.yrel;

            break;

            // Cas de la fermeture de la fenêtre

            case SDL_WINDOWEVENT:

                if(m_evenements.window.event == SDL_WINDOWEVENT_CLOSE)

                    m_terminer = true;

            break;

            default:

            break;

        }

    }

}
Problème des coordonnées relatives

Cette méthode est presque complète, il reste juste un petit point à régler qui concerne les coordonnées relatives. Ces coordonnées représentent la différence entre la position actuelle et l'ancienne position, elles seront très utiles dans le chapitre sur la caméra.

Le problème avec ces coordonnées c'est que : s'il n'y a aucun évènement alors elles ne sont pas mises à jour, elles conservent donc leurs anciennes valeurs. Ce qui veut dire que le programme considère que la souris continue de bouger, même si elle est inactive.

Pour régler ce problème, on va ré-initialiser les coordonnées avec la valeur 0 au début de la méthode. Ne vous inquiétez pas, si les coordonnées doivent être mises à jour avec de vraies valeurs elles le seront dans le switch.

Grâce à cette astuce, nous n'aurons aucun problème de mouvement fictif. On rajoute donc ces deux lignes de code au début de la méthode :

// Pour éviter des mouvements fictifs de la souris, on réinitialise les coordonnées relatives

m_xRel = 0;

m_yRel = 0;

Attention, il faut mettre ces deux lignes de code avant la boucle while. Si vous les mettez à l'intérieur, les coordonnées ne seront ré-initialisées que lorsqu'il y aura un évènement. C'est justement ce qu'on cherche à éviter. ;)

Notre méthode donne donc au final :

void Input::updateEvenements()

{

    // Pour éviter des mouvements fictifs de la souris, on réinitialise les coordonnées relatives

    m_xRel = 0;

    m_yRel = 0;

    // Boucle d'évènements

    while(SDL_PollEvent(&m_evenements))

    {

        // Switch sur le type d'évènement

        switch(m_evenements.type)

        {

            // Cas d'une touche enfoncée

            case SDL_KEYDOWN:

                m_touches[m_evenements.key.keysym.scancode] = true;

            break;

            // Cas d'une touche relâchée

            case SDL_KEYUP:

                m_touches[m_evenements.key.keysym.scancode] = false;

            break;

            // Cas de pression sur un bouton de la souris

            case SDL_MOUSEBUTTONDOWN:

                m_boutonsSouris[m_evenements.button.button] = true;

            break;

            // Cas du relâchement d'un bouton de la souris

            case SDL_MOUSEBUTTONUP:

                m_boutonsSouris[m_evenements.button.button] = false;

            break;

            // Cas d'un mouvement de souris

            case SDL_MOUSEMOTION:

                m_x = m_evenements.motion.x;

                m_y = m_evenements.motion.y;

                m_xRel = m_evenements.motion.xrel;

                m_yRel = m_evenements.motion.yrel;

            break;

            // Cas de la fermeture de la fenêtre

            case SDL_WINDOWEVENT:

                if(m_evenements.window.event == SDL_WINDOWEVENT_CLOSE)

                    m_terminer = true;

            break;

            default:

            break;

        }

    }

}

Voilà ! Maintenant nous sommes capables de mettre à jour tous nos évènements, peu importe leur nombre ils seront tous gérés simultanément par la classe Input. :D

La méthode terminer()

Il ne reste plus qu'une chose à ajouter dans la classe : une méthode qui permet de dire si oui ou non l'utilisateur veut quitter le programme. Jusqu'à maintenant, on utilisait un booléen terminer pour fermer la fenêtre, mais maintenant ce booléen se trouve dans la classe Input.

Or, avec la règle de l'encapsulation on ne peut pas directement vérifier cet attribut. Il nous faut donc coder un accesseur pour récupérer la valeur du booléen.

Le prototype est terriblement simple. :p

bool terminer() const;

Son implémentation l'est tout autant :

bool Input::terminer() const

{

    return m_terminer;

}
Modification de la classe SceneOpenGL

On passe maintenant à l'utilisation de la classe Input dans notre scène 3D. Pour cela, on va remplacer l'ancien code de gestion des évènements par nos nouvelles méthodes.

On commence donc par déclarer un objet de type Input dans la classe SceneOpenGL qui viendra remplacer l'ancien attribut m_evenements :

#include "Input.h"

class SceneOpenGL

{

    public:

    /* *** Méthodes *** */

    private:

    /* *** Attributs *** */

    // Objet Input pour la gestion des évènements

    Input m_input;

}

On n'oublie pas de l'initialiser dans le constructeur :

SceneOpenGL::SceneOpenGL(std::string titreFenetre, int largeurFenetre, int hauteurFenetre) : m_titreFenetre(titreFenetre), m_largeurFenetre(largeurFenetre),

                                                                                m_hauteurFenetre(hauteurFenetre), m_fenetre(0), m_contexteOpenGL(0), m_input()

Dans la boucle principale, on ne vérifie donc plus le booléen terminer mais la valeur retournée par la méthode terminer() :

// Boucle principale

while(!m_input.terminer())

{

    /* ***** RENDU ***** */

}

Enfin, on supprime notre ancien code de gestion d'évènements que l'on remplace par la méthode updateEvenements() de la classe Input :

// Boucle principale

while(!m_input.terminer())

{

    // On définit le temps de début de boucle

    debutBoucle = SDL_GetTicks();

    // Gestion des évènements

    m_input.updateEvenements();

    /* *** Rendu *** */

}

Voilà pour la classe Input ! Grâce à elle, tous nos évènements seront mis à jour automatiquement. :)

Maintenant que l'on a fait cela, on va pouvoir passer à l'implémentation de plusieurs méthodes "indispensables" qui vont nous permettre de connaitre l'état du clavier, de la souris, ... Nous devons passer par ces méthodes pour respecter la règle de l'encapsulation, sans quoi nous serions obligés d’accéder directement aux attributs pour avoir nos valeurs.

Les méthodes indispensables

Méthodes

Cette dernière partie sera consacrée à une série de méthodes qui seront utilisées tout au long du tuto. Je vais vous donner une petite liste des fonctionnalités que l'on va implémenter. Il nous faudra une méthode pour :

  • Savoir si une touche est enfoncée

  • Savoir si un bouton de la souris est enfoncé

  • Savoir si le pointeur de la souris a bougé

  • Récupérer les coordonnées (x, y) du pointeur

  • Récupérer les coordonnées relatives (x, y) du pointeur

  • Cacher le pointeur

  • Capturer le pointeur dans la fenêtre

Houla on va devoir coder tout ça ?

Et bien oui. Mais sachez pour vous rassurer que la plus grosse méthode de cette liste ne fera que 4 lignes. :p

Ces méthodes ne se contenteront que de faire une simple action (renvoyer un booléen par exemple) avec parfois des bloc if else. Rien de compliqué ne vous inquiétez pas.

La méthode getTouche()

On commence par la méthode la plus importante : celle qui permet de savoir si une touche a été enfoncée (ou non). En gros, on va coder un getter sur le tableau m_touches[]. :p Elle prendra en paramètre une variable de type SDL_Scancode correspondant à la touche demandée :

bool getTouche(const SDL_Scancode touche) const;

Cette méthode renverra true si la touche est pressée ou false si elle ne l'est pas. N'oubliez pas de la déclarer en tant que méthode constante, vu que l'on ne modifie aucun attribut :

bool Input::getTouche(const SDL_Scancode touche) const

{

    return m_touches[touche];

}

Fini. :)

Bonus : On va utiliser cette nouvelle méthode dès maintenant. Désormais, je veux que chacune de vos fenêtres SDL de chaque projet que vous ferez puisse se fermer en appuyant sur la touche ECHAP.

Comment feriez-vous ça avec le getter que l'on vient de coder ? Je vous laisse réfléchir un peu. :p

Vous avez trouvé ?

// Boucle principale

while(!m_input.terminer())

{

    // On définit le temps de début de boucle

    debutBoucle = SDL_GetTicks();

    // Gestion des évènements

    m_input.updateEvenements();

    if(m_input.getTouche(SDL_SCANCODE_ESCAPE))

       break;

    /* *** Rendu *** */

}

Il suffit simplement d'appeler le getter getTouche() avec le scancode SDL_SCANCODE_ESCAPE. Si le getter retourne true, on casse la boucle avec le mot-clef break.

Bonus 2 : Pour savoir si deux touches sont enfoncées simultanément, il suffira d'utiliser un bloc if avec les deux touches demandées :

// Est-ce que les touches Z et D sont pressées ?

if(m_input.getTouche(SDL_SCANCODE_Z) && m_input.getTouche(SDL_SCANCODE_D))

{

    ...

}
La méthode getBoutonSouris()

La méthode, ou plutôt le getter, getBoutonSouris() fera la même chose que getTouche(). C'est-à-dire qu'elle permettra de savoir si un bouton spécifié est enfoncé ou pas. La seule différence avec la méthode précédente c'est que cette fois-ci, on lui donnera en paramètre non pas un SDL_Scancode mais une variable de type Uint8 correspondant au bouton demandé (En réalité, ce sera une constante comme avec les scancodes).

bool getBoutonSouris(const Uint8 bouton) const;

Elle renverra l'état du bouton demandé dans le tableau m_boutonsSouris[] :

bool Input::getBoutonSouris(const Uint8 bouton) const

{

    return m_boutonsSouris[bouton];

}
La méthode mouvementSouris()

La méthode suivante nous permettra de savoir si le pointeur de la souris a bougé. Grâce à elle, nous pourrons déclencher une action dès que la souris bougera. On appellera cette méthode : mouvementSouris(), elle renverra un booléen.

bool mouvementSouris() const;

Pour détecter un mouvement de souris il suffit de comparer la position relative du pointeur grâce aux attributs m_xRel et m_yRel. Si ces deux attributs sont égals à 0, alors le pointeur n'a pas bougé. Si en revanche ils ont une valeur non nulle, alors c'est que le pointeur a bougé.

bool Input::mouvementSouris() const

{

    if(m_xRel == 0 && m_yRel == 0)

        return false;

    else

        return true;

}
Les Getters du pointeur

Les méthodes suivantes sont des getters, elles renvoient chacune un attribut qui concerne la position du pointeur. Vous savez déjà comment fonctionne un getter, je vous épargne donc les explications. :)

Voici leur constructeur :

// Getters

int getX() const;

int getY() const;

int getXRel() const;

int getYRel() const;

Implémentation :

// Getters concernant la position du curseur

int Input::getX() const

{

    return m_x;

}

int Input::getY() const

{

    return m_y;

}

int Input::getXRel() const

{

    return m_xRel;

}

int Input::getYRel() const

{

    return m_yRel;

}
La méthode afficherPointeur()

Les méthodes suivantes peuvent vous paraitre inutiles pour le moment, mais dès que nous utiliserons une caméra mobile pour comprendrez vite leur utilité.

La méthode afficherPointeur() va permettre d'afficher ou de cacher le pointeur à l'écran. Elle prendra en paramètre un booléen qui :

  • S'il est à true : le pointeur est caché

  • S'il est à false : le pointeur est affiché

void afficherPointeur(bool reponse) const;

Cette méthode va faire appel à une fonction SDL pour afficher ou cacher le pointeur :

int SDL_ShowCursor(int toggle);

Cette fonction prend en paramètre une constante qui peut être égale soit à SDL_ENABLE soit à SDL_DISABLE, ce paramètre se comporte un peu comme un booléen. On ne s'occupera pas de la valeur retournée par la fonction.

Voici l'implémentation de la méthode :

void Input::afficherPointeur(bool reponse) const

{

    if(reponse)

        SDL_ShowCursor(SDL_ENABLE);

    else

        SDL_ShowCursor(SDL_DISABLE);

}
La méthode capturerPointeur()

La dernière méthode va permettre d'utiliser le Mode Relatif de la Souris. Ce mode permet de piéger le pointeur dans la fenêtre, il ne pourra pas en sortir. C'est utile voir obligatoire dans un jeu-vidéo par exemple. On appellera cette méthode : capturerPointeur().

Faites attention à cette méthode, si vous l'appelez dans votre code vous devrez prévoir quelque chose pour fermer votre programme. Utilisez un getTouche() par exemple. Si vous ne faites pas ça, vous ne pourrez plus fermer votre fenêtre. Vous devrez alors utiliser les combinaisons CTRL + MAJ + SUPPR ou WINDOWS + TAB (si vous êtes sous Windows) pour pouvoir ré-utiliser la souris.

Le prototype de la méthode est le suivant :

void capturerPointeur(bool reponse) const;

Elle se comportera exactement de la même manière que la méthode précédente. Elle utilisera la fonction SDL_SetRelativeMousseMode() de la SDL pour activer le mode relatif de la souris :

int SDL_SetRelativeMouseMode(SDL_bool enabled);

Cette fonction prendra en paramètre un SDL_bool. Sa valeur peut être soit SDL_TRUE soit SDL_FALSE. Voici son implémentation :

void Input::capturerPointeur(bool reponse) const

{

    if(reponse)

        SDL_SetRelativeMouseMode(SDL_TRUE);

    else

        SDL_SetRelativeMouseMode(SDL_FALSE);

}

Voilà pour l'implémentation des méthodes "indispensables". :)

Télécharger : Code Source C++ du Chapitre 9

Exercices

Énoncés

Je vais vous donner des exercices assez simples pour vous habituez à utiliser votre nouvelle classe. Ceux-ci seront axés sur la pseudo-animation du cube (celle qui permettait de le faire tourner). Vous n'aurez besoin d'utiliser que les méthodes qui sont déjà codées, vous n'aurez donc pas besoin d'en rajouter.

Il est préférable que vous incluiez le code relatif aux évènements avant la fonction glClear(). En effet, vous verrez dans le futur que nous aurons parfois de faire plusieurs affichages de la même scène. Pour économiser du temps de calcul les inputs doivent se gérer avant ces affichages. La seule chose qui peut se trouver après la fonction c'est la méthode rotate() car elle ne fait pas partie des évènements mais des matrices (et donc de l'affichage).

Autant prendre les bonnes habitudes dès maintenant, surtout que ça ne mange pas de pain. :)

Exercice 1 : L'animation initiale du cube permettait de le faire pivoter selon un angle et par rapport à l'axe Y. Votre objectif est de dé-automatiser cette rotation pour qu'elle réponde aux touches du clavier Flèche Gauche et Droite (SDL_SCANCODE_LEFT et SDL_SCANCODE_RIGHT). Vous pouvez vous aider du code que l'on avait utilisé dans le chapitre 7 :

// Incrémentation de l'angle

angle += 4.0;

if(angle >= 360.0)

    angle -= 360.0;

// Sauvegarde de la matrice

mat4 sauvegardeModelview = modelview;

    // Rotation du repère

    modelview = rotate(modelview, angle, vec3(0, 1, 0));

    // Affichage du cube

    cube.afficher(projection, modelview);

// Restauration de la matrice

modelview = sauvegardeModelview;

Exercice 2 : Même exercice que précédemment sauf que l'axe concerné n'est plus Y mais X et les touches sont Flèche Haut et Bas (SDL_SCANCODE_UP et SDL_SCANCODE_DOWN).

Exercice 3 : Rassemblez les deux animations précédentes pour le même cube. C'est-à-dire que vous devez pouvoir appuyer sur les 4 touches directionnelles pour le faire pivoter selon l'axe X et Y (Petit indice : vous aurez besoin de deux angles).

Solution

Exercice 1 :

Le but de l'exercice était de "manualiser" la rotation en fonction des touches Haut et Bas. Pour cela, il fallait simplement appeler la méthode getTouche() de l'objet m_input deux fois avec les constantes SDL_SCANCODE_LEFT et SDL_SCANCODE_RIGHT :

// Gestion des évènements

....

// Rotation du cube vers la gauche

if(m_input.getTouche(SDL_SCANCODE_LEFT))

{

}

// Rotation du cube vers la droite

if(m_input.getTouche(SDL_SCANCODE_RIGHT))

{

}

// Nettoyage de l'écran

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

....

Faites attention à bien placer ce code avant la fonction glClear().

Une fois la vérification des touches pressées faite, il ne manquait plus qu'à copier le code de rotation. Bien entendu, il fallait additionner l'angle dans un cas et le soustraire dans l'autre :

// Gestion des évènements

....

// Rotation du cube vers la gauche

if(m_input.getTouche(SDL_SCANCODE_LEFT))

{

    // Modification de l'angle

    angle -= 4.0;

    // Limitation

    if(angle >= 360.0)

        angle -= 360.0;

}

// Rotation du cube vers la droite

if(m_input.getTouche(SDL_SCANCODE_RIGHT))

{

    // Modification de l'angle

    angle += 4.0;

    // Limitation

    if(angle >= 360.0)

        angle -= 360.0;

}

// Nettoyage de l'écran

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

....

Enfin, il ne manquait plus qu'à appeler la méthode rotate() avec l'angle de rotation en paramètre et afficher le cube. Le tout encadré par la sauvegarde et la restauration de la matrice modelview. ;)

// Sauvegarde de la matrice modelview

mat4 sauvegardeModelview = modelview;

    // Rotation du repère

    modelview = rotate(modelview, angle, vec3(0, 1, 0));

    // Affichage du premier cube

    cube.afficher(projection, modelview);

// Restauration de la matrice

modelview = sauvegardeModelview;

Exercice 2 :

Cet exercice reprend le même principe que le précédent sauf qu'il fallait, d'une part, modifier les constantes utilisées :

// Rotation du cube vers le bas

if(m_input.getTouche(SDL_SCANCODE_DOWN))

{

    // Modification de l'angle

    angle -= 4.0;

    // Limitation

    if(angle >= 360.0)

        angle -= 360.0;

}

// Rotation du cube vers le haut

if(m_input.getTouche(SDL_SCANCODE_UP))

{

    // Modification de l'angle

    angle += 4.0;

    // Limitation

    if(angle >= 360.0)

        angle -= 360.0;

}

Et d'autre part, il fallait également modifier l'axe de rotation de la méthode rotate() :

// Rotation du repère

modelview = rotate(modelview, angle, vec3(0, 1, 0));

Exercice 3 :

Le dernier exercice était un poil plus dur que les deux autres, il fallait utiliser deux angles et gérer 4 touches du clavier.

Vous pouviez donner n'importe quel nom aux angles :

// Angles de la rotation

float angleX(0.0);

float angleY(0.0);

La gestion des 4 touches n'était pas compliquée du moment que vous utilisiez le bon angle avec le bon axe de rotation :

// Rotation du cube vers la gauche

if(m_input.getTouche(SDL_SCANCODE_LEFT))

{

    angleY -= 5;

    if(angleY > 360)

        angleY -= 360;

}

// Rotation du cube vers la droite

if(m_input.getTouche(SDL_SCANCODE_RIGHT))

{

    angleY += 5;

    if(angleY < -360)

        angleY += 360;

}

// Rotation du cube vers le haut

if(m_input.getTouche(SDL_SCANCODE_UP))

{

    angleX -= 5;

    if(angleX > 360)

        angleX -= 360;

}

// Rotation du cube vers le bas

if(m_input.getTouche(SDL_SCANCODE_DOWN))

{

    angleX += 5;

    if(angleX < -360)

        angleX += 360;

}

J'ai divisé le code en deux pour le rendre plus lisible. Il fallait évidemment tout coder au même endroit. ;)

Enfin, pour gérer les deux rotations, il fallait simplement appeler la méthode rotate() deux fois. Un appel prenait en compte l'angle angleX et l'autre l'angle angleY :

// Sauvegarde de la matrice modelview

mat4 sauvegardeModelview = modelview;

    // Rotation du repère

    modelview = rotate(modelview, angleY, vec3(0, 1, 0));

    modelview = rotate(modelview, angleX, vec3(1, 0, 0));

    // Affichage du premier cube

    cube.afficher(projection, modelview);

// Restauration de la matrice

modelview = sauvegardeModelview;

Encore une fois, n'oubliez pas d'utiliser la sauvegarde/restauration lorsque vous faites une transformation. :)

Notre classe Input est maintenant complète et prête à l'emploi. Avec elle, nous pourrons gérer tous nos évènements simplement. Nous serons capable de gérer la pression de plusieurs touches, des mouvements de la souris, etc. Et tout ça de manière simultanée.
Pour utiliser les évènements, il suffira juste de passer un objet de type Input aux modèles que l'on veut afficher.
De plus, l'avantage d'avoir une classe à part entière c'est que l'on pourra ajouter de nouvelles méthodes au fur et à mesure du tutoriel. Nous n'aurons pas besoin de revenir en arrière pour modifier le code, il suffira d'ajouter ce qu'il faut dans la classe Input.

Après ce chapitre repos, je vous propose de passer à un chapitre hyper méga important qui concerne les Textures avec OpenGL ! :D


Les textures

Après le chapitre quelque peu compliqué sur les piles de matrices, nous allons passer aujourd'hui à un chapitre beaucoup plus concret (et plus intéressant :p ) qui concerne le Texturing avec OpenGL. Avec ceci, nous pourrons rendre notre scène 3D plus réaliste. Nous verrons comment charger des textures stockées sur votre disque dur (ou SSD) et comment les appliquer à une surface 3D.

Introduction

Introduction

Avant de commencer ce chapitre très intéressant, on va définir ensemble un mot que vous avez déjà probablement entendu quelque part : le mot Texture.

Une texture est simplement une sorte de papier peint que l'on va "coller" sur les modèles (2D ou 3D). Dans un jeu vidéo, toutes les surfaces que vous voyez (sols, herbe, murs, personnages, ...) sont constituées de simples images collées sur des formes géométriques. Pour un mur par exemple, il suffit de créer une surface carrée puis de coller une image représentant des briques.

L'objectif de ce chapitre sera d'apprendre à créer une texture OpenGL de A à Z. Pour ceux qui auraient suivi le cours de M@téo sur la SDL, vous devriez déjà avoir une petite idée sur la façon de charger les images. Vous vous apercevrez cependant que le chargement de texture est assez différent avec OpenGL. Mais il permet de faire des choses beaucoup plus avancées.

L'une des premières difficultés va concerner la taille des textures à afficher. En effet, OpenGL ne sait gérer que les textures dont les dimensions sont des puissances de 2 (64 pixels, 128, 256, 512, ...).

Alors bon, ce n'est pas une obligation car OpenGL redimensionne de toute façon les images par lui-même mais si vous ne voulez pas vous retrouver avec une texture déformée, il vaut mieux prendre l'habitude des dimensions en puissance de 2. Le mieux est encore de modifier la taille de vos images avec des logiciels spécialisés. ;)

Avant d'aller plus loin, je vais vous demander de télécharger la librairie SDL_image (ou plutôt SDL2_image) qui nous permettra de réaliser la première étape dans la création de nos textures. Elle va nous faire gagner du temps en chargeant tous les bits d'une image en mémoire. Remarquez une fois de plus que toutes les librairies que nous utilisons sont portables. :p

Télécharger : SDL_image - MinGW pour Windows
Télécharger : Code Source SDL_image + Librairies - Visual C++ et Linux

Pour MinGW sous Windows : fusionnez les dossiers bin, dll, include et lib avec ceux du dossier SDL-2.0 que vous avez placé au tout début du tutoriel (chez moi : C:\Program Files (x86)\CodeBlocks\MinGW\SDL-2.0).
Pensez à rajouter les nouvelles dll soit dans le dossier de chaque projet, soit dans le dossier bin de MinGW selon la méthode que vous avez choisie au début du tuto.

Pour Linux ça va être folklo. :lol: Vu que tout le monde n'a pas forcément la même distribution, il faudra que vous compiliez vous même SDL_image. Cependant, cette librairie fait appel à 5 autres librairies et il faudra également toutes les compiler !
Mais bon vous savez que je ne suis pas sadique (ah bon ?), je vais donc vous donner directement toutes les commandes à taper dans votre terminal.

Enfin, commencez par télécharger le code source de SDL_image et dézippez son contenu dans votre home. Ensuite, exécutez les commandes suivantes mais attention ! Si j'ai divisé les commandes en bloc ce n'est pas pour rien, je vous conseille d'exécuter les blocs un à un pour voir si tout compile normalement. Si vous copiez toute les commandes d'un coup vous risquez de zapper une erreur que vous regretterez plus tard. ;)

cd
cd SDL_image/tiff-4.0.3/
chmod +x configure
./configure
make
sudo make install

cd ../zlib-1.2.7/
chmod +x configure
./configure
make
sudo make install

cd ../libpng-1.5.13/
chmod +x configure
./configure
make
sudo make install

cd ../jpeg-8d/
chmod +x configure
./configure
make
sudo make install

cd ../libwebp-0.2.0/
chmod +x configure
./configure
make
sudo make install

cd ../SDL_image
chmod +x configure
./configure
make
sudo make install

Malgré le fait que vous compiliez toutes les librairies par vous-même, il semble qu'il y ait un problème avec le chargement des images au format PNG. Tous les autres types de fichiers fonctionnent (jpeg, tiff, tga, bmp, ...) sauf celui-la. Je pense que ça vient du fait que la librairie est en plein développement, j'espère que le problème sera réglé rapidement.

On reprend au niveau des IDE pour tout le monde, il va falloir linker la librairie SDL_image avec vos projets. Voici un petit tableau avec le link à spécifier en fonction de votre IDE :

OS | Option
Code::Blocks Windows | SDL2_image
Code::Blocks Linux | SDL2_image
DevC++ | -lSDL2_image
Visual Studio | SDL2_image.lib

Enfin pour terminer cette introduction, nous allons télécharger un pack de textures que l'on utilisera tout au long de ce tutoriel. Vous y verrez à l'intérieur plusieurs catégories d'images (sols, pierres, bois, ...). Je remercie au passage notre petit Kayl qui a fait découvrir ce pack dans son tuto. Je reprends le même vu qu'il est assez complet. :p

Télécharger : Pack de Textures Haute Résolution

Chargement

Les Objets OpenGL

La librairie SDL_image va nous faciliter grandement la tâche dans le chargement de texture. Elle va nous faire gagner beaucoup de temps car elle sait charger une multitude de formats d'image, nous n'aurons donc pas à charger nos images manuellement. Cependant, elle ne peut pas tout faire pour nous. Les images chargées avec SDL_image seront, si on les laisse comme ça, inutilisables avec OpenGL.

En effet, la librairie permet de charger les textures uniquement pour la SDL et non pour les autres API. Il nous faut donc configurer OpenGL pour qu'il puisse reconnaitre ces nouvelles textures. Dans cette partie nous allons voir pas mal de fonctions spécifiques à la librairie OpenGL, et vous verrez que vous retrouvez certaines d'entre elles tout au long du tutoriel. Je vous dirai celles qui sont importantes à retenir. ;)

Les objets OpenGL

Avant d'aller plus loin, j'aimerais que l'on développe un point important de ce chapitre : les objets OpenGL. Les objets OpenGL sont semblables aux objets en C++ (même s'ils sont différents dans le fond), on peut les représenter par le laboratoire que l'on voit dans le chapitre sur les objets de M@téo. Ce sont donc des sortes de laboratoires dont on ne connait pas le fonctionnement, et d'ailleurs on s'en moque à partir du moment où ils fonctionnent. :p

Pourquoi je vous parle de ça ? Et bien simplement parce qu'une texture est un objet OpenGL. Vous verrez que l'on va apprendre à initialiser la texture mais vous n'aurez aucune idée de ce qui se passe à l'intérieur de la carte graphique, tout comme le laboratoire. Nous donnerons à la texture des pixels à afficher et OpenGL se chargera du reste. Bon je schématise un peu mais vous avez compris l'idée.

Comment on crée un objet OpenGL ? C'est dur à faire ? :(

Non pas du tout c'est en réalité très simple ! En effet, pour créer ces objets on utilise la plupart du temps la même fonction. Et cette fonction nous renverra toujours la même chose : un ID représentant l'objet créé. Cet ID est une variable de type unsigned int et va permettra à OpenGL de savoir sur quel objet il doit travailler.

En ce qui concerne la configuration de ces objets, nous procéderons ainsi :

  • Chargement d'une image avec la librairie SDL_image

  • Création (ou plutôt de génération) de l'ID

  • Verrouillage de l'ID (nous allons voir ce que c’est dans un instant)

  • Configuration de l'objet

  • Déverrouillage de l'ID

Toutes ces parties se gèrent avec les mêmes fonctions pour la plupart des objets OpenGL (que ce soit une texture ou autre). Il n'y a que l'étape de la configuration qui va varier.

Petite précision : Les shaders sont eux aussi des objets OpenGL (et oui encore eux :p ) mais ils se gèrent différemment des autres. Ce sont les seuls objets où nous aurons un contrôle plus global.

La classe Texture

On commence la partie programmation par le plus simple : la création d'une classe Texture. Mis à part le constructeur et le destructeur, cette classe ne contiendra que la méthode charger() qui s'occupera de charger la texture demandée. Elle retournera un booléen pour confirmer ou non le chargement :

bool charger();

La classe contiendra également 2 attributs :

  • GLuint m_id : Un unsigned int qui représentera le fameux ID

  • string m_fichierImage : Le chemin vers le fichier contenant l'image

Au niveau du code, ça donne ça :

// Attributs

GLuint m_id;
std::string m_fichierImage;

Le constructeur prendra en paramètre une string qui représentera le chemin vers le fichier image. L'ID quant à lui sera généré par OpenGL :

// Constructeur

Texture(std::string fichierImage);

On rajoutera au passage un accesseur pour l'attribut m_id et un mutateur pour m_fichierImage au cas où devrions spécifier une image après déclaration de l'objet. L'accesseur sera important pour la suite :

GLuint getID() const;
void setFichierImage(const std::string &fichierImage);

Sans oublier les includes de la SDL, de SDL_image et quelques autres voici ce que ça nous donne :

#ifndef DEF_TEXTURE
#define DEF_TEXTURE

// Include

#ifdef WIN32
#include <GL/glew.h>

#else
#define GL3_PROTOTYPES 1
#include <GL3/gl3.h>

#endif

#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <iostream>
#include <string>

// Classe Texture

class Texture
{
    public:

    Texture(std::string fichierImage);
    ~Texture();
    bool charger();
    GLuint getID() const;
    void setFichierImage(const std::string &fichierImage);

    private:

    GLuint m_id;
    std::string m_fichierImage;
};

#endif

Pour l'implémentation de ce début de code vous savez comment faire : :p

#include "Texture.h"

// Constructeur

Texture::Texture(std::string fichierImage) : m_id(0), m_fichierImage(fichierImage)
{

}

// Destructeur

Texture::~Texture()
{

}

// Méthodes

bool Texture::charger()
{

}

GLuint Texture::getID() const
{
    return m_id;
}

void Texture::setFichierImage(const std::string &fichierImage)
{
    m_fichierImage = fichierImage;
}

La méthode charger

Chargement de l'image dans une SDL_Surface

Maintenant que l'on a un squelette de classe propre, nous pouvons nous lancer dans la création de texture. La première étape consiste à charger un fichier image en mémoire grâce à la librairie SDL_image. Pour cela rien de plus simple, il existe une et unique fonction pour charger plus d'une dizaine de formats d'image différents ! Que demande le peuple.

La fonction est la suivante :

SDL_Surface *IMG_Load(const char *file)

  • file : Chemin du fichier image

La fonction renvoie une SDL_surface qui contiendra tous les pixels nécessaires.

Pensez bien à linker la librairie SDL_image, vous aurez une erreur de compilation dans le cas contraire.

Pour le chemin du fichier, nous donnerons l'attribut m_fichierImage, ou plutôt la chaine C de cet attribut car la fonction demande un tableau de caractère. ;)

bool Texture::charger()
{
    // Chargement de l'image dans une surface SDL

    SDL_Surface *imageSDL = IMG_Load(m_fichierImage.c_str());
}

Attention cependant, la fonction peut renvoyer un pointeur sur 0. Il faut donc gérer cette erreur au cas où l'image n'existerait pas ou si le chemin donné contient une erreur. En cas de problème, on affiche alors un message d'erreur grâce à la fonction SDL_GetError() :

char* SDL_GetError(void);

Cette fonction permet de renvoyer la dernière erreur qu'a rencontrée la SDL (dans une chaine de char). Donc en cas d'erreur de chargement, on inclut le résultat de cette fonction dans un flux cout :

bool Texture::charger()
{
    // Chargement de l'image dans une surface SDL

    SDL_Surface *imageSDL = IMG_Load(m_fichierImage.c_str());

    if(imageSDL == 0)
    {
        std::cout << "Erreur : " << SDL_GetError() << std::endl;
        return false;
    }
}
Génération de l'ID

On a vu tout à l'heure ce qu'étaient les objets OpenGL et on sait également que nous pouvons les gérer grâce à leur ID. Nous allons maintenant voir comment générer cet ID. Pour ce faire, il existe une fonction déjà toute prête dans OpenGL :

GLuint glGenTextures(GLsizei number,  GLuint *textures);

  • number : Le nombre d'ID à initialiser. Nous mettrons toujours la valeur 1

  • textures : Un tableau de type GLuint. On peut aussi mettre l'adresse d'une variable GLuint pour initialiser un seul ID de texture (et c'est ce qu'on fera)

Les fonctions-générateurs d'ID commenceront toujours par le préfixe glGenXXX(). Les paramètres seront également les mêmes.

Pour générer un ID de texture, il suffit d'utiliser cette fonction en donnant en paramètre l'attribut m_id de notre classe Texture :

// Génération de l'ID

glGenTextures(1, &m_id);

On appelle cette fonction juste après avoir charger l'image en mémoire :

bool Texture::charger()
{
    // Chargement de l'image dans une surface SDL

    SDL_Surface *imageSDL = IMG_Load(m_fichierImage.c_str());

    if(imageSDL == 0)
    {
        std::cout << "Erreur : " << SDL_GetError() << std::endl;
        return false;
    }

    // Génération de l'ID

    glGenTextures(1, &m_id);
}
Le verrouillage

Je vous ai parlé rapidement du verrouillage d'objet tout à l'heure, ceci permettait à OpenGL de verrouiller un objet pour travailler dessus. Tous les objets OpenGL doivent être verrouillés pour être configurés (et même pour être utilisés !) sinon vous ne pourrez rien faire avec.

On utilisera une fonction simple pour verrouiller nos objets. Ce sera d'ailleurs la même pour les déverrouiller. :p Voici son prototype :

void glBindTexture(GLenum target,  GLuint  texture);

  • target : C'est un paramètre que vous retrouvez souvent avec tous les objets, nous le verrons même plusieurs fois dans ce chapitre. Il correspond au type de l'objet que l'on veut créer, nous lui affecterons la valeur GL_TEXTURE_2D en ce qui concerne les textures.

  • texture : C'est l'ID de l'objet, nous lui donnerons la valeur de l'attribut m_id. La valeur de m_id et non un pointeur cette fois ci ! :)

Une fois de plus, cette fonction sera utilisée par tous les objets. Elle sera toujours de la forme glBindXXX().

Voici donc comment utiliser la fonction dans notre cas :

// Verrouillage

glBindTexture(GL_TEXTURE_2D, m_id);

Tiens au passage, vu que l'on a vu le verrouillage d'objets, nous allons voir maintenant le déverrouillage qui permet à OpenGL d’arrêter de se concentrer sur l'objet en cours, ce qui permet par extension d’empêcher les modifications.

Pour réaliser cette opération, on utilisera la même fonction mais avec le paramètre target non plus égal à la valeur de l'ID de la texture mais avec la valeur 0 (la valeur nulle quoi). En gros, on dit à OpenGL : "Verrouille l'objet possédant l'ID 0, soit rien du tout". :)

// Déverrouillage

glBindTexture(GL_TEXTURE_2D, 0);

Pensez bien à garder la valeur du paramètre target à GL_TEXTURE_2D, c'est important sinon le déverrouillage ne s'effectuera pas.

Petit récap :

bool Texture::charger()
{
    // Chargement de l'image dans une surface SDL

    SDL_Surface *imageSDL = IMG_Load(m_fichierImage.c_str());

    if(imageSDL == 0)
    {
        std::cout << "Erreur : " << SDL_GetError() << std::endl;
        return false;
    }

    // Génération de l'ID

    glGenTextures(1, &m_id);

    // Verrouillage

    glBindTexture(GL_TEXTURE_2D, m_id);

    // Déverrouillage

    glBindTexture(GL_TEXTURE_2D, 0);
}
Configuration de la texture

Notre texture a un ID généré, elle est également verrouillée, on peut maintenant passer à sa configuration. :D

Grossièrement parlant, pour avoir une texture dans OpenGL il suffit de copier les pixels d'une image dans la texture. C'est aussi simple que ça. Seulement voilà, il existe plusieurs formats d'image et certaines contiennent plus de données que d'autres, ...

Hein je croyais que la librairie SDL_image permettait justement de gérer tous ces formats ? :(

Et bien oui vous avez raison, c'est bien SDL_image qui gère les différents formats de l'image. Il existe cependant une chose qu'elle ne peut pas nous dire automatiquement.

Vous n'êtes pas sans savoir qu'un pixel est composé de 3 couleurs (rouge, vert et bleu) ... Les pixels d'une image n’échappent pas à cette règle, chacun d'entre eux est composé de ces 3 couleurs. Seulement voilà, il existe, pour certains formats, une quatrième composante qui s'appelle la composante Alpha. Cette composante permet de stocker le "niveau de transparence" d'une image.

Pour charger correctement une texture, il faut savoir si cette valeur alpha est présente ou non, et heureusement pour nous, la librairie SDL_image est capable de nous le dire. En effet, dans la structure SDL_Surface utilisée au début de la méthode charger(), il existe un champ BytesPerPixel qui permet de dire s'il y a 3 ou 4 couleurs. Nous devrons donc d'abord récupérer cette valeur avant de copier les pixels dans la texture.

Bien on arrête là pour la théorie, on passe au code.

On veut savoir si une image possède 3 ou 4 couleurs, on récupère donc le champ imageSDL->format->BytesPerPixel pour le vérifier puis on met le tout dans un bloc if. Si on a une valeur inconnue, on arrête le chargement de la texture pour éviter de se retrouver avec une grosse erreur puis on n'oublie pas de libérer la surface SDL avant de quitter la méthode :

// Détermination du nombre de composantes

if(imageSDL->format->BytesPerPixel == 3)
{

}

else if(imageSDL->format->BytesPerPixel == 4)
{

}

// Dans les autres cas, on arrête le chargement

else
{
    std::cout << "Erreur, format de l'image inconnu" << std::endl;
    SDL_FreeSurface(imageSDL);

    return false;
}

On sait maintenant qu'il faut faire attention au bidule alpha, mais qu'est qu'on met à l'intérieur des if ? Ils sont tout vide. :(

C'est normal, il manque encore quelque chose. Comme on l'a vu plus tôt, OpenGL a besoin de savoir si la composante alpha existe ou pas. Seulement si on lui donne la valeur 3 ou 4 ça ne va pas lui suffire, il faudra envoyer une autre valeur qui sera un peu comme le paramètre GL_TEXTURE_2D que l'on a vu plus haut. Avec ce paramètre, il comprendra mieux ce qu'on lui enverra.

Il y aura deux cas à gérer :

  • Soit l'image ne contiendra pas la composante alpha et dans ce cas on retiendra la constante GL_RGB

  • Soit l'image contiendra la composante alpha et dans ce cas on retiendra la constante GL_RGBA

Au niveau du code, on utilisera une variable de type GLenum pour retenir cette valeur. On l'appellera formatInterne, vous verrez pourquoi juste après :

// Détermination du nombre de composantes

GLenum formatInterne(0);

if(imageSDL->format->BytesPerPixel == 3)
{
    formatInterne = GL_RGB;
}

else if(imageSDL->format->BytesPerPixel == 4)
{
    formatInterne = GL_RGBA;
}

// Dans les autres cas, on arrête le chargement

else
{
    std::cout << "Erreur, format interne de l'image inconnu" << std::endl;
    SDL_FreeSurface(imageSDL);

    return false;
}

Il ne manque plus qu'une chose à faire. Selon le système d'exploitation ou même les images que vous utiliserez, les pixels ne seront pas stockés dans le même ordre. Par exemple sous Windows, la plupart des formats stockent leurs pixels selon l'ordre Rouge Vert Bleu (RGB) sauf les images au format BMP. Ceux-ci voient leurs pixels stockés selon l'ordre Bleu Vert Rouge (BGR). C'est un problème que nous devons gérer car certains auront la belle surprise de voir leurs images avec des couleurs complétement inversées (Imaginez un Dark Vador en blanc :p ).

Il faut donc dire à OpenGL dans quel ordre les pixels sont stockés, et pour ça on va utiliser une autre variable de type GLenum que l'on appelera format :

// Détermination du format et du format interne

GLenum formatInterne(0);
GLenum format(0);

Pour connaitre l'ordre des pixels, nous devons utiliser un autre champ de la structure imageSDL. Ce champ sera imageSDL->format->Rmask.

Il existe 4 champs similaires Rmask, Gmask, Bmask et Amask qui représente chacun la position de sa couleur à l'aide d'une valeur hexadécimal. Nous utiliserons le premier champ (Rmask), même si nous pouvions utiliser n'importe lequel. Sauf le dernier car il se trouve toujours à la fin quelque soit le format d'image.

Nous devons donc tester cette valeur pour connaitre la position de la couleur rouge. Si sa valeur est égale à 0xff alors elle est placée au début, sinon c'est qu'elle se trouve à la fin :

// Format de l'image

GLenum formatInterne(0);
GLenum format(0);

// Détermination du format et du format interne

if(imageSDL->format->BytesPerPixel == 3)
{
    // Format interne

    formatInterne = GL_RGB;

    // Format

    if(imageSDL->format->Rmask == 0xff)
    {}

    else
    {}
}

else if(imageSDL->format->BytesPerPixel == 4)
{
    // Format interne

    formatInterne = GL_RGBA;

    // Format

    if(imageSDL->format->Rmask == 0xff)
    {}

    else
    {}
}

// Dans les autres cas, on arrête le chargement

else
{
    std::cout << "Erreur, format interne de l'image inconnu" << std::endl;
    SDL_FreeSurface(imageSDL);

    return false;
}

Il faut maintenant affecter une valeur à la variable format. Il y a 4 cas à gérer :

  • Rouge en premier pour une image à 3 composantes

  • Rouge en dernier pour une image à 3 composantes

  • Rouge en premier pour une image à 4 composantes

  • Rouge en dernier pour une image à 4 composantes

Ces quatre cas seront représentés par les constantes suivantes :

  • GL_RGB

  • GL_BGR

  • GL_RGBA

  • GL_BGRA

Il n'y a plus qu'à affecter la bonne constante à la variable format :

// Format pour 3 couleurs

if(imageSDL->format->Rmask == 0xff)
    format = GL_RGB;

else
    format = GL_BGR;

....

// Format pour 4 couleurs

if(imageSDL->format->Rmask == 0xff)
    format = GL_RGBA;

else
    format = GL_BGRA;

Ce qui donne au final :

// Format de l'image

GLenum formatInterne(0);
GLenum format(0);

// Détermination du format et du format interne pour les images à 3 composantes

if(imageSDL->format->BytesPerPixel == 3)
{
    // Format interne

    formatInterne = GL_RGB;

    // Format

    if(imageSDL->format->Rmask == 0xff)
        format = GL_RGB;

    else
        format = GL_BGR;
}

// Détermination du format et du format interne pour les images à 4 composantes

else if(imageSDL->format->BytesPerPixel == 4)
{
    // Format interne

    formatInterne = GL_RGBA;

    // Format

    if(imageSDL->format->Rmask == 0xff)
        format = GL_RGBA;

    else
        format = GL_BGRA;
}

// Dans les autres cas, on arrête le chargement

else
{
    std::cout << "Erreur, format interne de l'image inconnu" << std::endl;
    SDL_FreeSurface(imageSDL);

    return false;
}

Pfiou tout ce gourbi pour déterminer deux valeurs ! :lol:

On a fait le plus dur, il ne nous reste plus qu'à copier les fameux pixels dans la texture. Pour ça, on va utiliser la fonction suivante (ne soyez pas surpris du nombre de paramètres :p ) :

void glTexImage2D(GLenum target,  GLint level,  GLint internalFormat,  GLsizei width,  GLsizei height,  GLint border,  GLenum format,  GLenum type,  const GLvoid * data);

  • target : Comme on l'a vu précédemment, pour les textures on affectera toujours la valeur GL_TEXTURE_2D

  • level : Paramètre que nous n’utiliserons pas, on le mettra à 0

  • internalFormat : Tiens ! On vient de le déterminer juste avant celui-la

  • width : Largeur de l'image qui est contenue dans le champ imageSDL->w

  • height : Hauteur de l'image qui est contenue dans le champ imageSDL->h

  • border : Paramètre utile quand vous avez une bordure sur votre image. Nous donnerons la valeur 0 en général

  • format : Oh lui aussi on l'a trouvé !

  • type : Type de donnée des pixels (float, int, ...). Nous lui donnerons un type d'OpenGL : GL_UNSIGNED_BYTE

  • data : Ce sont les pixels de l'image, on lui donnera l'adresse du tableau imageSDL->pixels

Ça en fait des paramètres tout ça ! Mais au moins on n'utilise qu'une seule fonction pour remplir notre texture.

Voyons son implémentation dans le code :

// Copie des pixels

glTexImage2D(GL_TEXTURE_2D, 0, formatInterne, imageSDL->w, imageSDL->h, 0, format, GL_UNSIGNED_BYTE, imageSDL->pixels);

On refait un petit récap :

bool Texture::charger()
{
    // Chargement de l'image dans une surface SDL

    SDL_Surface *imageSDL = IMG_Load(m_fichierImage.c_str());

    if(imageSDL == 0)
    {
        std::cout << "Erreur : " << SDL_GetError() << std::endl;
        return false;
    }

    // Génération de l'ID

    glGenTextures(1, &m_id);

    // Verrouillage

    glBindTexture(GL_TEXTURE_2D, m_id);

    // Format de l'image

    GLenum formatInterne(0);
    GLenum format(0);

    // Détermination du format et du format interne pour les images à 3 composantes

    if(imageSDL->format->BytesPerPixel == 3)
    {
        // Format interne

        formatInterne = GL_RGB;

        // Format

        if(imageSDL->format->Rmask == 0xff)
            format = GL_RGB;

        else
            format = GL_BGR;
    }

    // Détermination du format et du format interne pour les images à 4 composantes

    else if(imageSDL->format->BytesPerPixel == 4)
    {
        // Format interne

        formatInterne = GL_RGBA;

        // Format

        if(imageSDL->format->Rmask == 0xff)
            format = GL_RGBA;

        else
            format = GL_BGRA;
    }

    // Dans les autres cas, on arrête le chargement

    else
    {
        std::cout << "Erreur, format interne de l'image inconnu" << std::endl;
        SDL_FreeSurface(imageSDL);

        return false;
    }

    // Copie des pixels

    glTexImage2D(GL_TEXTURE_2D, 0, formatInterne, imageSDL->w, imageSDL->h, 0, format, GL_UNSIGNED_BYTE, imageSDL->pixels);

    // Déverrouillage

    glBindTexture(GL_TEXTURE_2D, 0);
}

Et voila ! La texture contient enfin ses propres pixels issus de notre fichier image.

Les filtres

Allez courage, il ne nous manque plus qu'une chose à faire.

Je vais vous parler rapidement d'une notion que je connais très mal : la notion de filtres. Je ne m'y connais pas assez pour tenir tout un pavé alors je ferai vite. :lol:

Un filtre permet de gérer la qualité d'affichage d'une texture. Il permet à OpenGL de savoir s'il doit afficher une image en mode pixelisé ou en mode lisse. On serait tenté de vouloir que toutes les textures soient lisses au moment de l'affichage mais sachez que ça joue sur la vitesse de votre programme. En effet, plus vous voudrez de belles textures à l'écran, plus votre matériel sera sollicité. Alors bon, aujourd'hui le problème est moins voyant qu'il y a 10 ans mais on est toujours aujourd'hui dans cette optique d'optimisation du rendu.

Pour vous éviter un gros bloc de théorie je vais résumer ma pensée en quelques lignes (en plus ça m'arrange, j'ai mal aux doigts :p ).

Il existe deux types d'affichage pour une texture :

  • Soit la texture est proche de l'écran et dans ce cas il vaut mieux la lisser, car le joueur a plus de chance de la voir

  • Soit la texture est éloignée de l'écran et dans ce cas on peut se permettre de "l'afficher à l'arrache", le joueur ne s'en rendre même pas compte

Ces deux cas vont correspondre à deux filtres que nous allons créer.

Pour créer un filtre, on utilise la fonction suivante :

void glTexParameteri(GLenum target,  GLenum pname,  GLint param);

Cette fonction permet d'envoyer des paramètres supplémentaires à nos textures, dans notre cas des filtres. Voici ses paramètres :

  • target : On ne le présente plus

  • pname : Type de paramètre à envoyer, ici le filtre

  • param : La valeur du paramètre

Avec cette fonction, nous pourrons créer deux filtres : un pour le cas où la texture est proche de l'écran et un autre pour le cas où elle en est éloignée.

Pour le premier filtre :

  • Le filtre permettant de gérer les "textures proches" s'appelle GL_TEXTURE_MIN_FILTER, ce sera donc la valeur du paramètre pname.

  • Nous voulons que les "textures proches" soient "lisses", nous donnerons donc la valeur GL_LINEAR au paramètre param

Code :

// Application des filtres

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

Pour le second filtre :

  • Le filtre permettant de gérer les "textures éloignées" s'appelle GL_TEXTURE_MAG_FILTER, ce sera donc la valeur du paramètre pname.

  • Nous voulons que les "textures éloignées" soient "pixelisées", nous donnerons donc la valeur GL_NEAREST au paramètre param

Code :

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

Grâce à ces filtres, nous pourrons avoir un bon équilibre entre érgonomie pour le joueur et rapidité pour le CPU et la carte graphique.

Petit récap :

bool Texture::charger()
{
    // Chargement de l'image dans une surface SDL

    SDL_Surface *imageSDL = IMG_Load(m_fichierImage.c_str());

    if(imageSDL == 0)
    {
        std::cout << "Erreur : " << SDL_GetError() << std::endl;
        return false;
    }

    // Génération de l'ID

    glGenTextures(1, &m_id);

    // Verrouillage

    glBindTexture(GL_TEXTURE_2D, m_id);

    // Format de l'image

    GLenum formatInterne(0);
    GLenum format(0);

    // Détermination du format et du format interne pour les images à 3 composantes

    if(imageSDL->format->BytesPerPixel == 3)
    {
        // Format interne

        formatInterne = GL_RGB;

        // Format

        if(imageSDL->format->Rmask == 0xff)
            format = GL_RGB;

        else
            format = GL_BGR;
    }

    // Détermination du format et du format interne pour les images à 4 composantes

    else if(imageSDL->format->BytesPerPixel == 4)
    {
        // Format interne

        formatInterne = GL_RGBA;

        // Format

        if(imageSDL->format->Rmask == 0xff)
            format = GL_RGBA;

        else
            format = GL_BGRA;
    }

    // Dans les autres cas, on arrête le chargement

    else
    {
        std::cout << "Erreur, format interne de l'image inconnu" << std::endl;
        SDL_FreeSurface(imageSDL);

        return false;
    }

    // Copie des pixels

    glTexImage2D(GL_TEXTURE_2D, 0, formatInterne, imageSDL->w, imageSDL->h, 0, format, GL_UNSIGNED_BYTE, imageSDL->pixels);

    // Application des filtres

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    // Déverrouillage

    glBindTexture(GL_TEXTURE_2D, 0);
}
Fin de la méthode

Pffffffiou, entre les ID, le verrouillage, les filtres et tout ça je n'en puis plus (comme dirait Hooper) !

Il est temps de terminer cette méthode. Il ne reste plus qu'à libérer la surface SDL qui contenait les pixels et à retourner la valeur true (car la méthode s'est normalement terminée sans erreur) :

// Fin de la méthode

SDL_FreeSurface(imageSDL);
return true;

Récap final :

bool Texture::charger()
{
    // Chargement de l'image dans une surface SDL

    SDL_Surface *imageSDL = IMG_Load(m_fichierImage.c_str());

    if(imageSDL == 0)
    {
        std::cout << "Erreur : " << SDL_GetError() << std::endl;
        return false;
    }

    // Génération de l'ID

    glGenTextures(1, &m_id);

    // Verrouillage

    glBindTexture(GL_TEXTURE_2D, m_id);

    // Format de l'image

    GLenum formatInterne(0);
    GLenum format(0);

    // Détermination du format et du format interne pour les images à 3 composantes

    if(imageSDL->format->BytesPerPixel == 3)
    {
        // Format interne

        formatInterne = GL_RGB;

        // Format

        if(imageSDL->format->Rmask == 0xff)
            format = GL_RGB;

        else
            format = GL_BGR;
    }

    // Détermination du format et du format interne pour les images à 4 composantes

    else if(imageSDL->format->BytesPerPixel == 4)
    {
        // Format interne

        formatInterne = GL_RGBA;

        // Format

        if(imageSDL->format->Rmask == 0xff)
            format = GL_RGBA;

        else
            format = GL_BGRA;
    }

    // Dans les autres cas, on arrête le chargement

    else
    {
        std::cout << "Erreur, format interne de l'image inconnu" << std::endl;
        SDL_FreeSurface(imageSDL);

        return false;
    }

    // Copie des pixels

    glTexImage2D(GL_TEXTURE_2D, 0, formatInterne, imageSDL->w, imageSDL->h, 0, format, GL_UNSIGNED_BYTE, imageSDL->pixels);

    // Application des filtres

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    // Déverrouillage

    glBindTexture(GL_TEXTURE_2D, 0);

    // Fin de la méthode

    SDL_FreeSurface(imageSDL);
    return true;
}

Enfin ! Nous avons une classe Texture qui permet de charger des fichiers images pour les afficher dans nos scènes 3D. Le petit truc que je ne vous ai pas dit, c'est que cette méthode est incomplète, il manque encore quelque chose mais nous verrons ça dans la dernière sous-partie de ce chapitre. Je ne vais pas vous assassiner plus pour l'instant. :lol: Nous avons ce qu'il nous faut pour afficher une texture sur un modèle 3D et c'est le principal pour le moment. Je veux que vous vous familiarisez avec l'affichage de texture avant d'aller plus loin. :)

Plaquage

Affichage d'une texture

Le plus dur est derrière nous, nous avons appris à déclarer et remplir un texture au sein d'OpenGL. On va maintenant apprendre à les afficher. Et la bonne nouvelle, c'est que vous savez déjà comment faire. :p Ça se passe de la même façon qu'avec les vertices ou les couleurs.

Pour débuter, on va se contenter d'afficher notre première texture sur un simple carré afin de se concentrer sur l'essentiel. Nous utiliserons ensuite notre classe Cube pour afficher un modèle un peu plus élaboré. D'ailleurs, voyons ensemble la première texture que nous serons capables de traiter :

Elle nous sera utile tout au long du chapitre, vous la trouverez dans l'archive que vous avez téléchargée au début sous le nom de crate13.jpg. Au pire des cas, faites un clique droit sur l'image ci-dessus pour la récupérer. Nous aurons l'occasion d'en utiliser d'autres aussi.

Coordonnées de Texture

Je ne sais pas si vous lisez toujours les sous-titres oranges et bleues, mais si vous lisez une ligne plus haut vous tomberez sur le terme "coordonnées de texture".

Oh ! C'est quoi ces coordonnées ? C'est similaire aux vertices et tout ça ?

Et bien oui ! On peut faire une analogie avec les vertices car, pour afficher une texture, il faut lui donner une sorte de position. Seulement attention, on ne parle pas ici de coordonnées spatiales avec un repère 3D, une matrice et tout ça ...

Je vais vous expliquer ça avec deux schéma.

Vous savez depuis un moment que les vertices sont composés de coordonnées spatiales (X, Y et Z) :

Avec ce système, vous pouvez donner n'importe quelle taille à une forme 3D comme le cube des chapitres précédents par exemple. Et bien les textures se comportent de la même façon. A une exception près : les coordonnées de texture sont toujours comprises entre 0 et 1 :

On peut voir sur le schéma le point d'origine de la texture en bas à gauche ainsi que 3 autres coordonnées. La plupart du temps, nous n'utiliserons que ces 4 points vu qu'ils correspondent aux coins de la texture. Mais sachez qu'il est tout à fait possible d'avoir des coordonnées du type (0.27; 0.38) pour ne récupérer qu'une partie de la texture.

Attention, ces nombres n'ont aucun rapport avec le repère 3D. Elles sont propres à la texture.

Pour plaquer une texture sur une surface il faudra simplement faire correspondre les coordonnées de texture avec les bons vertices :

La chose se complexifie légèrement quand on n'oublie le carré et que l'on parle des deux triangles, mais le tout reste tout de même assez simple :

Simple n'est-ce pas ? :p

Vous savez maintenant ce que sont les coordonnées de texture : ce sont des points, correspondant généralement aux coins d'une texture, qui permettent de la plaquer sur une surface.

Affichage d'une texture

Nous avons toutes les cartes en main pour afficher notre première texture. Nous n'avons plus qu'à instancier la classe Texture et à utiliser ses coordonnées.

On reprend donc notre bonne vielle classe SceneOpenGL pour déclarer un objet de type Texture (n'oubliez pas d'inclure le header) qu'on appellera simplement texture, puis on la chargera en mémoire. Pensez également à effacer tout le code qui concernait le cube des chapitres précédents :

void SceneOpenGL::bouclePrincipale()
{
    // Variables

    unsigned int frameRate (1000 / 50);
    Uint32 debutBoucle(0), finBoucle(0), tempsEcoule(0);

    // Matrices

    mat4 projection;
    mat4 modelview;

    projection = perspective(70.0, (double) m_largeurFenetre / m_hauteurFenetre, 1.0, 100.0);
    modelview = mat4(1.0);

    // Texture

    Texture texture("Textures/Caisse.jpg");
    texture.charger();

    // Boucle principale

    while(!m_input.terminer())
    {
        /* *** Boucle principale *** */
    }
}

Je vous redonne au passage le code de base pour la boucle principale :

// Boucle principale

while(!m_input.terminer())
{
    // On définit le temps de début de boucle

    debutBoucle = SDL_GetTicks();

    // Gestion des évènements

    m_input.updateEvenements();

    if(m_input.getTouche(SDL_SCANCODE_ESCAPE))
       break;

    // Nettoyage de l'écran

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Placement de la caméra

    modelview = lookAt(vec3(0, 0, 2), vec3(0, 0, 0), vec3(0, 1, 0));

    /* *** Rendu *** */

    // Actualisation de la fenêtre

    SDL_GL_SwapWindow(m_fenetre);

    // Calcul du temps écoulé

    finBoucle = SDL_GetTicks();
    tempsEcoule = finBoucle - debutBoucle;

    // Si nécessaire, on met en pause le programme

    if(tempsEcoule < frameRate)
        SDL_Delay(frameRate - tempsEcoule);
}

Remarquez que j'ai placé la caméra au point (0; 0; 2).

Bien, avec ce code propre on va pouvoir bosser tranquillement.

Notre objectif depuis le début de ce chapitre est l'affichage d'une texture sur une surface, et de préférence un carré. On va commencer par créer nos coordonnées pour les vertices. D'ailleurs, vous savez déjà depuis un moment comment faire un carré avec OpenGL. Je vais donc vous demander de me coder vous-même les vertices pour un carré avec des arrêtes mesurant 4 unités de longueur :

Cependant, je souhaite attirer votre attention sur deux points :

  • Faites votre carré en 3 dimensions, il faut donc intégrer la coordonnée Z

  • Faites attention au repère qui se trouve au milieu de la surface et non en bas à gauche.

Voila pour les explications, en gros faites un carré à l'ancienne avec 3 dimensions.

...

Fini ? Voici la solution :

// Vertices

float vertices[] = {-2, -2, -2,   2, -2, -2,   2, 2, -2,   // Triangle 1
                    -2, -2, -2,   -2, 2, -2,   2, 2, -2};  // Triangle 2

Aaaah, faire un carré c'est tout de même plus simple que de devoir faire un cube complet.

L'étape suivante consisterait normalement à colorier notre nouvelle forme. Cependant, nous n'en avons plus besoin vu que nous avons une texture qui va s'appliquer dessus.
Dans notre code, au lieu de déclarer un tableau de couleurs nous allons déclarer un tableau de coordonnées de texture. Et comme je suis sadique, je vais vous demander de me coder ce tableau vous-même comme des grands. :p N’ayez pas peur c'est exactement le même principe qu'avec les vertices et les couleurs.

Pour vous aider dans cette tâche je vais vous donner deux petites schémas avec les informations nécessaires :

Rien de bien compliqué, vous devez simplement faire comme s'il s'agissait de vertices. Vous avez toutes les coordonnées (x, y) et vous devez en faire un tableau. Faites attention cependant à leur ordre à l'intérieur du tableau car celles-ci sont liées à vos vertices (au même titre que les couleurs).

... Pause café ...

Fini. Allez on passe à la correction.

// Coordonnées de texture

float coordTexture[] = {0, 0,   1, 0,   1, 1,     // Triangle 1
                        0, 0,   0, 1,   1, 1};    // Triangle 2

On a maintenant un carré, une texture et des coordonnées permettant de la plaquer dessus. On n'a plus qu'à donner tout ça à OpenGL et le tour est joué. :)

Ah mais justement : comment on envoie les coordonnées à OpenGL ?

Bonne question, je vous ai dit tout à l'heure que les coordonnées de texture se géraient de la même façon que les vertices et les couleurs. On va donc utiliser donc un Vertex Array pour envoyer toutes les données à OpenGL.

Vous vous souvenez des Vertex Array quand même ? Ce sont des tableaux qui font le lien entre les coordonnées et OpenGL. Le tableau Vertex Attrib 0 permet d'envoyer les vertices et le tableau Vertex Attrib 1 permet d'envoyer les couleurs. Maintenant nous allons utiliser le tableau Vertex Attrib 2 qui va nous permettre d'envoyer nos coordonnées de texture.

Dans notre code, il nous faut appeler la fonction glVertexAttribPointer() avec l'indice 2 (et non 1). Il faut également donner la valeur 2 au paramètre size car nos coordonnées de texture ne sont constituées que de couple de valeurs (x, y) et non de triplet (x, y, z). On pensera à donner le tableau coordTexture en dernier paramètres sinon on ne risque pas d'envoyer grand chose à OpenGL. :lol:

Voici à quoi ressemblerait l'appel à glVertexAttribPointer() dans notre cas :

// Envoi des coordonnées de texture

glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, coordTexture);

Puisque nous envoyons des données à travers un tableau Vertex Attrib, il faut penser à l'activer grâce à la fonction glEnableVertexAttribArray(). On appelle donc celle-ci avec la valeur 2 pour activer le tableau d'indice 2 :

// Envoi des coordonnées de texture

glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, coordTexture);
glEnableVertexAttribArray(2);

On place ce bout de code au même endroit où on envoyait les couleurs, c'est-à-dire entre les deux appels à la fonction glUseProgram() :

// Activation du shader

glUseProgram(shader.getProgramID());

    // Envoi des vertices

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertices);
    glEnableVertexAttribArray(0);

    // Envoi des coordonnées de texture

    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, coordTexture);
    glEnableVertexAttribArray(2);

    // Envoi des matrices et rendu

    ....

    // Désactivation des tableaux

    glDisableVertexAttribArray(2);
    glDisableVertexAttribArray(0);

// Désactivation du shader

glUseProgram(0);

Pensez à appeler la fonction glDisableVertexAttribArray() avec la valeur 2 pour désactiver votre tableau.

Tiens en parlant de shader, nous allons devoir modifier les codes sources (le petits fichiers dans le dossier Shaders) qu'il utilise afin de pouvoir intégrer l'affichage de texture. En effet, ceux que nous avons utilisés jusqu'à maintenant ne gèrent que la couleur, nous en avons donc besoin de nouveaux pour gérer notre nouvelle façon d'afficher.

Ces nouveaux codes sources se nomment texture.vert et texture.frag et devraient être présents dans votre dossier Shaders :

// Shader gérant les texture

Shader shaderTexture("Shaders/texture.vert", "Shaders/texture.frag");
shaderTexture.charger();

Faisons un petit récap (sans la boucle principale) pour voir où nous en sommes pour le moment :

void SceneOpenGL::bouclePrincipale()
{
    // Variables

    unsigned int frameRate (1000 / 50);
    Uint32 debutBoucle(0), finBoucle(0), tempsEcoule(0);

    // Matrices

    mat4 projection;
    mat4 modelview;

    projection = perspective(70.0, (double) m_largeurFenetre / m_hauteurFenetre, 1.0, 100.0);
    modelview = mat4(1.0);

    // Vertices

    float vertices[] = {-2, -2, -2,   2, -2, -2,   2, 2, -2,   // Triangle 1
                        -2, -2, -2,   -2, 2, -2,   2, 2, -2};  // Triangle 2

    // Coordonnées de texture

    float coordTexture[] = {0, 0,   1, 0,   1, 1,     // Triangle 1
                            0, 0,   0, 1,   1, 1};    // Triangle 2

    // Texture

    Texture texture("Textures/Caisse.jpg");
    texture.charger();

    // Shader

    Shader shaderTexture("Shaders/texture.vert", "Shaders/texture.frag");
    shaderTexture.charger();

    // Boucle principale

    while(!m_input.terminer())
    {
        /* *** Rendu *** */
    }
}

Nous ferons un petit récap de la boucle principale dans un instant. Concentrons-nous d'abord sur le code d'affichage.

Celui-ci va commencer par l'activation du shader (qui gère les textures maintenant) suivie de l'envoi des vertices, des coordonnées de texture et des matrices ainsi que de l'appel à la fonction de rendu glDrawArrays() :

// Activation du shader

glUseProgram(shaderTexture.getProgramID());

    // Envoi des vertices

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertices);
    glEnableVertexAttribArray(0);

    // Envoi des coordonnées de texture

    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, coordTexture);
    glEnableVertexAttribArray(2);

    // Envoi des matrices

    glUniformMatrix4fv(glGetUniformLocation(shaderTexture.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));
    glUniformMatrix4fv(glGetUniformLocation(shaderTexture.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));

    // Rendu

    glDrawArrays(GL_TRIANGLES, 0, 6);

    // Désactivation des tableaux

    glDisableVertexAttribArray(2);
    glDisableVertexAttribArray(0);

// Désactivation du shader

glUseProgram(0);

Ah encore une petite chose, normalement je devrais faire le sadique en vous demandant de compiler votre code maintenant. Seulement si vous le faites vous aurez au mieux un écran noir. :p

Il manque donc encore une dernière chose à faire. Pour le moment, nous nous contentons d'envoyer les coordonnées de texture à OpenGL, mais à aucun moment nous lui disons quelle texture il doit afficher. Et comme OpenGL n'est pas devin, il va nous envoyer balader et afficher un carré noir.

Pour éviter cela, il faut faire une opération que vous savez déjà faire : le verrouillage de texture. En effet, grâce à ça, OpenGL saura ce qu'il doit afficher exactement comme au moment de la configuration où il savait sur quelle texture travailler.

Pour effectuer cette opération, nous allons réutiliser la fonction glBindTexture() avec le même paramètre que d'habitude soit l'ID de la texture. D'où l'utilité de la méthode getID() que je vous avais demandée de coder précédemment.

En définitif, nous devons appeler la fonction glBindTexture() deux fois avec la méthode getID() en tant que paramètre pour le premier appel et la valeur 0 pour le second :

// Verrouillage de la texture

glBindTexture(GL_TEXTURE_2D, texture.getID());

// Rendu

glDrawArrays(GL_TRIANGLES, 0, 6);

// Déverrouillage de la texture

glBindTexture(GL_TEXTURE_2D, 0);

Je vous conseille de verrouiller vos textures uniquement au moment de l'affichage. C'est-à-dire que pour CHAQUE texture vous devez :

  • La verrouiller

  • Afficher votre surface

  • La déverrouiller immédiatement

Si vous ne le faites pas vous risquez d'afficher une mauvaise texture par la suite.

Et si on veut afficher deux textures en même temps sur une surface on fait comment ? On les verrouille toutes les deux ?

Ah là on touche au domaine du multi-texturing, vaste sujet passionnant que nous ne verrons pas maintenant. Nous verrons cela quand nous saurons manipuler les shaders. ;)

On revient au code avec un petit récap de la boucle principale :

// Boucle principale

while(!m_input.terminer())
{
    // On définit le temps de début de boucle

    debutBoucle = SDL_GetTicks();

    // Gestion des évènements

    m_input.updateEvenements();

    if(m_input.getTouche(SDL_SCANCODE_ESCAPE))
       break;

    // Nettoyage de l'écran

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Placement de la caméra

    modelview = lookAt(vec3(0, 0, 2), vec3(0, 0, 0), vec3(0, 1, 0));

    // Activation du shader

    glUseProgram(shaderTexture.getProgramID());

        // Envoi des vertices

        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertices);
        glEnableVertexAttribArray(0);

        // Envoi des coordonnées de texture

        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, coordTexture);
        glEnableVertexAttribArray(2);

        // Envoi des matrices

        glUniformMatrix4fv(glGetUniformLocation(shaderTexture.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));
        glUniformMatrix4fv(glGetUniformLocation(shaderTexture.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));

        // Verrouillage de la texture

        glBindTexture(GL_TEXTURE_2D, texture.getID());

        // Rendu

        glDrawArrays(GL_TRIANGLES, 0, 6);

        // Déverrouillage de la texture

        glBindTexture(GL_TEXTURE_2D, 0);

        // Désactivation des tableaux

        glDisableVertexAttribArray(2);
        glDisableVertexAttribArray(0);

    // Désactivation du shader

    glUseProgram(0);

    // Actualisation de la fenêtre

    SDL_GL_SwapWindow(m_fenetre);

    // Calcul du temps écoulé

    finBoucle = SDL_GetTicks();
    tempsEcoule = finBoucle - debutBoucle;

    // Si nécessaire, on met en pause le programme

    if(tempsEcoule < frameRate)
        SDL_Delay(frameRate - tempsEcoule);
}

Cette fois-ci, notre code est complet. :D Affichons enfin notre première texture !

Heeeeein !

Pourquoi la texture est inversée ? C'est pourtant les bonnes coordonnées qu'on a envoyées non ? Qu'est-ce qui s'passe ? :(

C'est tout à fait normal ne vous inquiétez pas. :p Tout à l'heure, je vous ai dit qu'on n'en arrêtait là avec le code de la classe Texture, et que du coup elle serait incomplète. En effet, la librairie SDL_image n'a pas le même repère que nous pour charger les images. Son repère à elle lui se trouve en haut à gauche alors que le notre se situe en bas à droite :

Les textures se retrouvent donc la tête en bas pour nous. Pour corriger ce problème, il nous suffit d'inverser tous les pixels d'une image au moment de son chargement. C'est le fameux dernier point qu'il nous manquait dans notre méthode charger(). Nous allons régler ça dans un moment.

Mais avant cela, je vous conseille de vous entrainer à afficher quelques textures sur plusieurs carrés pour vous familiariser avec l'affichage de texture. ;)

Améliorations

Si vous n'êtes pas trop fatigués on va pouvoir passer à la dernière partie de ce chapitre. :p

Les objectifs de cette dernière partie sont :

  • De de terminer l'implémentation de la méthode charger() pour afficher nos textures dans le bon sens.

  • D'améliorer la classe Texture en y ajoutant des méthodes comme le constructeur de copie, l'opérateur =, ...

L'inversion des pixels

Théorie

Commençons tout de suite par nous n'occuper du premier point : l'inversion des pixels. Vous avez remarqué (avec effroi) que nos textures s'affichent la tête inversée. Il y a deux façons de régler ce problème :

  • Soit on inverse l’ordre de toutes les coordonnées de texture pour afficher l'image à l'envers (mais qui du coup sera dans le bon sens)

  • Soit on inverse les pixels de l'image avant de la charger coté OpenGL

Comme vous le savez déjà, on va opter pour la seconde solution.

En effet, la première pourrait fonctionner un temps mais une fois que nous chargerons des modèles depuis des fichiers externes il ne sera plus possible d'inverser les coordonnées de texture. Imaginez-vous en train de modifier 20 000 coordonnées de texture à la main. :lol:

L'avantage d'inverser les pixels au moment du chargement c'est qu'il nous suffit d'une méthode pour inverser toutes nos textures. Avouez quand même que c’est un gain de temps énorme, si ce n'est astronomique !

Au niveau de la théorie, il faut juste de créer une copie conforme de la première surface SDL (celle qui contient l'image) puis d'inverser ses pixels. C'est grâce à une boucle que nous pourrons effectuer cette opération.

Cette boucle devra être capable de copier chaque ligne de la texture et d'inverser la position de celle-ci par rapport à l'axe des ordonnées. Et quand je dis copier chaque ligne, je parle de copier TOUS les pixels de la ligne SANS CHANGER L'ORDRE. En effet, les pixels présents sur l'axe des abscisses sont, eux, dans le bon ordre, il n'y que sur l'axe des ordonnées que ça ne va pas. Il faut conserver l'ordre des pixels sur chaque ligne mais inverser ces fameuses lignes par rapport à l'axe des ordonnées :

Essayez de bien comprendre cette notion. Revoyez le rendu de texture de la sous-partie précédente et vous verrez que sur l'axe des abscisses les pixels sont dans le bon ordre mais pas sur l'axe des ordonnées.

La méthode inverserPixels

Pour régler notre problème de texture, nous avons donc choisi d'inverser les pixels d'une image avant son chargement du coté OpenGL.

Pour faire ça proprement, on va déclarer une méthode inverserPixels() dans laquelle nous placerons notre code d’inversement. Cette méthode devra prendre en paramètre la surface SDL à inverser et elle renverra une autre surface SDL contenant l'image inversée.

SDL_Surface* inverserPixels(SDL_Surface *imageSource) const;

On déclare cette méthode en tant que méthode constante car elle ne modifie en rien les attributs de la classe Texture, elle ne fait que renvoyer une surface. ;)

L'implémentation de cette méthode va être rapide, on commence par créer une copie conforme de la surface SDL envoyée en paramètre. Pour créer les surfaces avec la SDL, on va utiliser une fonction que vous devez déjà connaitre si vous avez suivi le tuto de M@téo dessus : la fonction SDL_CreateRGBSurface() :

SDL_Surface* SDL_CreateRGBSurface(Uint32 flags, int width, int  height, int depth, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask)

Houla ! Ça en fait des paramètres tout ça. Si vous connaissez la SDL vous devriez au moins connaitre les 4 premiers. :p Faisons tout de même le point autour des paramètres :

  • flags : Option permettant notamment de stocker l'image dans la carte graphique. On lui donnera la valeur 0. Avec la SDL 1.2, nous aurions pu lui donner la valeur SDL_HWSURFACE, mais celle-ci n'est plus utilisée avec la version 2.0

  • width : Largeur de l'image. On lui donnera la valeur du champ w

  • height : Hauteur de l'image. On lui donnera la valeur du champ h

  • depth : Profondeur de l'image, on lui donnera la valeur du champ BitsPerPixel, qui n'est pas à confondre avec le champ BytesPerPixel attention !

  • Rmask, Gmask, Bmask, Amask : Paramètres un peu spéciaux qui concerne le masque des couleurs. On leur donnera les champs respectifs de la structure SDL_Surface.

Avec cette fonction, nous disposerons d'une nouvelle surface identique à la première, excepté le fait qu'elle ne contiendra encore aucun pixel.

Si on implémente cette fonction, ça nous donne :

SDL_Surface* Texture::inverserPixels(SDL_Surface *imageSource) const
{
    // Copie conforme de l'image source sans les pixels

    SDL_Surface *imageInversee = SDL_CreateRGBSurface(0, imageSource->w, imageSource->h, imageSource->format->BitsPerPixel, imageSource->format->Rmask,
                                                         imageSource->format->Gmask, imageSource->format->Bmask, imageSource->format->Amask);
}

Je coupe volontairement les paramètres pour ne pas rendre le code trop long. Mais vous pouvez tout coder sur une seule ligne si ça vous chante.

On a vu dans la partie théorique que l'objectif de cette méthode est de pouvoir copier chaque ligne d'une texture afin d'inverser leur position sur l'axe des ordonnées, ceci à l'aide d'une boucle. En code "grossier" ça va nous donner ça :

for(int i(0); i < hauteur; i++)
{
    for(int j(0); j < largeur; j++)
        imageInversee->pixels[hauteur - i][j] = imageSource->pixels[i][j];
}

Pas de panique je vous explique. :)

On remarque que grâce à cette boucle, chaque pixel présent sur la largeur de la texture (à l'indice j) sera copié exactement au même endroit dans l'image finale à l'indice j (vu qu'ils sont dans le bon ordre sur la largeur). Par exemple, un pixel présent en j = 10ième position dans l'image source sera copié au même endroit dans l'image finale en j = 10ième position.

En revanche, toute la ligne se retrouve inversée par rapport à sa position initiale : la ligne qui était présente en position i sur l'image source se retrouvera en position hauteur - i. Par exemple prenons une image 256x256, la ligne qui se trouvait en 5ième position se retrouvera en [256 - 5] = 251ième position, la ligne s'est retrouvé en haut de l'image.

Si nous pouvions écrire directement cette boucle dans notre code ça serait le rêve. Cependant il existe un gros problème : le champ pixels de la structure SDL_Surface n'est pas un tableau à deux dimensions, il est donc impossible d'utiliser les doubles cases [i][j]. :(

Néanmoins, il existe une façon de contrer cela dans les tableaux à une dimension. En effet :

  • Un pixel du type [i][j] peut être remplacé par un indice du type [(largeur * i) + j] et

  • Le pixel inverse de type [hauteur - i][j] peut être remplacé par [(largeur * (hauteur - i)) + j]

Par exemple, si on veut inverser un pixel situé à la position [5][10] sur l'image 256x256, on devra :

  • Récupérer l'indice source en position [(256 * 5) + j] = [1280 + 10] = [1290]

  • Que l'on copiera dans l'image finale en position [(256 * (256 - 5)) + 10] = [(256 * 251) + 10] = [64256] = [64266]

Oui oui, ces chiffres donnent le tournis mais on a bien inversé notre pixel dans le bon sens. :p

Avec cette méthode, on récupère bien notre pixel même si on est en présence d'un tableau à une dimension. :D Du coup, la boucle précédente devient :

for(int i(0); i < hauteur; i++)
{
    for(int j(0); j < largeur; j++)
        imageInversee->pixels[(largeur * i) + j] = imageSource->pixels[(largeur * (hauteur - i)) + j];
}

On est maintenant prêt pour coder notre méthode. :D Dernier petit détail cependant, je vous rappelle qu'en C++ il est malheureusement interdit d'utiliser un tableau contenu dans un pointeur (exemple : imageSource->pixels[56]), il va donc falloir passer par des tableaux intermédiaires pour manipuler les pixels :

SDL_Surface* Texture::inverserPixels(SDL_Surface *imageSource) const
{
    // Copie conforme de l'image source sans les pixels

    SDL_Surface *imageInversee = SDL_CreateRGBSurface(0, imageSource->w, imageSource->h, imageSource->format->BitsPerPixel, imageSource->format->Rmask,
                                                         imageSource->format->Gmask, imageSource->format->Bmask, imageSource->format->Amask);

    // Tableau intermédiaires permettant de manipuler les pixels

    unsigned char* pixelsSources = (unsigned char*) imageSource->pixels;
    unsigned char* pixelsInverses = (unsigned char*) imageInversee->pixels;
}

Pour plus de clarté, j'ai déclaré les tableaux sur deux lignes, vous pouvez très bien le faire sur une seule. ;)

Reprenons le grossier code de la boucle pour la convertir en code fonctionnel :

// Inversion des pixels

for(int i = 0; i < imageSource->h; i++)
{
    for(int j = 0; j < imageSource->w; j++)
        pixelsInverses[(imageSource->w * (imageSource->h - 1 - i)) + j] = pixelsSources[(imageSource->w * i) + j];
}

Remarquez le -1 dans l'indice de l'image inversée [(image->w * (imageSource->h - 1 - i)) + j], je vous rappelle que l'on travaille avec des tableaux donc les indices partent de 0 pour arriver à hauteur- 1. Je n'en parle que maintenant car je ne voulais pas surcharger la boucle précédente. Déjà que la notion n'est pas évidente à intégrer. :)

Nous avons pratiquement terminé notre boucle, il ne manque plus qu'une seule chose : jusqu'à maintenant, cette boucle permettait d'échanger des pixels entre eux, mais je vous rappelle qu'un pixel est composé de 3 couleurs. Pour le moment, nous n'échangeons qu'un tiers de la texture, et encore cet échange est complétement buggé !

Pour pallier à ce problème, il suffit juste de multiplier par 3 ou par 4 (pour la composante alpha) la boucle qui parcourt la ligne, car chaque ligne du tableau fait en réalité 3 ou 4 fois la longueur de l'image :

Il ne faut surtout pas multiplier la boucle parcourant la hauteur ! Sinon vous vous retrouvez à charger des zones mémoires qui ne vous appartiennent pas. Il n'y a que la boucle de la largeur qui doit être modifiée.

Dans notre code, il suffit de rajouter un "* imageSource->format->BytesPerPixel" partout où vous trouvez le champ imageSource->w (y compris dans la boucle), soit 3 fois normalement :

// Inversion des pixels

for(int i = 0; i < imageSource->h; i++)
{
    for(int j = 0; j < imageSource->w * imageSource->format->BytesPerPixel; j++)
        pixelsInverses[(imageSource->w * imageSource->format->BytesPerPixel * (imageSource->h - 1 - i)) + j] = pixelsSources[(imageSource->w * imageSource->format->BytesPerPixel * i) + j];
}

Avec cette boucle, nous pourrons inverser tous les pixels d'une image et ainsi les afficher à l'endroit dans nos scènes 3D. ;)

Pour terminer la méthode, il ne nous manque plus qu'à retourner la nouvelle surface SDL :

// Retour de l'image inversée

return imageInversee;

Ce qui nous donne au final :

SDL_Surface* Texture::inverserPixels(SDL_Surface *imageSource) const
{
    // Copie conforme de l'image source sans les pixels

    SDL_Surface *imageInversee = SDL_CreateRGBSurface(0, imageSource->w, imageSource->h, imageSource->format->BitsPerPixel, imageSource->format->Rmask,
                                                         imageSource->format->Gmask, imageSource->format->Bmask, imageSource->format->Amask);

    // Tableau intermédiaires permettant de manipuler les pixels

    unsigned char* pixelsSources = (unsigned char*) imageSource->pixels;
    unsigned char* pixelsInverses = (unsigned char*) imageInversee->pixels;

    // Inversion des pixels

    for(int i = 0; i < imageSource->h; i++)
    {
        for(int j = 0; j < imageSource->w * imageSource->format->BytesPerPixel; j++)
            pixelsInverses[(imageSource->w * imageSource->format->BytesPerPixel * (imageSource->h - 1 - i)) + j] = pixelsSources[(imageSource->w * imageSource->format->BytesPerPixel * i) + j];
    }

    // Retour de l'image inversée

    return imageInversee;
}
Adaptation dans la méthode charger

Maintenant que nous sommes capables d'inverser le sens d'une image, nous pouvons intégrer la nouvelle méthode dans le chargement de texture. Il nous suffit d'appeler la méthode inverserPixels() juste après avoir chargé la surface SDL. Bien évidemment, on pense à détruire l'ancienne surface qui ne sert plus rien :

bool Texture::charger()
{
    // Chargement de l'image dans une surface SDL

    SDL_Surface *imageSDL = IMG_Load(m_fichierImage.c_str());

    if(imageSDL == 0)
    {
        printf("IMG_Load: %s\n", IMG_GetError());
        return false;
    }

    // Inversion de l'image

    SDL_Surface *imageInversee = inverserPixels(imageSDL);
    SDL_FreeSurface(imageSDL);

    // ....
}

Il ne reste plus qu'à remplacer toutes les occurrences du pointeur imageSDL par le pointeur imageSDLInversee (Vous devriez trouver 9 occurrences à remplacer). Ce qui donne au final :

bool Texture::charger()
{
    // Chargement de l'image dans une surface SDL

    SDL_Surface *imageSDL = IMG_Load(m_fichierImage.c_str());

    if(imageSDL == 0)
    {
        std::cout << "Erreur : " << SDL_GetError() << std::endl;
        return false;
    }

    // Inversion de l'image

    SDL_Surface *imageInversee = inverserPixels(imageSDL);
    SDL_FreeSurface(imageSDL);

    // Génération de l'ID

    glGenTextures(1, &m_id);

    // Verrouillage

    glBindTexture(GL_TEXTURE_2D, m_id);

    // Format de l'image

    GLenum formatInterne(0);
    GLenum format(0);

    // Détermination du format et du format interne pour les images à 3 composantes

    if(imageInversee->format->BytesPerPixel == 3)
    {
        // Format interne

        formatInterne = GL_RGB;

        // Format

        if(imageInversee->format->Rmask == 0xff)
            format = GL_RGB;

        else
            format = GL_BGR;
    }

    // Détermination du format et du format interne pour les images à 4 composantes

    else if(imageInversee->format->BytesPerPixel == 4)
    {
        // Format interne

        formatInterne = GL_RGBA;

        // Format

        if(imageInversee->format->Rmask == 0xff)
            format = GL_RGBA;

        else
            format = GL_BGRA;
    }

    // Dans les autres cas, on arrête le chargement

    else
    {
        std::cout << "Erreur, format interne de l'image inconnu" << std::endl;
        SDL_FreeSurface(imageInversee);

        return false;
    }

    // Copie des pixels

    glTexImage2D(GL_TEXTURE_2D, 0, formatInterne, imageInversee->w, imageInversee->h, 0, format, GL_UNSIGNED_BYTE, imageInversee->pixels);

    // Application des filtres

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    // Déverrouillage

    glBindTexture(GL_TEXTURE_2D, 0);

    // Fin de la méthode

    SDL_FreeSurface(imageInversee);
    return true;
}

Et voila nous pouvons enfin charger des textures dans le bon ordre. :D Essayez avec la texture de la caisse de missiles, vous verrez qu'elle s'affiche désormais à l'endroit :

Les méthodes additionnelles

On va terminer tranquillement ce chapitre par quelques méthodes importantes qu'il faut implémenter, à savoir : le constructeur par défaut, celui de copie, le destructeur et la surcharge de l'opérateur =.

Ces méthodes sont importantes pour assurer le bon cycle de vie de la classe Texture. Le constructeur par défaut par exemple est utile lorsque vous souhaitez initialiser tout un tableau de textures, l'opérateur = quant à lui sert à copier une texture facilement en une ligne de code, etc ...

Le constructeur par défaut et le destructeur

On va commencer tout de suite par implémenter le constructeur par défaut et le destructeur.

Je vous rappelle que le constructeur par défaut est appelé lorsque vous ne donnez aucun paramètre à votre objet. Son prototype est le suivant :

Texture();

Son rôle ne lui permet que d'initialiser les deux attributs de la classe. Il va affecter l'ID 0 à l'attribut m_id et une chaine vide à m_fichierImage :

Texture::Texture() : m_id(0), m_fichierImage("")
{

}

Occupons maintenant du destructeur. Cette "méthode" est appelée à chaque fois qu'un objet est détruit, c'est notamment ici que nous devrons détruire nos textures. Car oui, même les objets OpenGL doivent être détruits pour libérer la mémoire qu'ils occupent.

Les objets OpenGL sont alloués dynamiquement dans la carte graphique par la fonction glGenTextures(), il va nous falloir libérer l'espace occupé pour éviter les fuites de mémoire. Pour ça, OpenGL nous fournit une fonction qui reprend exactement les mêmes paramètres que glGenTextures(). Voici son prototype :

void glDeleteTextures(GLsizei number, const GLuint *textures);

  • number : Le nombre d'ID à initialiser. Comme pour la génération, nous lui donnerons la valeur 1

  • textures : Un tableau de type GLuint ou une adresse d'ID. Nous lui donnerons l'adresse de l'ID à détruire

Nous utiliserons cette fonction dans notre destructeur dont j'espère que vous connaissez son utilité. :p

Il est appelé au moment de la destruction d'un objet pour permettre au développeur de libérer toute la mémoire qu'il avait prise. D'ailleurs, nous avions déjà déclaré cette "méthode" précédemment, il ne nous reste donc plus qu'à utiliser la fonction glDeleteTextures() à l'intérieur :

Texture::~Texture()
{
    // Destruction de la texture

    glDeleteTextures(1, &m_id);
}

Et voila, nos textures seront maintenant détruites proprement lorsqu'elles ne seront plus utilisées. :)

Copier une texture

On passe maintenant à la copie de textures. Mais avant ça, je vais d'abord vous faire une petite parenthèse sur le chargement des textures dans un vrai jeux-vidéo.

Dans un jeu-vidéo lambda, le moteur 3D doit être capable de charger toutes les textures dont il a besoin dans un espèce de gros tableau qui doit être accessible par toutes les classes :

A partir de là, chaque modèle 3D (comme un personnage ou un arbre) va récupérer un pointeur sur la texture dont il aura besoin, ce n'est donc pas lui qui va charger sa propre texture. Et de plus, si vous avez une dizaine d'ennemis qui doit s'afficher à l'écran, vous n'allez pas charger 10 fois les mêmes textures pour les 10 ennemis. En théorie, chaque personnage devrait récupérer un pointeur sur la texture dont il a besoin dans le gros tableau pour l'utiliser. De cette façon, les textures ne sont chargées qu'une seule fois et ça économise pas mal de ressources.

Je vous parle de ça maintenant car c'est ce que nous "devrions faire" en réalité. Cependant nos scènes étant encore assez simples nous pouvons nous permettre de charger une texture plusieurs fois sans se préoccuper de créer le gros tableau. :)

Bien, ceci étant dit on va pouvoir retourner à nos méthodes de copie.

Il existe grosso-modo deux façons de copier une texture : soit on utilise le constructeur de copie, soit on surcharge l'opérateur =. La différence entre les deux est que si vous initialisez une texture à partir d'une autre texture, alors ce sera le constructeur de copie qui sera appelé. En revanche, si vous le faites après sa déclaration se sera l'opérateur = qui sera sollicité.

On va commencer l'implémentation de ces deux méthodes par le constructeur de copie dont voici le prototype :

Texture(Texture const &textureACopier);

Vu que l'on commence à bien manipuler les objets, nous utiliserons de plus en plus les références dans nos programmes. ;)

Le constructeur prend en paramètre une référence constante sur l'objet à copier, dans notre cas une texture.

Petit point important : si nous avons besoin de coder nous-même ce constructeur c'est parce qu'il y a, en général, un problème au niveau des pointeurs lors de la copie d'un objet. En effet, si on dispose de deux copies d'une même texture alors elles partagerons le même ID. Or, si on détruit l'une des deux textures, la deuxième se retrouvera avec un ID invalide et donc une erreur d'affichage.

Alors bon, il est vrai que dans notre classe l'ID n'est qu'une simple variable. Mais pour votre carte graphique cet ID représente un pointeur pointant sur la texture chargée.

Par conséquent, pour copier proprement deux textures il va falloir créer un nouvel ID indépendant du premier. De cette façon, même si un objet est détruit, la copie ne sera pas affectée.

Dans la classe Texture, nous devrons donc :

  • Copier l'attribut m_fichierImage qui contient le chemin vers le fichier image

  • Recharger la texture pour avoir un nouvel ID indépendant du premier

Le constructeur de copie ressemblera donc à ceci :

Texture::Texture(Texture const &textureACopier)
{
    // Copie de la texture

    m_fichierImage = textureACopier.m_fichierImage;
    charger();
}

Rien de plus facile isn't it ? :lol:

D'ailleurs on va s'occuper maintenant de coder la surcharge de l'opérateur = car le code ne change pas vraiment. Le prototype de cette surcharge sera le suivant :

Texture& operator=(Texture const &textureACopier);

La méthode retournera bien une référence sur un objet de type Texture. Cette référence concerna d'ailleurs l'objet lui-même (pointeur this).

Pour cette méthode, on copie simplement le même code que le constructeur de copie puis on renvoie le pointeur *this :

Texture& Texture::operator=(Texture const &textureACopier)
{
    // Copie de la texture

    m_fichierImage = textureACopier.m_fichierImage;
    charger();

    // Retour du pointeur *this

    return *this;
}

Il manque encore un tout petit détail à cette méthode - un détail qui s'étend même au chargement en général. Imaginez qu'une texture soit déjà chargée et que l'on copie une autre texture dans celle-ci, que se passerait-il ?

Et bien le premier ID qui a été initialisé sera perdu, et du coup la texture qui a été chargée en mémoire sera elle aussi perdue. C'est encore une fuite. Pour régler ce petit problème, il faut simplement détruire l'ancien ID de la même façon qu'avec le destructeur. Dans le pire des cas, même si la texture n'était pas chargée avant, ce sera l'ID 0 qu'OpenGL essaiera de détruire. Mais vu que c'est impossible il passera à autre chose sans nous renvoyer d'erreur.

Si j'ai dit que ce problème s'étendait aussi au chargement de texture en général, c'est parce que nous avons exactement le même problème lorsque nous utilisons la méthode charger(). En effet, si nous utilisons cette méthode deux fois sur le même objet, alors le premier chargement qui a été fait sera perdu en mémoire.

Pour régler ce problème, nous allons rajouter un petit bout de code dans la méthode charger() juste avant de générer l'identifiant. Ce code sera constitué d'une condition if qui permettra de savoir si une texture a déjà été chargée. Si oui, alors il faudra la détruire avant de la recharger.

La fonction qui permet de savoir ceci s'appelle glIsTexture() :

GLboolean glIsTexture(GLuint texture);

Elle ne prend en paramètre que l'identifiant de la texture à vérifier. Elle renvoie la valeur GL_TRUE si elle a déjà été chargée et GL_FALSE dans le cas contraire.

Nous devons donc appeler cette fonction avant de générer l'ID. Si elle renvoie GL_TRUE alors il faudra détruire la texture à l'aide de glDeleteTextures() :

bool Texture::charger()
{
    // Chargement de l'image dans une surface SDL

    SDL_Surface *imageSDL = IMG_Load(m_fichierImage.c_str());

    if(imageSDL == 0)
    {
        std::cout << "Erreur : " << SDL_GetError() << std::endl;
        return false;
    }

    // Inversion de l'image

    SDL_Surface *imageInversee = inverserPixels(imageSDL);
    SDL_FreeSurface(imageSDL);

    // Destruction d'une éventuelle ancienne texture

    if(glIsTexture(m_id) == GL_TRUE)
        glDeleteTextures(1, &m_id);

    .....

Et voila, nos méthodes de copie sont maintenant complètes et prêtes à l'emploi. :D

Aller plus loin

Répéter une texture

Fonctionnement

Maintenant que l'on peut afficher une texture dans le bon sens, je vais vous montrer la technique utilisée pour répéter une texture plusieurs fois. Si vous avez déjà joué à un jeu-vidéo, vous avez peut-être remarqué que les textures au sol se répétait en boucle.

On peut prendre l'exemple d'un sol composé d'herbe :

Si vous vous concentrez sur cette image, vous remarquerez qu'elle est composée d'une seule petite texture qui se répète en boucle.

Si un jeu-vidéo devait afficher ce sol et qu'il s'amuserait à charger la même texture pour chaque carré d'herbe, alors il deviendrait totalement injouable et serait beaucoup trop lent. A la place, il se contentera de la charger une seule fois puis il utilisera des coordonnées de texture 'spéciales' pour la répéter :

Cette technique est utilisée par tous les jeux-vidéo. Sans elle, il n'y en aurait pas beaucoup d'ailleurs car ils seraient tous terriblement lents à l'affichage. :p

Bon en réalité les coordonnées utilisées n'ont rien de spéciales, elles ne sont juste pas comprises entre 0 et 1.

Hein ? Mais tu as dit que les coordonnées de texture devaient toujours être comprises entre 0 et 1 ?

Oui et c'est totalement vrai !

Si vous allez au-delà de 1 (2 par exemple) ou en dessous de 0 alors vous répéterez la même texture :

Les coordonnées sont toujours comprises entre 0 et 1 mais au lieu de renvoyer une erreur, OpenGL recopiera la texture. Bien évidemment, plus vous fournissez des valeurs importantes, plus votre texture sera répétée :

Ce n'est absolument pas un bug puisque c'est prévu par OpenGL et c'est même LA technique à utiliser pour répéter vos textures que ce soit sur les sols, les murs, ... Nous ferons comme ça dans le futur pour afficher de grands espaces. :)

Le seul point à prendre en compte c'est qu'il faut que votre texture soit prévue pour la répétition, si elle ne l'est pas vous aurez un rendu assez moche et pas continu. En général, ce point concerne surtout le graphiste et non le développeur (une bonne raison de lui râler dessus si ça ne va pas :p )

Vous pouvez essayer de voir ce que ça donne avec la texture que l'on utilise dans ce chapitre. Remplacez les coordonnées par celles-ci pour l'affiche 12 fois !

// Coordonnées de texture

float coordTexture[] = {0, 0,   4, 0,   4, 4,     // Triangle 1
                        0, 0,   0, 4,   4, 4};    // Triangle 2
Exercice

La répétition de texture est une occasion parfaite pour moi de vous proposer un petit exercice à faire.

En reprenant ce qu'on vient de voir, essayez de recréer le sol constitué d'herbe que je vous ai montré au-dessus. Les consignes sont les suivantes :

  • Vous ne devez utiliser que 4 vertices (fournis dans le schéma ci-dessous)

  • Répéter la texture 49 fois (7 fois en largeur et en hauteur)

Vous pouvez télécharger l'image à afficher un peu plus haut ou si vous avez téléchargé l’archive, vous la retrouverez sous le nom de "veg005.jpg".

Voici le schéma explicatif :

Et voici ce que vous devriez obtenir (en plaçant votre caméra un peu différemment pour mieux voir) :

Bonne chance. ;) (et n’oubliez pas de verrouiller votre texture !)

Solution

La solution de cette exercice est assez simple, nous n'avons que quelques lignes de code à modifier par rapport à l'exemple que l'on a vu.

En premier, il fallait définir les vertices. Nous faisons des carrés depuis un moment maintenant il n'aurait pas dû y avoir de problèmes :

// Vertices

float vertices[] = {-10, 0, -10,   10, 0, -10,   10, 0, 10,    // Triangle 1
                    -10, 0, -10,   -10, 0, 10,   10, 0, 10};   // Triangle 2

N'oubliez pas qu'il fallait afficher un sol, les vertices étaient donc tous à la hauteur 0.

Ensuite, il fallait définir les coordonnées de texture. Nous avons vu que si on allait plus que 1 dans leurs valeurs alors on attaquait un autre affichage de la même texture. Il fallait l'afficher 7 fois en largeur et en hauteur, donc les coordonnées maximales à utiliser étaient respectivement [7 ; 0] et [0 ; 7].

On peut représenter la situation avec le schéma suivant :

Le tableau coordTexture à utiliser était donc le suivant :

// Coordonnées de texture

float coordTexture[] = {0, 0,   7, 0,   7, 7,     // Triangle 1
                        0, 0,   0, 7,   7, 7};    // Triangle 2

Une fois les données définies, il ne manquait plus qu'à déclarer un objet Texture qui permettait de charger l'image en mémoire :

// Vertices et coordonnées de texture

....

// Texture

Texture texture("Textures/Herbe.jpg");
texture.charger();

Au niveau du code d'affichage, il fallait juste reprendre celui que l'on utilisait précédemment en faisant attention de bien verrouiller la texture au moment d'appeler la fonction glDrawArrays(). Le reste du code permettait, entre autres, d'envoyer les vertices, les coordonnées de texture et les matrices à OpenGL :

// Activation du shader

glUseProgram(shaderTexture.getProgramID());

    // Envoi des vertices

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertices);
    glEnableVertexAttribArray(0);

    // Envoi des coordonnées de texture

    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, coordTexture);
    glEnableVertexAttribArray(2);

    // Envoi des matrices

    glUniformMatrix4fv(glGetUniformLocation(shaderTexture.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));
    glUniformMatrix4fv(glGetUniformLocation(shaderTexture.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));

    // Verrouillage de la texture

    glBindTexture(GL_TEXTURE_2D, texture.getID());

    // Rendu

    glDrawArrays(GL_TRIANGLES, 0, 6);

    // Déverrouillage de la texture

    glBindTexture(GL_TEXTURE_2D, 0);

    // Désactivation des tableaux

    glDisableVertexAttribArray(2);
    glDisableVertexAttribArray(0);

// Désactivation du shader

glUseProgram(0);

Afficher une caisse

Pour clore ce chapitre d'une façon intéressante, nous allons appliquer ce que l'on a vu sur les textures non plus sur une forme simple 2D mais bien sur un modèle 3D comme un cube. Vu que nous avons utilisé une texture de caisse jusqu'ici, nous pouvons légitimement l'utiliser pour transformer notre cube coloré à l'ancienne en super caisse de missiles. :D

Notre objectif final est d'être capables d'avoir ce rendu :

Pour arriver à cela, nous allons coder une classe Caisse qui contiendra tout ce qu'il faut pour afficher un cube et le texturer. Pour nous faciliter la tâche, nous la ferons hériter de la classe Cube afin de ne pas avoir à refaire l'initialisation des vertices et du shader. ;)

L'héritage de la classe Cube

Avant de se lancer dans la classe Caisse, nous allons devoir régler un petit problème d'héritage au niveau du cube qui nous empêche de profiter de ses attributs. En effet, si on regarde son header, on remarque que ces derniers sont tous privés à cause de l'utilisation du mot-clef private :

#ifndef DEF_CUBE
#define DEF_CUBE

// Includes

....

// Classe Cube

class Cube
{
    public:

    Cube(float taille, std::string const vertexShader, std::string const fragmentShader);
    ~Cube();

    private:

    Shader m_shader;
    float m_vertices[108];
    float m_couleurs[108];
};

#endif

Pour prétendre à un héritage, il faut changer ce mot-clef en le remplaçant par son confrère protected :

#ifndef DEF_CUBE
#define DEF_CUBE

// Includes

....

// Classe Cube

class Cube
{
    public:

    Cube(float taille, std::string const vertexShader, std::string const fragmentShader);
    ~Cube();

    protected:

    Shader m_shader;
    float m_vertices[108];
    float m_couleurs[108];
};

#endif

De cette façon, nous pouvons utiliser les trois attributs présents ici dans les classes filles.

Le gros avantage pour nous, c'est que le shader et surtout les vertices seront déjà initialisés sans même que nous ayons à faire quoi que ce soit. Il ne nous restera plus qu'à gérer les nouveaux attributs. Entre parenthèses, le tableau de couleurs ne sera pas utile pour la suite mais mieux vaut le garder sous la main au cas où.

Le header de la classe Caisse

Comme toute implémentation de classe, nous devons commencer par le header. Celui de la classe Caisse sera assez similaire à celui du Cube puisqu'il comportera les mêmes méthodes ainsi qu'un tableau à initialiser dans le constructeur.

Le début du fichier, que nous appellerons Caisse.h, contiendra la définition de la classe accompagnée du code ":publicCube" qui lui permettra de profiter de l'héritage :

#ifndef DEF_CAISSE
#define DEF_CAISSE

// Includes

#include "Cube.h"

// Classe Caisse

class Caisse : public Cube
{
    public:

    private:
};

#endif

Pour agrémenter un peu ce code, nous allons ajouter les attributs nécessaires à la réalisation de notre caisse. Sachant que les vertices et le shader sont déjà fournis, nous n'en avons donc besoin que de deux supplémentaires :

  • Un objet Texture qui représentera l'image à plaquer

  • Un tableau de float qui représentera les coordonnées à associer aux vertices

Petit précision pour le tableau, celui-ci contiendra 72 cases car il faut associer 2 coordonnées à chaque vertex, ce qui donne 36 vertices x 2 coordonnées = 72 cases.

// Attributs

Texture m_texture;
float m_coordTexture[72];

Passons maintenant aux méthodes dont nous aurons besoin. Hormis celle nécessaire pour afficher le rendu final, nous n'aurons besoin que du constructeur et du destructeur.

Le premier reprendra les mêmes paramètres que celui de la classe Cube afin de pouvoir lui donner les valeurs qu'il attend au moment de l'initialisation. Ces paramètres représentaient la taille désirée ainsi que les codes sources du shader à utiliser.

Nous en rajouterons un dernier, spécifique au constructeur Caisse(), qui représentera l'image à plaquer sur les faces de la caisse et qui permettra surtout d'initialiser l'attribut m_texture.

Le prototype du constructeur au final est le suivant :

Caisse(float taille, std::string const vertexShader, std::string const fragmentShader, std::string const texture);

On en profite au passage pour déclarer le destructeur (qui est quand même plus simple à faire :p ) :

~Caisse();

Si on réunit tout ça dans le header, on devrait avoir :

#ifndef DEF_CAISSE
#define DEF_CAISSE

// Includes

#include "Cube.h"
#include "Texture.h"
#include <string>

// Classe Caisse

class Caisse : public Cube
{
    public:

    Caisse(float taille, std::string const vertexShader, std::string const fragmentShader, std::string const texture);
    ~Caisse();

    private:

    Texture m_texture;
    float m_coordTexture[72];
};

#endif

Pensez à inclure les en-têtes Texture.h et string sinon votre compilateur risque de ne pas être d'accord avec vous.

Le constructeur

Le constructeur va être un poil différent de ceux que nous avons l'habitude d'implémenter. En effet, avant d’initialiser n'importe quel attribut, nous devons faire appel au constructeur parent de la classe Caisse comme nous le demande le C++.

Nous l'appelons donc juste après les deux points ":" en lui passant les trois premiers paramètres reçus (la taille et les codes sources shader) :

Caisse::Caisse(float taille, std::string const vertexShader, std::string const fragmentShader, std::string const texture) : Cube(taille, vertexShader, fragmentShader)
{

}

Maintenant que la classe-mère est prête, nous pouvons nous occuper de nos attributs. Le seul que l'on peut initialiser après les deux points est la texture m_texture, l'autre étant un tableau il doit être rempli entre les accolades.

Nous donnerons à m_texture la string reçue en paramètre pour lui spécifier l'image à charger. Nous appellerons également sa méthode charger() sinon elle risque d'être un peu vide dans notre rendu. :p

Caisse::Caisse(float taille, std::string const vertexShader, std::string const fragmentShader, std::string const texture) : Cube(taille, vertexShader, fragmentShader),
                                                                                m_texture(texture)
{
    // Chargement de la texture

    m_texture.charger();
}

Pour initialiser le second attribut, m_coordTexture, nous allons reprendre le principe que nous avons utilisé lors de la création du tableau de couleurs. C'est-à-dire que nous devrons faire correspondre un couple de coordonnées de texture à chaque vertex comme nous l'avons fait précédemment.

Pour la première face de la caisse par exemple, nous pouvons reprendre celles que nous avons utilisées pour le carré :

// Coordonnées de texture

float coordTextureTmp[] = {0, 0,   1, 0,   1, 1,     // Face 1
                           0, 0,   0, 1,   1, 1};    // Face 1

Nous ré-utiliserons aussi le principe des tableaux temporaires dont le contenu sera copié dans l'attribut final.

L'avantage d'un cube, c'est que toutes ses faces ne sont en fait que des carrés. Du coup, nous pouvons reprendre les coordonnées de texture de la première face pour les assigner à toutes les autres. Nous n'avons pas besoin de les déterminer manuellement. Le tableau temporaire devient donc :

// Coordonnées de texture temporaires

float coordTextureTmp[] = {0, 0,   1, 0,   1, 1,     // Face 1
                           0, 0,   0, 1,   1, 1,     // Face 1

                           0, 0,   1, 0,   1, 1,     // Face 2
                           0, 0,   0, 1,   1, 1,     // Face 2

                           0, 0,   1, 0,   1, 1,     // Face 3
                           0, 0,   0, 1,   1, 1,     // Face 3

                           0, 0,   1, 0,   1, 1,     // Face 4
                           0, 0,   0, 1,   1, 1,     // Face 4

                           0, 0,   1, 0,   1, 1,     // Face 5
                           0, 0,   0, 1,   1, 1,     // Face 5

                           0, 0,   1, 0,   1, 1,     // Face 6
                           0, 0,   0, 1,   1, 1};    // Face 6

On a l'impression de travailler avec des couleurs tellement que ça y ressemble. ;)

Une fois les coordonnées définies, il ne manque plus qu'à les copier dans l'attribut m_coordTexture à l'aide d'une boucle for :

// Copie des valeurs dans le tableau final

for(int i (0); i < 72; i++)
    m_coordTexture[i] = coordTextureTmp[i];

Si on résume tout ça :

Caisse::Caisse(float taille, std::string const vertexShader, std::string const fragmentShader, std::string const texture) : Cube(taille, vertexShader, fragmentShader),
                                                                                m_texture(texture)
{
    // Chargement de la texture

    m_texture.charger();

    // Coordonnées de texture temporaires

    float coordTextureTmp[] = {0, 0,   1, 0,   1, 1,     // Face 1
                               0, 0,   0, 1,   1, 1,     // Face 1

                               0, 0,   1, 0,   1, 1,     // Face 2
                               0, 0,   0, 1,   1, 1,     // Face 2

                               0, 0,   1, 0,   1, 1,     // Face 3
                               0, 0,   0, 1,   1, 1,     // Face 3

                               0, 0,   1, 0,   1, 1,     // Face 4
                               0, 0,   0, 1,   1, 1,     // Face 4

                               0, 0,   1, 0,   1, 1,     // Face 5
                               0, 0,   0, 1,   1, 1,     // Face 5

                               0, 0,   1, 0,   1, 1,     // Face 6
                               0, 0,   0, 1,   1, 1};    // Face 6

    // Copie des valeurs dans le tableau final

    for(int i (0); i < 72; i++)
        m_coordTexture[i] = coordTextureTmp[i];
}

Le constructeur initialise maintenant tous nos attributs, même ceux de sa classe-mère puisqu'il fait appel aussi à son constructeur.

Le destructeur

On passe rapidement sur le destructeur qui restera vide au même titre que celui du cube :

Caisse::~Caisse()
{

}
La méthode afficher()

La méthode afficher() va faire exactement la même chose que sa méthode "parente" à savoir afficher notre caisse à l'écran. Elle aura besoin des matrices projection et modelview en paramètres afin de pouvoir les envoyer à son shader :

void afficher(glm::mat4 &projection, glm::mat4 &modelview);

Petit détail : Nous faisons ici ce qu'on appelle le masquage de méthode, c'est une notion dont M@téo parle dans son tuto sur le C++. ;)

Son implémentation ne va pas nous poser de problème puisqu'il suffit de recopier le code que nous avons utilisé pour afficher le carré. Nous avons juste une petite modification à faire au niveau de la fonction glDrawArrays(). Celle-ci ne prendra pas en compte 6 mais 36 vertices, soit le nombre requis pour un cube comme nous avons eu l'occasion de le voir.

Il faut également changer le nom des variables utilisées comme :

  • Le shader qui devient m_shader

  • Les tableaux qui deviennent m_vertices et m_coordTexture

  • La texture qui devient m_texture

Le code de base à recopier est le suivant :

// Activation du shader

glUseProgram(shaderTexture.getProgramID());

    // Envoi des vertices

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertices);
    glEnableVertexAttribArray(0);

    // Envoi des coordonnées de texture

    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, coordTexture);
    glEnableVertexAttribArray(2);

    // Envoi des matrices

    glUniformMatrix4fv(glGetUniformLocation(shaderTexture.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));
    glUniformMatrix4fv(glGetUniformLocation(shaderTexture.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));

    // Verrouillage de la texture

    glBindTexture(GL_TEXTURE_2D, m_texture.getID());

    // Rendu

    glDrawArrays(GL_TRIANGLES, 0, 36);

    // Déverrouillage de la texture

    glBindTexture(GL_TEXTURE_2D, 0);

    // Désactivation des tableaux

    glDisableVertexAttribArray(2);
    glDisableVertexAttribArray(0);

// Désactivation du shader

glUseProgram(0);

Une fois remanié et intégré dans la méthode afficher(), il ressemble à ceci :

void Caisse::afficher(glm::mat4 &projection, glm::mat4 &modelview)
{
    // Activation du shader

    glUseProgram(m_shader.getProgramID());

        // Envoi des vertices

        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, m_vertices);
        glEnableVertexAttribArray(0);

        // Envoi des coordonnées de texture

        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, m_coordTexture);
        glEnableVertexAttribArray(2);

        // Envoi des matrices

        glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));
        glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));

        // Verrouillage de la texture

        glBindTexture(GL_TEXTURE_2D, m_texture.getID());

        // Rendu

        glDrawArrays(GL_TRIANGLES, 0, 36);

        // Déverrouillage de la texture

        glBindTexture(GL_TEXTURE_2D, 0);

        // Désactivation des tableaux

        glDisableVertexAttribArray(2);
        glDisableVertexAttribArray(0);

    // Désactivation du shader

    glUseProgram(0);
}

La classe Caisse est maintenant terminée. Nous n'avons plus qu'à la tester.

Afficher une caisse

Le plus dur est derrière nous maintenant, on peut facilement afficher un cube texturé en seulement 2 lignes de code. :p

Nous devons premièrement déclarer un objet Caisse dans la méthode bouclePrincipale(), pensez à inclure l'en-tête "Caisse.h". Nous lui donnerons en paramètre une taille de 2.0, le chemin vers les fichiers texture.vert et texture.frag ainsi vers la texture Caisse.jpg :

// Objet Caisse

Caisse caisse(2.0, "Shaders/texture.vert", "Shaders/texture.frag", "Textures/Caisse.jpg");

Ensuite, nous appelons sa méthode afficher() dans la boucle while en n'oubliant pas de lui donner les matrices projection et modelview :

// Boucle principale

while(!terminer)
{
    // Gestion des évènements, nettoyage de l'écran, ...

    ....

    // Affichage de la caisse

    caisse.afficher(projection, modelview);

    // Actualisation de la fenêtre

    ....
}

Si vous compilez votre projet, vous devriez avoir :

Vu que nous avons superbement bien coder notre classe ( ;) ), nous pouvons changer l'aspect de notre caisse en ne modifiant qu'un seul paramètre lors de sa création. Par exemple, si vous voulez avoir une caisse avec la texture suivante (crate12.jpg dans l'archive) :

Il vous suffit juste de copier cette image dans votre dossier "Textures" puis de l'utiliser dans votre déclaration d'objet :

// Objet Caisse

Caisse caisse(2.0, "Shaders/texture.vert", "Shaders/texture.frag", "Textures/Caisse2.jpg");

Vous pouvez même combiner la répétition de texture et notre caisse pour voir ce que ça donne. Vous devrez cependant faire translater votre cube pour ne pas qu'il transperce l'herbe. La valeur de la translation dépend de la taille de votre cube, celle-ci doit en faire la moitié. Si vous avez donné une taille de 2.0 unités alors la valeur sera de 1.0 :

// Sauvegarde de la matrice modelview

mat4 sauvegardeModelview = modelview;

    // Translation pour positionner le cube

    modelview = translate(modelview, vec3(0, 1, 0));

    // Affichage du cube

    caisse.afficher(projection, modelview);

// Restauration de la matrice

modelview = sauvegardeModelview;

Télécharger : Code Source C++ du chapitre 10

Pfiou il était long ce chapitre. Je me demande encore si je n'aurais pas dû le couper en deux. :lol:

Au moins ça nous a permis d'en apprendre pas mal sur les textures car nous savons maintenant charger des images depuis le disque dur et les afficher dans nos scènes 3D. Nous allons abandonner petit à petit les couleurs au profit de ces nouvelles textures, cela nous permettra d'avoir un monde plus réaliste.

Nous avons également vu dans ce chapitre comment manipuler des objets OpenGL. Je vous ai mis un commentaire en vert en dessous de chaque fonction que nous reverrons plus tard dans le tuto. Vous verrez que ces objets se manipulent tous de la même façon. ;)


La caméra

J'ai une bien mauvaise nouvelle très chers compagnons, nous arrivons à la fin de la première partie de ce tutoriel. :(
(Super façon d'introduire un chapitre :-° )

En effet, nous avons déjà vu presque toutes les notions essentielles d'OpenGL. Nous maitrisons l'affichage de formes simples, l'utilisation basique de Shaders, l'affichage de textures, la gestion des évènements, ... Il ne reste plus qu'une seule chose à voir pour compléter ces essentielles : une caméra déplaçcable.

Nous avons déjà eu l'occasion d'approcher la caméra, notamment dans le chapitre sur la 3D, mais jusqu'à maintenant on se contentait de la positionner au début du programme et du coup elle restait immobile. Notre objectif, avec ce chapitre, va être de lui donner la capacité de se déplacer. Ainsi nous pourrons nous balader librement dans nos scènes 3D.

Après ce chapitre, nous passerons à un TP qui rassemblera toutes les notions ce que nous avons vu. Nous aurons un début de monde 3D réaliste. Mais bon pour le moment, nous devons encore étudier la caméra.

Commençons ! :magicien:

Fonctionnement

Une caméra déplaçable

Les types de caméra

On a déjà eu l’occasion d'approcher le système caméra avec OpenGL qui permet d'avoir une représentation d'un monde virtuel sur notre écran. Ce système fait exactement la même chose qu'un tournage de film, nous ne sommes pas présents sur un plateau et pourtant nous voyons les scènes qui y sont tournées.

Le problème de notre caméra actuelle c'est qu'elle est totalement immobile, nous n'avons aucun moyen pour la faire bouger. L'objectif de ce chapitre va donc être de lui ajouter de la mobilité afin que nous puissions nous déplacer dans notre monde virtuel. Nous utiliserons une méthode de déplacement particulière basée sur le vol en mode libre (FreeFly en anglais). Ce mode permet à la caméra de voler, tel un avion, de façon totalement libre. On pourra donc aller à gauche, à droite, en haut et en bas comme on voudra. :p

On retrouve ce type de caméra dans les jeux-vidéo où on pilote un hélicoptère, un avion, ...

Image issue du jeu GTA San Andreas

Cette caméra représentera une base avec laquelle nous pourrons travailler. Dans l'avenir, il suffira de quelques modifications pour la faire passer en vue à la 1ière ou à la 3ième personne. Il existe aussi d'autres types caméras en vue de dessus que l'on remarque dans les jeux de stratégie par exemple. Mais pour le moment, nous nous contenterons de développer une caméra libre que nous utiliserons dans les futurs chapitres. :)

Les contrôles

Pour pouvoir se déplacer dans notre monde 3D, nous devrons utiliser le clavier et la souris. Grâce à la classe Input (que l'on a déjà codée) nous allons pouvoir lier un évènement à un déplacement, ces évènements seront divisés en deux catégories. Nous utiliserons :

  • La souris : pour orienter la caméra en fonction du mouvement effectué

  • Le clavier : pour déplacer la caméra selon 4 axes (gauche, droite, haut et bas)

Ces deux catégories d'évènements vont se gérer de manière totalement différente, nous aurons besoin de deux méthodes distinctes. Et croyez-moi, elles ne feront pas du tout la même chose. :p

La gestion de l'orientation

On vient de voir le principe d'une caméra libre et je vous ai même précisé la façon dont on va la gérer. Je vais maintenant aller plus loin dans la théorie afin que vous puissiez comprendre ce que nous allons faire par la suite. Ouvrez grand les oreilles et n'hésitez pas à relire plusieurs fois ce début de chapitre. Il est un peu technique mais il est absolument fondamental que vous compreniez les notions que l'on va aborder. D’ailleurs, on en ré-utilisera certaines dans de futurs chapitres.

La première chose que l'on va voir concerne l'orientation de la caméra. Les évènements déclenchés par la souris permettront de l'orienter en fonction du mouvement effectué. Lorsque nous bougerons la souris vers le haut ou vers le bas, la caméra s'orientera de la même façon :

Et lorsque nous bougerons la souris vers la gauche ou vers la droite, la caméra s'orientera également de la même façon :

Bien entendu, on peut combiner ces deux mouvements pour que la caméra s'oriente dans n'importe quelle direction. Voici un schéma résumant toutes les informations dont nous avons besoin sur l'orientation :

Les angles Phi et Theta représentent les éléments mathématiques indispensables qui vont nous permettre de calculer l'orientation de la caméra. Le premier représente l'orientation sur l'axe horizontal, le second l'orientation sur l'axe vertical. Ces deux angles sont absolument primordiaux. Sans eux, on pourrait toujours se déplacer vers la gauche ou la droite, ... mais notre regard resterait totalement fixe. Imaginez-vous en train de marcher dans la rue sans pouvoir tourner la tête (un peu embêtant n'est-ce pas ?).

Les angles ne sont pas les seuls éléments important sur ce schéma, le vecteur V (orientation) est tout aussi important. Il représente la direction de notre regard (ce qui sera affiché sur l'écran).

Mais avec quoi va-t-on pouvoir calculer ce vecteur orientation ? Je sais pas le calculer moi. :(

Rassurez-vous, je ne vais pas vous demander de trouver une formule par vous-même. :p

En fait, il existe une formule toute prête pour calculer ce vecteur, enfin plus précisément pour calculer ses coordonnées (x, y, z). Cette formule permet de trouver ce que l'on appelle les coordonnées sphériques :

Ah tiens ! On retrouve les angles de tout à l'heure !

Et oui ah ah ! Je vous avais dit que ces angles étaient super importants. :p Et encore, on peut simplifier cette fonction en enlevant le paramètre R. Ce paramètre correspond à la longueur du rayon de la sphère précédente. Mais vu que nous travaillerons avec des vecteurs normalisés, cette longueur sera toujours égale à 1, ce qui donne au final :

Grâce aux angles Theta et Phi, nous pourrons calculer le vecteur orientation les doigts dans le nez. :-°

La formule des coordonnées sphériques n'est pas compliquée à démontrer. Si vous êtes curieux, vous pouvez voir comment la démontrer grâce à l'annexe sur la trigonométrie de Kayl. Attention cependant, Kayl utilise la coordonnée Z comme repère vertical et non l'axe Y. Les résultats sont donc inversés entre les coordonnées. Mais au moins, grâce à cette annexe vous pourrez constater que je n'ai pas sorti la formule de mon chapeau. :p

Et ces deux angles, comment va-t-on les calculer ?

Il n'y aura pas besoin des les calculer. Au niveau de la programmation, ces angles correspondront au mouvement de la souris. L'angle Phi représentera l'axe Y et Théta l'axe X.

En définitif, grâce à ces deux angles et aux coordonnées sphériques nous pourrons gérer l'orientation de notre caméra très facilement.

La gestion du déplacement

Avancer et reculer

La gestion du déplacement de la caméra va être différente de celle de l'orientation. Nous n'aurons ni besoin d'angles, ni de coordonnées sphériques, mais uniquement de vecteurs. On va diviser cette gestion en deux parties :

  • Le déplacement 'vertical' (à défaut de trouver meilleur terme) pour avancer et reculer

  • Le déplacement latéral pour aller vers la gauche ou la droite

On commence par la gestion du déplacement vertical car ce sera le plus facile. Pour faire avancer notre caméra nous n'aurons besoin que d'une simple addition. Oui oui vous avez bien entendu, nous n'aurons que d'une simple addition. :p

En effet, quand on se déplace vers l'avant (en marchant par exemple) on ne fait en réalité qu'une simple translation. Or quand on translate, on ne modifie pas notre orientation. C'est comme si l'on additionnait un vecteur par lui-même :

Pour faire avancer notre caméra, il suffira d’additionner le vecteur orientation par lui-même autant de fois que nécessaire. Et évidemment la faire pour reculer, il suffira de faire l'inverse d'une addition, c'est-à-dire ... une soustraction !

Je vous avais dit que la gestion verticale était simple.

Le déplacement latéral (1/2)

Le déplacement latéral va être un peu plus délicat à gérer. Son principe est cependant simple : il faut trouver un vecteur 'perpendiculaire' (on dira plutôt orthogonal) au vecteur orientation :

Ce vecteur orthogonal va nous permettre de déplacer la caméra de façon latéral.
D'ailleurs, une fois que nous l'aurons trouvé il suffira de faire la même chose que pour avancer :

  • Pour aller vers la gauche : on additionnera ce vecteur par lui-même autant de fois que nécessaire

  • Pour aller vers la droite : on soustraira ce vecteur par lui-même autant de fois que nécessaire

Ah d'accord, on fait la même chose que pour avancer sauf qu'ici on avance 'perpendiculairement' à l'orientation ?

Vous avez compris.
Il faut donc calculer ce vecteur pour avancer latéralement. Mais avant cela, il va falloir que l'on parle de la notion de normale d'un plan. Cette notion est importante car c'est elle qui va nous permettre de trouver ce fameux vecteur.

La normale d'un plan

Les normales sont très importantes avec OpenGL. On retrouvera cette notion plusieurs fois dans le tuto alors soyez attentifs. ;)

Une normale est une sorte de droite perpendiculaire à une surface plane, on appellera cette surface un plan. Si vous prenez un cahier (qui est une surface plane) la normale de son plan partira de la couverture pour aller vers le haut.

Cette normale peut être représentée par un vecteur. Et pour le calculer, il suffit de multiplier deux vecteurs appartenant à votre cahier :

Ça veut dire que si on multiplie deux vecteurs appartenant au cahier on trouve la normale du plan ?

Oui tout à fait, car il existe une propriété de la géométrie dans l'espace qui dit que : lorsque l'on multiplie deux vecteurs appartenant à un même plan, le résultat de cette multiplication sera un vecteur perpendiculaire au plan.

Ce vecteur résultat se nomme la normale du plan.

Cependant, pour pouvoir valider cette multiplication il va falloir respecter deux règles :

  • Premièrement, il faut absolument que les deux vecteurs à multiplier ne soient pas parallèles.

  • Deuxièmement, les vecteurs doivent partir vers des directions opposées. Il ne faut pas que leur flèche puissent se croiser.

Si une de ces deux règles n'est pas respectée alors votre calcul sera faux et vous ne trouverez pas la normale.
Cependant je vous rassure, nous utiliserons toujours de bons vecteurs, je ne vais pas être sadique et vous faire faire des calculs foireux. :p Mais sachez au moins que ces règles existent.

Si on reprend le schéma précédent, on remarque que les vecteurs V1 et V2 ne sont pas parallèles et que leur flèche ne se rejoignent pas, les deux règles sont donc respectées. Si on multiplie ces deux vecteurs entre eux on trouvera la normale du plan. ;)

Bien évidemment, les normales ne se limitent pas aux surfaces carrées. D'ailleurs, on les utilisera presque toujours sur des triangles, ce qui compte c'est que la surface utilisée soit plane :

Petite question : comment est-ce qu'on multiplie deux vecteurs ?

Ce n'est pas à proprement parler une multiplication, il s'agit plus d'un produit vectoriel. Nous n'aurons pas à le faire nous même donc ne vous prenez pas la tête avec ça.

D'ailleurs en parlant de ça, je vous avais également dit qu'il fallait faire attention à l'ordre dans la multiplication, que V1 x V2 n'était pas forcément égal à V2 x V1.

Cette remarque est toujours d'actualité. Si vous inversez la multiplication de deux vecteurs vous trouverez bien la normale, mais le problème c'est qu'elle sera inversée :

Faites juste attention à l'ordre des multiplications que nous ferrons et tout ira bien. ;)

Le déplacement latéral (2/2)

Pour en revenir à ce qui nous intéresse, je vous rappelle que l'on recherche un vecteur orthogonal au vecteur orientation pour que l'on puisse se déplacer latéralement.
Par chance, nous venons d'apprendre ce que sont les normales et je vous annonce que le vecteur que nous recherchons en est justement une !

Hein ? Mais pour calculer une normale il faut un plan non ? Or là on n'a que le vecteur orientation.

Oui tout à fait, nous avons besoin d'un plan dans lequel se trouve le vecteur orientation. Et pour le trouver, nous allons utiliser une petite astuce : l'axe y du repère 3D.

Cet axe formera le deuxième vecteur dont on a besoin pour la multiplication. En effet, quelle que soit l'orientation de la caméra, le vecteur orientation et le vecteur de l'axe y feront toujours partie du même plan :

Ces vecteurs font partie du même plan et sont orientés vers des directions différentes, on peut donc les utiliser pour calculer la normale du plan. :)

Cette astuce est efficace mais il existe un cas où elle ne fonctionnera pas. En effet, souvenez-vous que la normale est calculable tant que les deux vecteurs utilisés ne sont pas parallèles. Or, si la caméra s'aligne parfaitement à l'axe y alors l'une des deux règles sur les normales sera violée.

Pour éviter ce problème, il va falloir interdire à la caméra de se pencher à 90° vers le haut (et -90° pour le bas). Ce qui veut dire qu'à une orientation de 89°, la caméra ne pourra plus aller plus haut. Cependant, elle restera toujours libre au niveau de ses autres mouvements.

En définitif, pour trouver le vecteur de déplacement latéral il va falloir multiplier le vecteur orientation par le vecteur de l'axe y. Ensuite, il suffira de l'additionner ou de le soustraire par lui-même pour se déplacer vers la gauche ou vers la droite.

Pfiouuuu ... Tout ce gourbi pour calculer un vecteur !

Et oui c'est toute une histoire, mais au moins vous savez maintenant ce que sont les normales. C'est une notion hyper-importante qu'on aura l'occasion de retrouver lorsque l'on fera de l'éclaire dynamique avec les shaders. ;)

En résumé

Nous venons de voir pas mal de mathématique, je peux comprendre votre probable mal de tête. :p Je vais faire un résumé des points importants qu'il faut retenir.

Pour gérer l'orientation :

  • Nous avons besoin de deux angles : Theta et Phi

  • Ces deux angles permettent de calculer des coordonnées sphériques

  • Ces coordonnées sphériques formeront un vecteur qui représentera l'orientation de la caméra

Pour gérer le déplacement :

  • Si on additionne le vecteur orientation par lui-même alors la caméra avance. Quand le soustrait par lui-même, elle recule

  • Le vecteur orientation et le vecteur de l'axe y font partie du même plan

  • Si on multiplie ces vecteurs entre eux, on trouve la normale du plan. Cette normale sera le vecteur de déplacement latéral

Voilà ce qu'il faut retenir.

N'hésitez pas à relire encore et encore cette première partie de chapitre. ;)
Lorsque vous vous sentirez prêt, nous passerons à la suite du chapitre.

Implémentation de la caméra

La classe Camera

Le header

J'espère que vous avez eu le temps de digérer tout ce que l'on vient de voir. Si ça peut vous rassurer, sachez que c'était la partie la plus compliquée. Nous allons pouvoir passer à l'implémentation de la caméra. :)

Comme d'habitude, on va commencer par le header en déclarant une nouvelle classe : la classe Camera. Elle possèdera pas mal d'attributs, mais on les connait déjà tous. Voici le header de base :

#ifndef DEF_CAMERA

#define DEF_CAMERA

// Classe

class Camera

{

    public:

    Camera();

    ~Camera();

    private:

};

#endif

Dans la première partie de ce chapitre, nous avons vu que le déplacement de la caméra se divisait en 2 catégories : une qui s'occupait de gérer l'orientation et l'autre du déplacement pur et dur.

Pour gérer ce premier point, nous aurons besoin de 3 choses :

  • Un angle Theta : qui représentera l'orientation sur l'axe horizontal

  • Un angle Phi : représentera l'orientation sur l'axe vertical

  • Un vecteur orientation : qui représentera la direction dans laquelle on regarde

Ah mais comment on fait pour un avoir un vecteur en C++ ? Ça n'existe pas ?

En C++ non, mais n'oubliez pas que nous disposons d'une librairie mathématique complète. ;) GLM ne sert pas qu'aux matrices mais aussi aux vecteurs, quaternions, etc. Plein de choses en somme.

Il existe un objet que nous avons déjà utilisé et qui permet de gérer des vecteurs à 3 dimensions. Cet objet s'appelle vec3. Vous vous en souvenez j'espère . :p Il a l'avantage de posséder des méthodes simples qui permettent d'accéder à ses valeurs. Ainsi, pour retrouver sa coordonnée x par exemple, il nous suffit de faire :

monVecteur.x;

En définitif, nous allons utiliser un objet de type vec3 pour gérer l'orientation de la caméra. :)

Avec ça, nous avons donc nos 3 premiers attributs :

// Attributs d'orientation

float m_phi;

float m_theta;

glm::vec3 m_orientation;

Pour ne pas avoir d'erreur de compilation, il faut inclure les en-têtes relatifs à la librairie GLM :

#ifndef DEF_CAMERA

#define DEF_CAMERA

// Includes GLM

#include <glm/glm.hpp>

#include <glm/gtx/transform.hpp>

#include <glm/gtc/type_ptr.hpp>

// Classe

class Camera

{

    public:

    Camera();

    ~Camera();

    private:

};

#endif

Au niveau du deuxième point (le déplacement de la caméra), nous avons vu que nous aurons besoin de 3 choses :

  • Un vecteur orientation (le même que le précédent)

  • Un vecteur représentant l'axe vertical (l'axe y dans notre cas) : afin de pouvoir former un plan avec le vecteur orientation

  • Un vecteur de déplacement latéral : qui représente la normale du plan formé

Vu que nous avons déjà le vecteur orientation, on ne rajoute que les deux derniers vecteurs aux attributs :

// Attributs de déplacement

glm::vec3 m_axeVertical;

glm::vec3 m_deplacementLateral;

Petite info au passage : souvenez-vous que la méthode lookAt() prends 3 paramètres de type vec3. Pour les alimenter, il nous suffira juste de donner nos attributs qui sont eux-mêmes de objets de type vec3 ;)

En parlant de cette méthode, on va refaire un petit tour au niveau de son prototype :

mat4 lookAt(vec3 eye, vec3 center, vec3 up);

  • Vecteur eye : Position de la caméra

  • Vecteur center : Point fixé par la caméra

  • Vecteur up : Axe vertical utilisé (x, y ou z)

Si je vous remémore cette méthode c'est parce que nous en aurons besoin dans la classe Caméra. En effet, nous avons beau créer une classe toute neuve pour gérer la caméra, nous aurons toujours besoin d'utiliser les matrices et notamment la méthode lookAt() de la matrice modelview. Nous aurons donc besoin de 3 vecteurs correspondant aux 3 vecteurs demandés par cette méthode.

Comme on vient de le voir on a déjà l'axe vertical, on ne va donc ajouter que les deux autres dans les attributs. Cependant, on va faire une petite modification au niveau de leur nom de façon à les rendre plus compréhensibles. Ainsi, le vecteur eye deviendra le vecteur 'position' et center deviendra le vecteur 'pointCible' (point ciblé):

glm::vec3 m_position;

glm::vec3 m_pointCible;

Si on regroupe tous les attributs :

// Attributs

float m_phi;

float m_theta;

glm::vec3 m_orientation;

glm::vec3 m_axeVertical;

glm::vec3 m_deplacementLateral;

glm::vec3 m_position;

glm::vec3 m_pointCible;

Notre header est presque complet, il ne manque plus qu'à ajouter un constructeur - non pas un constructeur par défaut mais un constructeur qui prendra exactement les mêmes paramètres que la méthode lookAt(), à savoir :

  • Une position

  • Un point à cibler

  • Un vecteur représentant l'axe vertical

Camera(glm::vec3 position, glm::vec3 pointCible, glm::vec3 axeVertical);

Header final :

#ifndef DEF_CAMERA

#define DEF_CAMERA

// Includes GLM

#include <glm/glm.hpp>

#include <glm/gtx/transform.hpp>

#include <glm/gtc/type_ptr.hpp>

// Classe

class Camera

{

    public:

    Camera();

    Camera(glm::vec3 position, glm::vec3 pointCible, glm::vec3 axeVertical);

    ~Camera();

    private:

    float m_phi;

    float m_theta;

    glm::vec3 m_orientation;

    glm::vec3 m_axeVertical;

    glm::vec3 m_deplacementLateral;

    glm::vec3 m_position;

    glm::vec3 m_pointCible;

};

#endif

Les Constructeurs et le Destructeur

Vous commencez à avoir l'habitude avec les constructeurs et tout ça. :p Le principe ne change pas, on initialise toujours les attributs.

Au niveau du constructeur par défaut, il suffit simplement d'initialiser les angles avec la valeur 0 et les vecteurs avec leur constructeur par défaut.

Il faut cependant faire attention à l'attribut m_axeVertical. En effet, celui-ci permet savoir quel axe parmi X, Y ou Z représentera l'axe vertical. Si nous n'utilisons le constructeur par défaut il faudra quand même affecter une valeur non nulle sinon la caméra ne pourra pas faire ses calculs correctement. Vu que l'axe Z représente (malheureusement) souvent l'axe vertical alors nous donnerons cette valeur par défaut à l'attribut m_axeVertical.

Camera::Camera() : m_phi(0.0), m_theta(0.0), m_orientation(), m_axeVertical(0, 0, 1), m_deplacementLateral(), m_position(), m_pointCible()

{

}

Le second constructeur est identique au premier sauf que l'on initialise ici les vecteurs m_position, m_pointCible et m_axeVertical avec les paramètres fournis :

Camera::Camera(glm::vec3 position, glm::vec3 pointCible, glm::vec3 axeVertical) : m_phi(0.0), m_theta(0.0), m_orientation(), m_axeVertical(axeVertical),

                                                                          m_deplacementLateral(), m_position(position), m_pointCible(pointCible)

{

}

En théorie, ce constructeur ne devrait pas être modifié mais nous allons quand même y apporter une modification temporaire. Je vais vous demander d'affecter une certaine valeur aux angles, je vous expliquerai pourquoi en dernière partie de chapitre (je ne vais pas alourdir les explications pour le moment :) ).

Ainsi, affectez la valeur -35.26 à l'angle Phi et -135 à l'angle Theta.

Camera::Camera(glm::vec3 position, glm::vec3 pointCible, glm::vec3 axeVertical) : m_phi(-35.26), m_theta(-135), m_orientation(), m_axeVertical(axeVertical),

                                                                                m_deplacementLateral(), m_position(position), m_pointCible(pointCible)

{

}

Pour finir, l'implémentation du destructeur se passe de commentaires. :p

Camera::~Camera()

{

}

La méthode orienter

Alimentation des angles

La méthode que nous allons coder va être la plus importante du chapitre, c'est grâce à elle que l'on va pouvoir calculer les coordonnées sphériques du vecteur orientation. Nous l'appellerons la méthode orienter(), elle prendra en paramètres 2 integer représentant les coordonnées relatives de la souris.

Je vous rappelle que les coordonnées relatives représentent le mouvement effectué par la souris depuis l'affichage précédent, elles sont modifiées à chaque affichage (60 fois par second pour nous).

Voici le prototype de cette méthode :

void orienter(int xRel, int yRel);

Avant de commencer l'implémentation de cette méthode, nous allons revoir un petit peu les angles Phi et Theta. Je vous avais dit que ces angles allaient être alimentés par les mouvements de la souris :

  • L'angle Phi : représentant l'orientation sur l'axe vertical sera alimenté par la coordonnée y de la souris

  • L'angle Theta : représentant l'orientation sur l'axe horizontal sera alimentée par la coordonné x de la souris

A chaque tour de la boucle principale, nous devons modifier ces angles en fonction du mouvement de la souris.

Par exemple, si on bouge la souris vers le haut alors l'angle Phi s'agrandira. Au niveau du code, on additionne l'angle Phi par le petit mouvement vertical yRel :

void Camera::orienter(int xRel, int yRel)

{

    // Modification des angles

    m_phi += -yRel;

}

Hey tu t'es trompé non ? Tu as rajouté un signe - devant le paramètre yRel ?

Le sens trigonométrique

Non pas du tout. :p

C'est quelque chose que vous ne pouviez pas forcément savoir, mais lorsque l'on travaille avec des angles il y a un certain sens à respecter qui s'appelle le sens trigonométrique (sens inverse des aiguilles d'une montre). Ce sens étant inversé, les angles se retrouvent donc eux-aussi inversés.

Ainsi, un mouvement de souris allant vers le haut (donc une augmentation de l'angle Phi) sera interprété par une augmentation de son opposé (donc -Phi). Même chose pour les mouvements horizontaux, l'angle Theta sera inversé.

Mais avant on a jamais inversé nos angles ?

Dans la plupart des cas, nous n'aurons pas à inverser nos angles. Cependant ici, il y a un contre-sens entre le sens trigonométrique et le sens normal de la souris.
Si vous bougez votre souris vers le haut alors sa position Y va augmenter, elle utilise (grossièrement parlant) le sens horaire.
Or un angle, lui, va utiliser le sens anti-horaire. Donc pour compenser cette inversion, il faut additionner l'opposé de l'angle.

D'ailleurs en parlant de ça, vous avez probablement remarqué que dans certains jeux, on vous offrait la possibilité d'inverser le sens des axes X et Y. Cette option spécifie simplement à votre caméra s'il faut utiliser le sens horaire ou le sens trigonométrique, donc additionner l'angle normal ou son opposé. Si vous avez envie d'implémenter la même option un jour vous savez maintenant comment faire. ;)

En définitif, nous devons additionner l'opposé des mouvements générés par la souris à cause du sens trigonométrique.

L'angle Theta ne fait d'ailleurs pas exception :

void Camera::orienter(int xRel, int yRel)

{

    // Modification des angles

    m_phi += -yRel;

    m_theta += -xRel;

}

Nous allons rajouter encore une toute petite chose à ces calculs (ne vous inquiétez pas, il n'y a a rien de tordu cette fois :p ). Vous verrez tout à l'heure que si nous utilisons les coordonnées relatives directement comme ça, la caméra va bouger trop vite et nous fera des mouvements bizarres. Pour régler ce problème, on va abaisser les coordonnées relatives en les multipliant par 0.5. De cette façon, les mouvements de la caméra seront deux fois moins rapides, ce qui sera utile pour mieux voir.

Dans la dernière partie, nous rajouterons un attribut pour gérer cette vitesse avec un setter :

void Camera::orienter(int xRel, int yRel)

{

    // Modification des angles

    m_phi += -yRel * 0.5;

    m_theta += -xRel * 0.5;

}
Calcul des coordonnées sphériques

Nous avons maintenant des angles actualisés en fonction de la souris, c'est bien mais il faut encore imposer une limite à un angle en particulier. En effet, dans la première partie du chapitre je vous avais dit que le vecteur orientation ne devait jamais être parallèle avec l'axe Y vous vous souvenez ? Car dans ce cas, nous ne pouvions plus utiliser la formule des coordonnées sphériques.

Pour éviter de se retrouver devant cette alignement, il faut limiter l'angle Phi à une valeur maximale de 89° (ou -89° dans l'autre sens) sinon l'une des deux règles sur le calcul de la normale sera violée. Donc en imposant cette limite, nous pourrons toujours utiliser le plan formé par les deux vecteurs pour le calcul du déplacement latéral. ;)

On implémente donc deux conditions pour limiter l'attribut m_phi à une valeur de 89° ou -89° :

void Camera::orienter(int xRel, int yRel)

{

    // Récupération des angles

    m_phi += -yRel * 0.5;

    m_theta += -xRel * 0.5;

    // Limitation de l'angle phi

    if(m_phi > 89.0)

        m_phi = 89.0;

    else if(m_phi < -89.0)

        m_phi = -89.0;

}

Nous avons maintenant des angles parfaitement utilisables, nous allons pouvoir passer au calcul des coordonnées sphériques de l'orientation. Et pour cela on va utiliser une formule que nous avons déjà vue. :p

Nous devons donc transposer cette formule en code source. On utilisera le setter tout bête de chaque coordonnée du vecteur m_orientation pour affecter le résultat :

// Calcul des coordonnées sphériques

m_orientation.x = cos(m_phi) * sin(m_theta);

m_orientation.y = sin(m_phi);

m_orientation.z = cos(m_phi) * cos(m_theta);

....

....

Vous ne voyez pas une petite erreur dans ce code ?

Euh non je pense pas ... Il y en a une ?

Hum bon c'est un peu sadique je l'avoue, mais je veux que vous intégrez bien cette notion : le problème ici c'est qu'on envoie des angles exprimés en degrés alors que les fonctions sin() et cos() attendent des angles exprimés radians. Si on ne modifie pas ça, on risque d'avoir une sacrée surprise au moment de déplacer la caméra. :lol:

Pour corriger le problème, nous allons convertir les angles Phi et Theta en radian. On stockera les nouveaux angles dans des variables temporaires que l'on utilisera pour calculer les coordonnées sphériques.

Je vous rappelle que pour convertir un angle en radian il faut le multiplier par Pi puis le diviser par 180 :

// Conversion des angles en radian

float phiRadian = m_phi * M_PI / 180;

float thetaRadian = m_theta * M_PI / 180;

// Calcul des coordonnées sphériques

m_orientation.x = cos(phiRadian) * sin(thetaRadian);

m_orientation.y = sin(phiRadian);

m_orientation.z = cos(phiRadian) * cos(thetaRadian);

Cette fois le calcul est fonctionnel car nous utilisons bien des angles exprimés en radians. Cependant et malgré ça, il subsiste encore un petit problème.

En effet, la formule avec laquelle je vous rabâche la tête depuis tout à l'heure n'est valable que si l'on utilise l'axe Y. Or tout le monde n'utilise pas forcément cet axe, il faut gérer les axes X et Z également.

Pour cela, il n'y a rien de compliqué puisqu'il suffit juste d'interchanger les setters du vecteur orientation entre eux. Je vous donne les formules pour les 3 axes X, Y et Z :

(Celle-la on la connait)

Pour gérer ces 3 formules nous allons simplement utiliser 3 blocs if() qui vont tester les coordonnées de l'attribut m_axeVertical. Celui qui posséde la valeur 1.0 représentera l'axe vertical :

// Si l'axe vertical est l'axe X

if(m_axeVertical.x == 1.0)

{

}

// Si c'est l'axe Y

else if(m_axeVertical.y == 1.0)

{

}

// Sinon c'est l'axe Z

else

{

}

Si le développeur n'a pas rentré la valeur 1.0 à une coordonnée alors ce sera l'axe Z qui sera choisi. Le développeur doit faire attention à la valeur qu'il donne pour ce vecteur, c'est sa responsabilité. ;)

Une fois les blocs créés, il suffit d'associer la bonne formule aux bonnes coordonnées :

// Si l'axe vertical est l'axe X

if(m_axeVertical.x == 1.0)

{

    // Calcul des coordonnées sphériques

    m_orientation.x = sin(phiRadian);

    m_orientation.y = cos(phiRadian) * cos(thetaRadian);

    m_orientation.z = cos(phiRadian) * sin(thetaRadian);

}

// Si c'est l'axe Y

else if(m_axeVertical.y == 1.0)

{

    // Calcul des coordonnées sphériques

    m_orientation.x = cos(phiRadian) * sin(thetaRadian);

    m_orientation.y = sin(phiRadian);

    m_orientation.z = cos(phiRadian) * cos(thetaRadian);

}

// Sinon c'est l'axe Z

else

{

    // Calcul des coordonnées sphériques

    m_orientation.x = cos(phiRadian) * cos(thetaRadian);

    m_orientation.y = cos(phiRadian) * sin(thetaRadian);

    m_orientation.z = sin(phiRadian);

}

Cette fois, le code est totalement fonctionnel. :D

Dernier point à préciser, on devrait en théorie normaliser le vecteur orientation, mais vu que nous utilisons des fonctions trigonométriques le vecteur est déjà normalisé. En effet, lorsqu'on utilise la trigonométrie, le vecteur qui représente le rayon a toujours une norme égale à 1 donc il est normalisé. ;)

Calcul du vecteur de déplacement latéral

Nous avons maintenant le fameux vecteur orientation, nous pouvons donc l'utiliser pour le calcul du vecteur de déplacement latéral.
Vous vous souvenez que pour le trouver, il fallait multiplier le vecteur orientation par le vecteur représentant l'axe vertical. On trouve ainsi la normale du plan formé par ces deux vecteurs.

Quand je parle de la multiplication c'est un peu un abus de langage car il s'agit en fait d'un produit vectoriel (en comparaison du produit d'une multiplication). Le produit vectoriel permet de trouver le fameux vecteur orthogonal que nous cherchons.

Ce produit ne se fait pas avec un signe * classique, il faut plutôt utiliser une méthode de la librairie GLM qui s'appelle cross() :

glm::vec3 cross(glm::vec3 vector1, glm::vec3 vector2);

Cette méthode retourne le vecteur orthogonal des deux paramètres donnés.

Nous l'appelons donc en lui donnant les attributs m_axeVertical et m_orientation pour qu'elle puisse le calculer :

// Calcul de la normale

m_deplacementLateral = cross(m_axeVertical, m_orientation);

On pensera cette fois à normaliser le résultat car on n'utilise pas la trigo, ce qui fait que rien n'est normalisé tant qu'on ne l'a pas demandé. Mais encore une fois, la librairie GLM nous fournit directement une méthode pour normaliser les vecteurs. Nous n'avons rien à faire décidément. :p

Cette méthode s'appelle normalize() :

glm::vec3 normalize(glm::vec3 vector);

Elle prend en paramètre le vecteur à normaliser et renvoie le résultat.

Nous l'appellerons en lui donnant le vecteur que l'on vient de calculer, à savoir m_deplacementVertical :

// Calcul de la normale

m_deplacementLateral = cross(m_axeVertical, m_orientation);

m_deplacementLateral = normalize(m_deplacementLateral);
Actualisation du point ciblé

Enfin pour terminer cette méthode, il ne reste plus qu'à actualiser le point fixé par la matrice modelview (l'un des trois paramètres dont à besoin la matrice).

Pour trouver ce point, on va utiliser une petite astuce : on va additionner le vecteur position avec le vecteur orientation. Le résultat sera le point qu'attend la matrice, soit le point pile en face de la position de la caméra.

On peut additionner deux objets vec3 ? Ça doit être compliqué non ?

Non pas du tout car les développeurs de GLM ont pensé à tout et ont intégré, au même titre que les matrices, les surcharges d'opérateurs. Nous pouvons donc les additionner sans problème juste en utilisant le signe +. Génial non ?

Pour additionner le vecteur position et orientation, il nous suffit donc d'utiliser le signe + :

Au niveau du code, on additionne simplement ces deux vecteurs :

// Calcul du point ciblé pour OpenGL

m_pointCible = m_position + m_orientation;

Addition terminée. :p

Et ce vecteur on ne le normalise pas ?

Nan, les vecteurs position et pointCible ne doivent jamais être normalisés car ils représentent des positions dans l'espace. Ils permettent à OpenGL de savoir où l'on se trouve dans un monde 3D, et un monde 3D ne se limite pas à des positions de 1 unité maximum.

Les vecteurs normalisés servent pour les calculs diverses comme l'orienation et le vecteur déplacementLateral.

Récapitulation

Si on récapitule tout ce que l'on vient de coder :

void Camera::orienter(int xRel, int yRel)

{

    // Récupération des angles

    m_phi += -yRel * 0.5;

    m_theta += -xRel * 0.5;

    // Limitation de l'angle phi

    if(m_phi > 89.0)

        m_phi = 89.0;

    else if(m_phi < -89.0)

        m_phi = -89.0;

    // Conversion des angles en radian

    float phiRadian = m_phi * M_PI / 180;

    float thetaRadian = m_theta * M_PI / 180;

    // Si l'axe vertical est l'axe X

    if(m_axeVertical.x == 1.0)

    {

        // Calcul des coordonnées sphériques

        m_orientation.x = sin(phiRadian);

        m_orientation.y = cos(phiRadian) * cos(thetaRadian);

        m_orientation.z = cos(phiRadian) * sin(thetaRadian);

    }

    // Si c'est l'axe Y

    else if(m_axeVertical.y == 1.0)

    {

        // Calcul des coordonnées sphériques

        m_orientation.x = cos(phiRadian) * sin(thetaRadian);

        m_orientation.y = sin(phiRadian);

        m_orientation.z = cos(phiRadian) * cos(thetaRadian);

    }

    // Sinon c'est l'axe Z

    else

    {

        // Calcul des coordonnées sphériques

        m_orientation.x = cos(phiRadian) * cos(thetaRadian);

        m_orientation.y = cos(phiRadian) * sin(thetaRadian);

        m_orientation.z = sin(phiRadian);

    }

    // Calcul de la normale

    m_deplacementLateral = cross(m_axeVertical, m_orientation);

    m_deplacementLateral = normalize(m_deplacementLateral);

    // Calcul du point ciblé pour OpenGL

    m_pointCible = m_position + m_orientation;

}

Grâce à cette méthode, nous pouvons calculer non seulement l'orientation de la caméra mais aussi son vecteur orthogonal qui sera utilisé pour le déplacement latéral. :)

La méthode deplacer

La méthode suivante va permettre de s'occuper du déplacement pur et dur de la caméra. Nous savons déjà comment faire en plus ça ne sera pas compliqué.

Nous appellerons cette méthode la méthode deplacer(), elle prendra en paramètre une référence constante sur un objet de type Input. Car oui, nous aurons besoin de savoir si les touches de déplacement sont pressées ou non, nous avons donc besoin d'un objet de ce type.

void deplacer(Input const &input);

Pour faire avancer ou reculer la caméra on additionnera ou on soustraira le vecteur orientation :

Et pour la déplacer latéralement on additionnera ou on soustraira le vecteur orthogonal au vecteur orientation :

Au niveau du code, on va encapsuler les quatre touches de déplacement dans des blocs if. Si une touche est pressée, alors on effectue l'action désirée (addition ou soustraction de vecteurs).

Par exemple, si la touche Haut (SDL_SCANCODE_UP) est pressée alors on additionnera le vecteur orientation avec le vecteur position pour faire avancer la caméra. On ajoutera ici aussi une contrainte de vitesse exactement comme pour l'orientation de la caméra (on créera également un attribut pour gérer ça plus tard). Nous diviserons la vitesse de déplacement par 2 en multipliant le vecteur orientation par 0.5 (un vecteur multiplié par un nombre est différent d'un calcul vectoriel. On multiplie juste les coordonnées par 0.5 dans ce cas [image] ) :

void Camera::deplacer(Input const &input)

{

    // Avancée de la caméra

    if(input.getTouche(SDL_SCANCODE_UP))

        m_position = m_position + m_orientation * 0.5f;

}

Lorsque la position de la caméra est modifiée alors le point ciblé par OpenGL doit également être modifié. Pour cela, on fait exactement ce que l'on a fait dans la méthode orienter() :

void Camera::deplacer(Input const &input)

{

    // Avancée de la caméra

    if(input.getTouche(SDL_SCANCODE_UP))

    {

        m_position = m_position + m_orientation * 0.5f;

        m_pointCible = m_position + m_orientation;

    }

}

Grâce à cette condition, notre caméra peut avancer quand nous voulons. :D

D'ailleurs pour la faire reculer ce n'est pas plus compliqué, on fait exactement la même chose sauf que cette fois on soustrait le vecteur position et le vecteur orientation. On utilisera le scancode SDL_SCANCODE_DOWN pour la touche du clavier :

// Recul de la caméra

if(input.getTouche(SDL_SCANCODE_DOWN))

{

    m_position = m_position - m_orientation * 0.5f;

    m_pointCible = m_position + m_orientation;

}

Le principe ne change pas pour le déplacement latéral : si on veut aller vers la gauche on additionne le vecteur m_deplacementLateral à la position, et si on veut aller vers la doite on le soustrait. On utilisera respectivement les scancodes SDL_SCANCODE_LEFT et SDL_SCANCODE_RIGHT :

// Déplacement vers la gauche

if(input.getTouche(SDL_SCANCODE_LEFT))

{

    m_position = m_position + m_deplacementLateral * 0.5f;

    m_pointCible = m_position + m_orientation;

}

// Déplacement vers la droite

if(input.getTouche(SDL_SCANCODE_RIGHT))

{

    m_position = m_position - m_deplacementLateral * 0.5f;

    m_pointCible = m_position + m_orientation;

}

Si on récapitule tout ça :

void Camera::deplacer(Input const &input)

{

    // Avancée de la caméra

    if(input.getTouche(SDL_SCANCODE_UP))

    {

        m_position = m_position + m_orientation * 0.5f;

        m_pointCible = m_position + m_orientation;

    }

    // Recul de la caméra

    if(input.getTouche(SDL_SCANCODE_DOWN))

    {

        m_position = m_position - m_orientation * 0.5f;

        m_pointCible = m_position + m_orientation;

    }

    // Déplacement vers la gauche

    if(input.getTouche(SDL_SCANCODE_LEFT))

    {

        m_position = m_position + m_deplacementLateral * 0.5f;

        m_pointCible = m_position + m_orientation;

    }

    // Déplacement vers la droite

    if(input.getTouche(SDL_SCANCODE_RIGHT))

    {

        m_position = m_position - m_deplacementLateral * 0.5f;

        m_pointCible = m_position + m_orientation;

    }

}

Petite question : pourquoi n'utilise-t-on pas des else if ?

Simplement pour être capable d'utiliser plusieurs touches à la fois. ;) Si on utilisait des blocs else if, il n'y aurait qu'une seule touche qui serait gérée pour se déplacer.

Il ne manque plus qu'une petite chose à cette méthode. En effet, vu qu'on a l'objet input sous la main on peut en profiter pour savoir si il y a eu un mouvement de souris. Et s'il y a mouvement de souris alors il y a une modification de l'orientation et donc un appel à la méthode orienter().

On va donc rajouter une condition qui sera déclenchée lors d'un mouvement de la souris. A l'intérieur, on appellera la méthode orienter() à laquelle on donnera en paramètres les attributs m_xRel et m_yRel de l'objet input.

// Gestion de l'orientation

if(input.mouvementSouris())

    orienter(input.getXRel(), input.getYRel());

Méthode finale :

void Camera::deplacer(Input const &input)

{

    // Gestion de l'orientation

    if(input.mouvementSouris())

        orienter(input.getXRel(), input.getYRel());

    // Avancée de la caméra

    if(input.getTouche(SDL_SCANCODE_UP))

    {

        m_position = m_position + m_orientation * 0.5f;

        m_pointCible = m_position + m_orientation;

    }

    // Recul de la caméra

    if(input.getTouche(SDL_SCANCODE_DOWN))

    {

        m_position = m_position - m_orientation * 0.5f;

        m_pointCible = m_position + m_orientation;

    }

    // Déplacement vers la gauche

    if(input.getTouche(SDL_SCANCODE_LEFT))

    {

        m_position = m_position + m_deplacementLateral * 0.5f;

        m_pointCible = m_position + m_orientation;

    }

    // Déplacement vers la droite

    if(input.getTouche(SDL_SCANCODE_RIGHT))

    {

        m_position = m_position - m_deplacementLateral * 0.5f;

        m_pointCible = m_position + m_orientation;

    }

}

Avec cette méthode, nous pouvons gérer complétement le déplacement de la caméra. :D

Méthode lookAt

On arrive à la dernière méthode de notre caméra. :(

Jusqu'à maintenant, nous avons utilisé pas mal de vecteurs mais vous savez que seuls 3 d'entre eux sont vraiment importants : la position, le point ciblé et l'axe vertical. Ces trois vecteurs sont indispensables pour la matrice modelview, nous devons donc les lui envoyer.

Nous allons coder une nouvelle méthode pour s'occuper de ça, on l'appellera lookAt() en référence à la méthode du même nom chez GLM. Elle prendra en paramètre une référence (non constante) sur la matrice modelview :

void lookAt(glm::mat4 &modelview);

Pour son implémentation, on appelle simplement la méthode lookAt() de la matrice modelview, on lui donne au passage les 3 vecteurs qu'elle demande. Faites attention à utiliser le namespace glm:: ici même si vous utilisez le using dans votre fichier. Vu que nous avons deux méthodes portant le même nom, il faut utiliser le namespace pour les différencier :

void Camera::lookAt(glm::mat4 &modelview)

{

    // Actualisation de la vue dans la matrice

    modelview = glm::lookAt(m_position, m_pointCible, m_axeVertical);

}

Implémentation de la caméra

Notre caméra est maintenant totalement opérationnelle ! Il ne manque plus qu'à l'implémenter dans notre scène. :D

On ajoute donc l'en-tête Camera.h dans le header SceneOpenGL.h, puis on déclare un objet de type Camera dans la méthode bouclePrincipale(). On placera cette caméra au point de coordonnées (3, 3, 3) et elle ciblera le point de coordonnées (0, 0, 0). Bien entendu, l'axe vertical reste l'axe y (0, 1, 0).

void SceneOpenGL::bouclePrincipale()

{

    // Variables

    Uint32 frameRate (1000 / 50);

    Uint32 debutBoucle(0), finBoucle(0), tempsEcoule(0);

    // Matrices

    mat4 projection;

    mat4 modelview;

    projection = perspective(70.0, (double) m_largeurFenetre / m_hauteurFenetre, 1.0, 100.0);

    modelview = mat4(1.0);

    // Caméra mobile

    Camera camera(vec3(3, 3, 3), vec3(0, 0, 0), vec3(0, 1, 0));

    ....

}

On en profite au passage pour piéger et cacher le pointeur de la souris grâce aux méthodes suivantes :

// Capture du pointeur

m_input.afficherPointeur(false);

m_input.capturerPointeur(true);

void SceneOpenGL::bouclePrincipale()

{

    // Variables

    Uint32 frameRate (1000 / 50);

    Uint32 debutBoucle(0), finBoucle(0), tempsEcoule(0);

    // Matrices

    mat4 projection;

    mat4 modelview;

    projection = perspective(70.0, (double) m_largeurFenetre / m_hauteurFenetre, 1.0, 100.0);

    modelview = mat4(1.0);

    // Caméra mobile

    Camera camera(vec3(3, 3, 3), vec3(0, 0, 0), vec3(0, 1, 0));

    m_input.afficherPointeur(false);

    m_input.capturerPointeur(true);

    ....

}

Pour les linuxiens, il semble qu'il y ait un problème avec la méthode capturerPointeur(). La fonction SDL_SetRelativeMouseMode() qui est utilisée à l'intérieur ne semble pas fonctionner chez vous. C'est un problème qui existe depuis la SDL 1.3, j'espère que la librairie va corriger ce problème rapidement pour que vous n'ayez plus de soucis. :(

Ce qui est bizarre c'est que tout fonctionne correctement sous Windows mais pas sous Linux ...

Ensuite, on appelle la méthode deplacer() de l'objet camera sans oublier de donner l'attribut m_input pour qu'il puisse travailler avec :

// Boucle principale

while(!m_input.terminer())

{

    // On définit le temps de début de boucle

    debutBoucle = SDL_GetTicks();

    // Gestion des évènements

    m_input.updateEvenements();

    if(m_input.getTouche(SDL_SCANCODE_ESCAPE))

        break;

    camera.deplacer(m_input);

    ....

}

Enfin, on remplace la méthode lookAt() de la matrice modelview par la méthode lookAt() de la caméra. On n'oublie pas de lui donner le paramètre qu'elle attend soit la matrice modelview elle-même :

....

// Nettoyage de l'écran

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// Gestion de la caméra

camera.lookAt(modelview);

....

Et là, vous pouvez compiler joyeusement votre code source. :D

Essayez votre nouvelle caméra, vous pouvez vous déplacer dans toutes les directions sans aucun problème.

Notre caméra est maintenant quasi-complète, il manque en réalité encore une méthode. En effet, tout à l'heure je vous ai demandé de mettre des valeurs par défaut pour les angles dans le constructeur. Or cette solution ne fonctionne que dans le cas où la caméra se trouve au point de coordonnées (3, 3, 3). Dans la dernière partie de ce chapitre, nous allons coder une nouvelle méthode qui permettra de régler ce problème.

Fixer un point

Dans ces deux dernières parties, nous allons ajouter quelques petites fonctionnalités à la classe Camera. Nous nous occuperons :

  • D'ajouter une méthode pour cibler un point

  • D'ajouter un setter pour la position de la caméra

  • D'ajouter deux attributs pour gérer sa sensibilité (orientation) et sa vitesse (déplacement)

Nous commencerons évidemment par le premier point. ;) Les autres seront développés dans la dernière partie.

Fonctionnalités de la classe Camera

La méthode setPointcible()

Tout à l'heure, je vous avais demandé d'affecter certaines valeurs aux angles Theta et Phi dans le constructeur. Si vous n'aviez pas fait cela, vos angles auraient été initialisés avec la valeur 0 et votre regard aurait été complétement inversé par rapport au point à cibler :

Vous auriez bien placé votre caméra mais elle ne serait pas du tout orientée de la bonne façon malgré le fait que vous auriez donné en paramètre le point à cibler. Elle serait orientée selon la valeur des angles soit la valeur 0 à cause du constructeur. Rappelez-vous qu'à chaque fois on ne fait qu'additionner les changements de position et les angles partent avec leur valeur d'origine :

// Modification des angles

m_phi += -yRel * 0.5;

m_theta += -xRel * 0.5;

Pour régler ce problème, il faut que les angles soient initialisés avec la bonne orientation dès le départ. Et pour ça, nous allons coder une méthode qui fera l'inverse de la méthode orienter(). Elle devra être capable de calculer la valeur initiale des angles à partir des vecteurs qu'on lui envoie.

Cette méthode va être facile à coder, il suffit juste de reprendre la formule des coordonnées sphériques et d'en inverser les opérations pour trouver la valeur des angles. :p On commence avec la formule valable pour l'axe Y :

Avec cette formule, on peut trouver les angles Theta et Phi à partir des coordonnées (x, y, z) - coordonnées qui appartiennent au vecteur orientation dans le cas où on utilise l'axe Y. Pour trouver les angles avec les autres axes, il suffit juste d'interchanger les formules de départ pour tomber sur les résultats suivants :

Vous voyez que la formule reste la même, il n'y a que la coordonnée utilisée qui va changer.

Oui c'est bien tout ça mais on a besoin des coordonnées du vecteur orientation pour calculer les angles non ? Or on ne l'a pas celui-la à l'initialisation de la caméra ?

Oui tout à fait nous ne l'avons pas à l'initialisation de la caméra. Mais encore une fois, nous pouvons le trouver en inversant une autre formule mathématique.

Et oui, souvenez-vous que pour trouver le vecteur pointCible, on additionne le vecteur position et le vecteur orientation ? :

// Calcul du point ciblé pour OpenGL

m_pointCible = m_position + m_orientation;

Donc pour trouver le vecteur orientation (et surtout ses coordonnées), il suffit de soustraire le vecteur pointCible par le vecteur position :

Grâce à cette soustraction, on va pouvoir calculer la valeur initiale des angles au moment de l'initialisation de la caméra.

Pour effectuer tous ces calculs, nous utiliserons une méthode, ou plutôt un setter sur l'attribut m_pointCible que nous appellerons simplement setPointcible(). Il prendra en paramètre le vecteur à cibler :

void setPointcible(glm::vec3 pointCible);

On commence son implémentation en soustrayant les vecteurs pointCible et position pour trouver le vecteur orientation. Comme d'habitude, on normalisera ce vecteur :

void Camera::setPointcible(glm::vec3 pointCible)

{

    // Calcul du vecteur orientation

    m_orientation = m_pointCible - m_position;

    m_orientation = normalize(m_orientation);

}

Une fois le vecteur orientation trouvé, on peut maintenant calculer les angles Phi et Theta grâce aux formules inversées que l'on a trouvées précédemment :

Comme pour la méthode orienter(), on commence par faire 3 blocs if qui vont permettre de gérer le cas des trois axes verticaux :

// Si l'axe vertical est l'axe X

if(m_axeVertical.x == 1.0)

{

}

// Si c'est l'axe Y

else if(m_axeVertical.y == 1.0)

{

}

// Sinon c'est l'axe Z

else

{

}

Une fois que c'est fait, on associe la bonne formule au bon axe. Je vous donne l'exemple du cas où on utilise l'axe Y :

// Calcul des angles pour l'axe Y

m_phi = asin(m_orientation.y);

m_theta = acos(m_orientation.z / cos(m_phi));

Implémentation du bon calcul dans les blocs if :

// Si l'axe vertical est l'axe X

if(m_axeVertical.x == 1.0)

{

    // Calcul des angles

    m_phi = asin(m_orientation.x);

    m_theta = acos(m_orientation.y / cos(m_phi));

}

// Si c'est l'axe Y

else if(m_axeVertical.y == 1.0)

{

    // Calcul des angles

    m_phi = asin(m_orientation.y);

    m_theta = acos(m_orientation.z / cos(m_phi));

}

// Sinon c'est l'axe Z

else

{

    // Calcul des angles

    m_phi = asin(m_orientation.x);

    m_theta = acos(m_orientation.z / cos(m_phi));

}

En temps normal nous pourrions nous arrêter là mais il existe un petit problème avec la coordonnée utilisée pour calculer l'angle Theta. En effet, les formules que nous utilisons ne sont valables que si cette coordonnée est supérieure à 0. Si elle est inférieure à 0, les formules vont donner une valeur opposée de l'angle ( -Theta ), ce qui inversera le résultat final.

Pour régler ce problème, il suffit d'ajouter une sous-condition dans chaque bloc if pour vérifier le signe de la coordonnée utilisée : s'il est négatif, alors on multiplie l'angle Theta par -1 pour avoir l'opposée de l'opposée, et donc la vraie valeur. S'il est positif alors l'angle est déjà correcte, on ne fait rien.

Voici ce que ça donne dans le cas où on utilise l'axe Y :

// Calcul des angles

m_phi = asin(m_orientation.y);

m_theta = acos(m_orientation.z / cos(m_phi));

if(m_orientation.z < 0)

    m_theta *= -1;

Implémentation pour tous les blocs if :

// Si l'axe vertical est l'axe X

if(m_axeVertical.x == 1.0)

{

    // Calcul des angles

    m_phi = asin(m_orientation.x);

    m_theta = acos(m_orientation.y / cos(m_phi));

    if(m_orientation.y < 0)

        m_theta *= -1;

}

// Si c'est l'axe Y

else if(m_axeVertical.y == 1.0)

{

    // Calcul des angles

    m_phi = asin(m_orientation.y);

    m_theta = acos(m_orientation.z / cos(m_phi));

    if(m_orientation.z < 0)

        m_theta *= -1;

}

// Sinon c'est l'axe Z

else

{

    // Calcul des angles

    m_phi = asin(m_orientation.x);

    m_theta = acos(m_orientation.z / cos(m_phi));

    if(m_orientation.z < 0)

        m_theta *= -1;

}

On arrive à la fin de la méthode, il ne reste plus qu'une chose à faire : convertir les angles Theta et Phi en degrés pour pouvoir les utiliser avec les coordonnées relatives de la souris. Les fonctions cos() et sin() ne prennent et ne renvoient que des angles exprimés en radians (je ne l'ai pas déjà dit :-° ) alors que nous, nous avons besoin d'angles exprimés en degrés pour pouvoir travailler avec la souris. Il faut donc convertir les résultats en multipliant les angles par 180 puis en les divisant par Pi :

// Conversion en degrés

m_phi = m_phi * 180 / M_PI;

m_theta = m_theta * 180 / M_PI;

Si on résume tout ça :

void Camera::setPointcible(glm::vec3 pointCible)

{

    // Calcul du vecteur orientation

    m_orientation = m_pointCible - m_position;

    m_orientation = normalize(m_orientation);

    // Si l'axe vertical est l'axe X

    if(m_axeVertical.x == 1.0)

    {

        // Calcul des angles

        m_phi = asin(m_orientation.x);

        m_theta = acos(m_orientation.y / cos(m_phi));

        if(m_orientation.y < 0)

            m_theta *= -1;

    }

    // Si c'est l'axe Y

    else if(m_axeVertical.y == 1.0)

    {

        // Calcul des angles

        m_phi = asin(m_orientation.y);

        m_theta = acos(m_orientation.z / cos(m_phi));

        if(m_orientation.z < 0)

            m_theta *= -1;

    }

    // Sinon c'est l'axe Z

    else

    {

        // Calcul des angles

        m_phi = asin(m_orientation.x);

        m_theta = acos(m_orientation.z / cos(m_phi));

        if(m_orientation.z < 0)

            m_theta *= -1;

    }

    // Conversion en degrés

    m_phi = m_phi * 180 / M_PI;

    m_theta = m_theta * 180 / M_PI;

}

Et voilà ! Grâce à cette méthode nous pouvons enfin régler le problème d'initialisation de la caméra. Et de plus, nous pouvons l'orienter quand on le veut (pour une cinématique par exemple).

Implémentons sans plus tarder cette méthode dans le constructeur. Pour ça, on ré-initialise les angles Theta et Phi à 0 puis on appelle la méthode setPointCible() :

Camera::Camera(glm::vec3 position, glm::vec3 pointCible, glm::vec3 axeVertical) : m_phi(0.0), m_theta(0.0), m_orientation(),

                                                                                m_axeVertical(axeVertical), m_deplacementLateral(),

                                                                                m_position(position), m_pointCible(pointCible)

{

    // Actualisation du point ciblé

    setPointcible(pointCible);

}

Améliorations

Dans cette dernière partie, nous allons finir en douceur et voir les dernières petites fonctionnalités que nous allons ajouter à notre classe Camera. Je vous les rappelle :

  • Ajouter un setter pour la position de la caméra

  • Ajouter deux attributs pour gérer sa sensibilité (orientation) et sa vitesse (déplacement)

On commencera par le premier point. :)

La méthode setPosition()

Bon je pense que vous avez l'habitude des setters maintenant, surtout que celui-ci va être plus simple que le précédent. :p

Nous allons coder un petit setter setPosition() qui nous permettra de positioner la caméra quand on le souhaitera :

void setPosition(glm::vec3 position);

Ce setter permet de mettre à jour l'attribut m_position. On utilisera le signe = qui nous permet de copier deux objets de type vec3 facilement. Le seul point auquel il faut faire attention est le fait qu'il faut mettre à jour le point ciblé à chaque fois que l'on change de position :

void Camera::setPosition(glm::vec3 position)

{

    // Mise à jour de la position

    m_position = position;

    // Actualisation du point ciblé

    m_pointCible = m_position + m_orientation;

}

Bien entendu on ne normalise pas ce vecteur-la, il fait partie des deux exceptions. ;)

Les attributs sensibilité et rapidité

On passe à la fonctionnalité suivante, nous allons créer deux nouveaux paramètres qui vont nous permettre de modifier la vitesse de déplacement de la caméra.

Vous vous souvenez que je vous avais demandé de multiplier les angles et le vecteur orientation par 0.5 pour réduire la vitesse de la caméra ?

void Camera::orienter(int xRel, int yRel)

{

    // Récupération des angles

    m_phi += -yRel * 0.5f;

    m_theta += -xRel * 0.5f;

    .....

}

void Camera::deplacer(Input const &input)

{

    .....

    // Avancée de la caméra

    if(input.getTouche(SDL_SCANCODE_UP))

    {

        m_position = m_position + m_orientation * 0.5f;

        m_pointCible = m_position + m_orientation;

    }

    .....

}

Le problème avec ces valeurs c'est qu'elles sont figées, on ne peut pas les moduler. Tous les jeux-vidéo n'utilisent pas forcément la même vitesse de déplacement et de plus, les valeurs que nous avons mises sont dignes des plus grands championnats de courses d'escargots !

En effet, pour un véritable jeu il faut multiplier ces vitesses par 10 voire plus et non 0.5.

Pour gérer tout ça, nous allons implémenter deux attributs m_sensibilite et m_vitesse qui correspondront respectivement à la sensibilité de la souris (l'orientation) et à la vitesse de déplacement (la position) :

class Camera

{

    public:

    // Méthodes

    ....

    private:

    float m_phi;

    float m_theta;

    glm::vec3 m_orientation;

    glm::vec3 m_axeVertical;

    glm::vec3 m_deplacementLateral;

    glm::vec3 m_position;

    glm::vec3 m_pointCible;

    float m_sensibilite;

    float m_vitesse;

};

Bien entendu, on modifie nos deux constructeurs pour qu'ils puissent prendre en paramètre des valeurs pour ces deux attributs.

Voici le constructeur par défaut :

Camera::Camera() : m_phi(0.0), m_theta(0.0), m_orientation(), m_axeVertical(0, 0, 1), m_deplacementLateral(), m_position(), m_pointCible(), m_sensibilite(0.0), m_vitesse(0.0)

{

}

Et voici le second constructeur :

Camera(glm::vec3 position, glm::vec3 pointCible, glm::vec3 axeVertical, float sensibilite, float vitesse);

Camera::Camera(glm::vec3 position, glm::vec3 pointCible, glm::vec3 axeVertical, float sensibilite, float vitesse) : m_phi(0.0), m_theta(0.0), m_orientation(),

                                                                                m_axeVertical(axeVertical), m_deplacementLateral(),

                                                                                m_position(position), m_pointCible(pointCible),

                                                                                m_sensibilite(sensibilite), m_vitesse(vitesse)

{

    // Actualisation du point ciblé

    setPointcible(pointCible);

}

Ensuite, on inclut l'attribut m_sensibilite dans la méthode orienter() :

void Camera::orienter(int xRel, int yRel)

{

    // Modification des angles

    m_phi += -yRel * m_sensibilite;

    m_theta += -xRel * m_sensibilite;

    .....

}

Et enfin, on ajoute l'attribut m_vitesse à chaque déplacement possible de la caméra :

void Camera::deplacer(Input const &input)

{

    // Gestion de l'orientation

    if(input.mouvementSouris())

        orienter(input.getXRel(), input.getYRel());

    // Avancée de la caméra

    if(input.getTouche(SDL_SCANCODE_UP))

    {

        m_position = m_position + m_orientation * m_vitesse;

        m_pointCible = m_position + m_orientation;

    }

    // Recul de la caméra

    if(input.getTouche(SDL_SCANCODE_DOWN))

    {

        m_position = m_position - m_orientation * m_vitesse;

        m_pointCible = m_position + m_orientation;

    }

    // Déplacement vers la gauche

    if(input.getTouche(SDL_SCANCODE_LEFT))

    {

        m_position = m_position + m_deplacementLateral * m_vitesse;

        m_pointCible = m_position + m_orientation;

    }

    // Déplacement vers la droite

    if(input.getTouche(SDL_SCANCODE_RIGHT))

    {

        m_position = m_position - m_deplacementLateral * m_vitesse;

        m_pointCible = m_position + m_orientation;

    }

}

Pensez à renseigner ces deux paramètres lorsque vous initialisez la caméra dans la classe SceneOpenGL :

// Caméra mobile

Camera camera(vec3(3, 3, 3), vec3(0, 0, 0), vec3(0, 1, 0), 0.5, 0.5);

Petit conseil pour terminer, je vous conseille vivement de faire des getters et setters sur ces deux attributs. Ca pourrait être utile dans vos futurs développements :

// Getters et Setters

float getSensibilite() const;

float getVitesse() const;

void setSensibilite(float sensibilite);

void setVitesse(float vitesse);

float Camera::getSensibilite() const

{

    return m_vitesse;

}

float Camera::getVitesse() const

{

    return m_vitesse;

}

void Camera::setSensibilite(float sensibilite)

{

    m_sensibilite = sensibilite;

}

void Camera::setVitesse(float vitesse)

{

    m_vitesse = vitesse;

}

Télécharger : Code Source C++ du chapitre 11

Nous sommes enfin arrivés à la fin de ce chapitre, j'espère que vous êtes toujours en vie. :p

Nous avons vu pas mal de notions mathématiques mais elles sont indispensables dans le développement 3D. Si ça peut vous rassurer, nous n'en reverrons pas beaucoup avant longtemps. Je ne vous ai pas encore parlé des quaternions ça sera un grand moment quand nous aborderons ce chapitre-la.

Enfin, nous avons maintenant une caméra totalement opérationnelle et nous pouvons nous déplacer allégrement dans notre monde 3D. Que diriez-vous maintenant si nous faisions un gros TP récapitulatif ?


TP : Une relique retrouvée

Piouf, nous en avons fait du chemin depuis le début du tutoriel. Nous avons vu toutes les notions de base de la programmation OpenGL depuis l'affichage de triangles simples jusqu'à la gestion des caméras mobiles.

Pour conclure cette première partie, nous allons faire un gros TP récapitulatif qui reprendra tout ce que l'on a vu à travers les différents chapitres. Il y aura donc des matrices, des textures, la caméra, etc. Je vous donnerai toutes les indications nécessaires ainsi que quelques conseils pour que votre TP se déroule dans de bonnes conditions.

Si vous vous sentez prêt, vous pouvez continuer. :)

Les consignes

Les consignes

Objectif

Ce premier TP va vous permettre de mettre en pratique toutes les notions que nous avons abordées au cours de cette première partie. L'objectif est de faire une petite scène 3D dans laquelle vous pourrez vous balader à l'aide de votre caméra mobile.

Bien entendu, c'est un projet simple, ne vous attendez pas à avoir de la physique ou des effets avancés. Nous ne sommes qu'à la première partie, nous faisons juste un petit programmer pour débuter. :)

Je vais vous donner les consignes et des petits conseils pour bien démarrer ce TP. Mais avant tout, voyons ensemble à quoi devra ressembler votre rendu final :

Vous voyez ici la présence d'une cabane ainsi que de trois caisses à l'intérieur. Sur l'une d'elles se trouve d'ailleurs une petite relique en forme de cristal (lancer musique de Tomb Raider ici) qui tourne sur elle-même un peu à la manière du cube dans le chapitre sur la troisième dimension.

Vous devez donc recréer cette scène en incluant tous les éléments suivants :

  • Une cabane placée au centre (0, 0, 0)

  • Plusieurs caisses

  • Un cristal

  • Un sol herbeux entourant toute la scène

  • Un sol terreux à l'intérieur de la cabane

Les dimensions

Afin d'avoir un rendu similaire à celui des images précédentes, je vais vous donner toutes les dimensions nécessaires. Vous n'aurez pas à vous prendre la tête pour les définir vous-même. Je vous donnerai également la répétition de texture à utiliser. Au cas où vous n'auriez pas le gros pack d'images donné dans le chapitre 10, je vous fournirai celles dont vous aurez besoin à la fin des explications.

  • On commence avec les données relatives à la cabane, et plus précisément à ses murs :

  • Puis celles des combles (partie triangulaire située au fond de la cabane) :

  • Et enfin, celles du toit (vu de haut) :

Les deux vertices jaunes sont de cette couleur uniquement pour mieux les voir sur la texture. Ils n'ont rien de spécial.

Aucune des parties de la cabane n'a besoin de la répétition de texture. Vos coordonnées ne doivent donc pas dépasser de l'intervalle [0; 1];

En ce qui concerne les dimensions du sol, on a :

  • Le sol herbeux :

Cette fois, votre sol aura besoin de répéter sa texture. Vous devrez le faire 225 fois (15 fois en longueur et en largeur). Ne vous inquiétez pas, l'image n'est chargée qu'une seule fois comme nous l'avons vu. Il n'y a donc rien de dangereux pour votre carte graphique.

  • Le sol terreux :

Même chose pour la texture, vous devrez la répéter. Faites le 25 fois ici (5 fois en longueur et en largeur).

Au niveau des caisses, je pense que vous n'avez pas besoin de savoir grand chose. :p Il en faut juste 3 avec un coté de 2.0 unités et les textures Caisse.jpg et Caisse2.jpg.

Enfin, en ce qui concerne le cristal, vous aurez besoin des dimensions suivantes :

Il s'agit d'une double pyramide à 4 cotés, il y a donc 8 triangles à créer.

Quelques conseils

Maintenant que nous avons vu ensemble les consignes, je vais vous donner quelques conseils pour vous aider dans votre travail. :)

Les classes

Premièrement, au niveau même de la base de votre projet, je vous conseille de faire différentes classes pour gérer les modèles que vous devez afficher. C'est-à-dire qu'il vaut mieux faire une classe pour la cabane, une autre pour le cristal, etc. Prenez exemple sur le Cube que nous avons codé ensemble mais en intégrant en plus la gestion des textures. Ne vous compliquez pas la tâche en faisant de l’héritage cette fois.

D'ailleurs, vous remarquerez que toutes vos classes contiendront les mêmes attributs : les vertices, les coordonnées de texture, un shader et une ou plusieurs textures. La méthode afficher() sera également identique, seuls les paramètres de la fonction glDrawArrays() vont varier.

La cabane

Au niveau de la cabane, je vous conseille de ne faire qu'une seule et unique classe. Celle-ci contiendrait toutes les données relatives aux murs, au toit et aux combles. Organisez votre tableau de vertices de cette façon pour avoir un code propre :

float verticesTmp[] = {1, 0, 1,   1, 0, 1,   1, 0, 1,      // Mur du Fond

               1, 0, 1,   1, 0, 1,   1, 0, 1,      // Mur du Fond

               1, 0, 1,   1, 0, 1,   1, 0, 1,      // Mur Gauche

               1, 0, 1,   1, 0, 1,   1, 0, 1,      // Mur Gauche

               ....

               1, 0, 1,   1, 0, 1,   1, 0, 1,      // Toit Gauche

               1, 0, 1,   1, 0, 1,   1, 0, 1};     // Toit Droit

Même chose pour le tableau de coordonnées de texture :

float coordTexture[] = {0, 0,   0, 0,   0, 0,        // Mur du Fond

            0, 0,   0, 0,   0, 0,        // Mur du Fond

            0, 0,   0, 0,   0, 0,        // Mur Gauche

            0, 0,   0, 0,   0, 0,        // Mur Gauche

            ....

            0, 0,   0, 0,   0, 0,        // Toit Gauche

            0, 0,   0, 0,   0, 0};       // Toit Droit

Pour éviter d'avoir à chercher l'endroit où se situe votre erreur, testez vos vertices triangle par triangle au lieu de tout faire d'un coup. Croyez-moi, ça va vous faire gagner du temps.

Autre point important pour la cabane, il y a deux textures à gérer pour un seul tableau de vertices. Vous devrez jouer avec les paramètres de la fonction glDrawArrays() pour verrouiller vos textures au bon moment.
Par exemple (et je dis bien par exemple), si vos 20 premiers vertices concernent la texture du mur, alors vous devrez faire comme ceci :

// Verrouillage de la texture du mur

glBindTexture(GL_TEXTURE_2D, m_textureMur.getID());

// Affichage des murs (20 premiers vertices)

glDrawArrays(GL_TRIANGLES, 0, 20);

// Déverrouillage de la texture

glBindTexture(GL_TEXTURE_2D, 0);

Et si les 10 vertices suivants concernent la texture du toit, alors vous devrez verrouiller la seconde texture et recommencer l'affichage en partant du 21ième vertex :

// Verrouillage de la texture du toit

glBindTexture(GL_TEXTURE_2D, m_textureToit.getID());

// Affichage du toit (10 vertices suivants)

glDrawArrays(GL_TRIANGLES, 20, 10);

// Déverrouillage de la texture

glBindTexture(GL_TEXTURE_2D, 0);

Les deux verrouillages doivent se passer dans la même méthode bien sûr.

Revoyez le chapitre 3 si vous ne comprenez pas trop pourquoi il faut mettre la valeur 20 et non 21 au deuxième paramètre pour commencer l'affichage à partir du 21ième vertex. ;)

Le cristal

Le cristal ne devrait pas trop vous poser de problème, il suffit de reprendre le même principe que le cube en y ajoutant la gestion de la texture. Faites juste attention à spécifier les coordonnées de texture de cette façon pour chaque triangle :

A part ça, n'oubliez pas de l'animer en le faisant tourner sur elle-même. Essayez de faire ça doucement en incrémentant votre angle de 1 degré à chaque tour de boucle au lieu de 4. :)

Les sols

Au niveau des deux types de sol, vous pouvez parfaitement faire deux classes pour les différencier, c'est plus facile et c'est efficace. Cependant, si vous pensez que vous pouvez le faire, je vous suggère de ne faire qu'une seule et unique classe. Vous pourrez l'instancier deux fois en modifiant juste la texture et sa répétition. Un constructeur que vous pouvez utiliser (sans être obligés) est le suivant :

Sol(float longueur, float largeur, int repitionLongueur, int repitionLargeur, std::string vertexShader, std::string fragmentShader, std::string texture);

Détail 1 : La terre doit évidemment se trouver au niveau de la cabane et pas autre part.

Détail 2 : Pour éviter de vous retrouvez avec un bug de texture, faites translater le sol herbeux de 0.01 sur l'axe Y :

// Sauvegarde de la matrice

mat4 sauvegardeModelview = modelview;

    // Affichage du sol herbeux

    modelview = translate(modelview, vec3(0, 0.01, 0));

    solHerbeux.afficher(projection, modelview);

// Restauration de la matrice

modelview = sauvegardeModelview;

Vous pouvez tenter de faire sans cette translation si vous le souhaitez pour voir ce que ça donne.

A vos classes !

Bon, je pense vous en avoir assez dit, je ne vais pas vous donner tout le code non plus. Vous avez à disposition toutes les données dont vous avez besoin ainsi que quelques conseils pour commencer.

C'est à vous de jouer maintenant !

Télécharger : Pack de textures à utiliser pour le TP

Correction

Stop, arrêtez là ! Rendez-moi vos codes. :p

Bon, cette phrase ne sert pas à grand chose mais ça veut au moins dire que nous pouvons passer à la correction. Je vous propose une des solutions possibles, il y avait plusieurs façons d’aborder ce TP donc ne vous inquiétez pas si vous avez fait différemment.

Nous allons nous mettre sans plus tarder dans le bain en commençant par le code de base de la boucle principale.

La boucle principale

La méthode bouclePrincipale() est celle qui va nous permettre de mettre en scène tous nos objets. Elle devra donc contenir la cabane, les caisses, ... Mais pour le moment, il y a juste les matrices, la caméra, le nettoyage et l'actualisation de la fenêtre :

void SceneOpenGL::bouclePrincipale()

{

    // Variables

    unsigned int frameRate (1000 / 50);

    Uint32 debutBoucle(0), finBoucle(0), tempsEcoule(0);

    // Matrices

    mat4 projection;

    mat4 modelview;

    projection = perspective(70.0, (double) m_largeurFenetre / m_hauteurFenetre, 1.0, 100.0);

    modelview = mat4(1.0);

    // Caméra mobile

    Camera camera(vec3(3, 4, 10), vec3(0, 2.1, 2), vec3(0, 1, 0), 0.5, 0.5);

    m_input.afficherPointeur(false);

    m_input.capturerPointeur(true);

    // Boucle principale

    while(!m_input.terminer())

    {

        // On définit le temps de début de boucle

        debutBoucle = SDL_GetTicks();

        // Gestion des évènements

        m_input.updateEvenements();

        if(m_input.getTouche(SDL_SCANCODE_ESCAPE))

            break;

        camera.deplacer(m_input);

        // Nettoyage de l'écran

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // Gestion de la caméra

        camera.lookAt(modelview);

        // Rendu

        ....

        // Actualisation de la fenêtre

        SDL_GL_SwapWindow(m_fenetre);

        // Calcul du temps écoulé

        finBoucle = SDL_GetTicks();

        tempsEcoule = finBoucle - debutBoucle;

        // Si nécessaire, on met en pause le programme

        if(tempsEcoule < frameRate)

            SDL_Delay(frameRate - tempsEcoule);

    }

}

Les objets à afficher seront ajoutés après.

La cabane

Le header

On passe maintenant aux différents modèles qui doivent être placés dans notre scène, on commence par la cabane.

La première chose avec elle est évidemment de créer une classe Cabane. Elle doit contenir tous les attributs nécessaires à l'affichage comme le tableau de vertices, celui des coordonnées de texture, le shader et les textures (une pour le toit et l'autre pour les murs) :

#ifndef DEF_CABANE

#define DEF_CABANE

// Includes OpenGL

#ifdef WIN32

#include <GL/glew.h>

#else

#define GL3_PROTOTYPES 1

#include <GL3/gl3.h>

#endif

// Includes GLM

#include <glm/glm.hpp>

#include <glm/gtx/transform.hpp>

#include <glm/gtc/type_ptr.hpp>

// Autres includes

#include "Shader.h"

#include "Texture.h"

// Classe Cabane

class Cabane

{

    public:

    Cabane(std::string const vertexShader, std::string const fragmentShader);

    ~Cabane();

    void afficher(glm::mat4 &projection, glm::mat4 &modelview);

    private:

    Shader m_shader;

    Texture m_textureMur;

    Texture m_textureToit;

    float m_vertices[99];

    float m_coordTexture[66];

};

#endif

Remarquez la méthode afficher() qui est la même que celle du Cube.

Mis à part le fait qu'il faille deux textures ici, cette architecture de classe est la même pour tous les objets présents dans ce TP. ;)

Le constructeur

Le constructeur est la méthode qui vous a probablement posés le plus de problèmes. Celui-ci doit initialiser tous les attributs et les premiers sont le shader et les deux textures :

Cabane::Cabane(std::string const vertexShader, std::string const fragmentShader) : m_shader(vertexShader, fragmentShader),

                                                                                m_textureMur("Textures/Mur.jpg"), m_textureToit("Textures/Toit.jpg")

{

    // Chargement du shader

    m_shader.charger();

    // Chargement des textures

    m_textureMur.charger();

    m_textureToit.charger();

}

L'attribut le plus compliqué à initialiser était le tableau de vertices, il fallait créer tous les triangles nécessaires à l'aide des schémas fournis dans l'énoncé. Je vous avais donné quelques conseils pour y arriver comme le fait de vérifier votre affichage triangle par triangle par exemple.

La chose à ne pas faire était de créer une classe pour chaque partie de la cabane (Murs, Toit et Combles). C'est plus compliqué de gérer toutes ces parties indépendamment plutôt que tout réunir dans un seul tableau.

En prenant votre temps, vous avez pu trouver le tableau de vertices suivant :

// Vertices temporaires

float verticesTmp[] = {-5, 0, -5,   5, 0, -5,   5, 5, -5,      // Mur du Fond

                       -5, 0, -5,   -5, 5, -5,   5, 5, -5,     // Mur du Fond

                       -5, 0, -5,   -5, 0, 5,   -5, 5, 5,      // Mur Gauche

                       -5, 0, -5,   -5, 5, -5,   -5, 5, 5,     // Mur Gauche

                       5, 0, -5,   5, 0, 5,   5, 5, 5,         // Mur Droit

                       5, 0, -5,   5, 5, -5,   5, 5, 5,        // Mur Droit

                       -5, 5, -5,   5, 5, -5,   0, 6, -5,      // Combles

                       -6, 4.8, -6,   -6, 4.8, 6,   0, 6, 6,   // Toit Gauche

                       -6, 4.8, -6,   0, 6, -6,   0, 6, 6,     // Toit Gauche

                       6, 4.8, -6,   6, 4.8, 6,   0, 6, 6,     // Toit Droit

                       6, 4.8, -6,   0, 6, -6,   0, 6, 6};     // Toit Droit

J'ai aéré le code pour que l'on puisse bien distinguer les parties de la cabane. Je vous recommande de faire ça pour vos modèles. :)

Au niveau du tableau de coordonnées de texture, il faut juste faire correspondre les triangles aux bonnes coordonnées de la même façon que nous le faisions dans les chapitres précédents :

// Coordonnées de texture temporaires

float coordTexture[] = {0, 0,   1, 0,   1, 1,        // Mur du Fond

                        0, 0,   0, 1,   1, 1,        // Mur du Fond

                        0, 0,   1, 0,   1, 1,        // Mur Gauche

                        0, 0,   0, 1,   1, 1,        // Mur Gauche

                        0, 0,   1, 0,   1, 1,        // Mur Droit

                        0, 0,   0, 1,   1, 1,        // Mur Droit

                        0, 0,   1, 0,   0.5, 0.5,    // Combles

                        0, 0,   1, 0,   1, 1,        // Toit Gauche

                        0, 0,   0, 1,   1, 1,        // Toit Gauche

                        0, 0,   1, 0,   1, 1,        // Toit Droit

                        0, 0,   0, 1,   1, 1};       // Toit Droit

Attention cependant aux combles qui ne sont représentés que par un seul triangle.

Une fois les tableaux définis, il ne manque plus qu'à copier leurs valeurs dans les attributs finaux :

// Copie des vertices

for(int i(0); i < 99; i++)

    m_vertices[i] = verticesTmp[i];

// Copie des coordonnées

for(int i(0); i < 66; i++)

    m_coordTexture[i] = coordTexture[i];

Si on résume :

Cabane::Cabane(std::string const vertexShader, std::string const fragmentShader) : m_shader(vertexShader, fragmentShader),

                                                                                m_textureMur("Textures/Mur.jpg"), m_textureToit("Textures/Toit.jpg")

{

    // Chargement du shader

    m_shader.charger();

    // Chargement des textures

    m_textureMur.charger();

    m_textureToit.charger();

    // Vertices temporaires

    float verticesTmp[] = {-5, 0, -5,   5, 0, -5,   5, 5, -5,      // Mur du Fond

                           -5, 0, -5,   -5, 5, -5,   5, 5, -5,     // Mur du Fond

                           -5, 0, -5,   -5, 0, 5,   -5, 5, 5,      // Mur Gauche

                           -5, 0, -5,   -5, 5, -5,   -5, 5, 5,     // Mur Gauche

                           5, 0, -5,   5, 0, 5,   5, 5, 5,         // Mur Droit

                           5, 0, -5,   5, 5, -5,   5, 5, 5,        // Mur Droit

                           -5, 5, -5,   5, 5, -5,   0, 6, -5,      // Combles

                           -6, 4.8, -6,   -6, 4.8, 6,   0, 6, 6,   // Toit Gauche

                           -6, 4.8, -6,   0, 6, -6,   0, 6, 6,     // Toit Gauche

                           6, 4.8, -6,   6, 4.8, 6,   0, 6, 6,     // Toit Droit

                           6, 4.8, -6,   0, 6, -6,   0, 6, 6};     // Toit Droit

    // Coordonnées de texture temporaires

    float coordTexture[] = {0, 0,   1, 0,   1, 1,        // Mur du Fond

                            0, 0,   0, 1,   1, 1,        // Mur du Fond

                            0, 0,   1, 0,   1, 1,        // Mur Gauche

                            0, 0,   0, 1,   1, 1,        // Mur Gauche

                            0, 0,   1, 0,   1, 1,        // Mur Droit

                            0, 0,   0, 1,   1, 1,        // Mur Droit

                            0, 0,   1, 0,   0.5, 0.5,    // Combles

                            0, 0,   1, 0,   1, 1,        // Toit Gauche

                            0, 0,   0, 1,   1, 1,        // Toit Gauche

                            0, 0,   1, 0,   1, 1,        // Toit Droit

                            0, 0,   0, 1,   1, 1};       // Toit Droit

    // Copie des vertices

    for(int i(0); i < 99; i++)

        m_vertices[i] = verticesTmp[i];

    // Copie des coordonnées

    for(int i(0); i < 66; i++)

        m_coordTexture[i] = coordTexture[i];

}

La méthode afficher()

En ce qui concerne la méthode afficher(), on peut reprendre celle de la classe Caisse mais en y faisant quelques modifications. En effet, il faut gérer l'affichage de deux textures ici et donc faire attention à savoir quels vertices correspondent à quelle texture.

D'après le tableau m_vertices, les 21 premiers vertices, représentés par les 63 premières cases, correspondent aux murs et aux combles qui utilisent la texture Mur.jpg. Il faut donc appeler la fonction glDrawArrays() pour afficher les 21 vertices en partant de celui d'indice 0 (le premier) :

// Verrouillage de la texture du Mur

glBindTexture(GL_TEXTURE_2D, m_textureMur.getID());

// Rendu

glDrawArrays(GL_TRIANGLES, 0, 21);

// Déverrouillage de la texture

glBindTexture(GL_TEXTURE_2D, 0);

Bien entendu, on n'oublie pas de verrouiller la texture du mur. ;)

Ensuite, il faut s'occuper du toit qui occupe les 12 derniers vertices, représentés par les 36 dernières cases du tableau. On ré-appelle donc la fonction glDrawArrays() pour afficher les 12 vertices en partant de celui d'indice 21 (le 22ième) :

// Verrouillage de la texture du Toit

glBindTexture(GL_TEXTURE_2D, m_textureToit.getID());

// Rendu

glDrawArrays(GL_TRIANGLES, 21, 12);

// Déverrouillage de la texture

glBindTexture(GL_TEXTURE_2D, 0);

N'oubliez pas de verrouiller l'autre texture maintenant.

Ce qui donne la méthode final :

void Cabane::afficher(glm::mat4 &projection, glm::mat4 &modelview)

{

    // Activation du shader

    glUseProgram(m_shader.getProgramID());

        // Envoi des vertices

        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, m_vertices);

        glEnableVertexAttribArray(0);

        // Envoi des coordonnées de texture

        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, m_coordTexture);

        glEnableVertexAttribArray(2);

        // Envoi des matrices

        glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));

        glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));

        // Verrouillage de la texture du Mur

        glBindTexture(GL_TEXTURE_2D, m_textureMur.getID());

        // Rendu

        glDrawArrays(GL_TRIANGLES, 0, 21);

        // Déverrouillage de la texture

        glBindTexture(GL_TEXTURE_2D, 0);

        // Verrouillage de la texture du Toit

        glBindTexture(GL_TEXTURE_2D, m_textureToit.getID());

        // Rendu

        glDrawArrays(GL_TRIANGLES, 21, 12);

        // Déverrouillage de la texture

        glBindTexture(GL_TEXTURE_2D, 0);

        // Désactivation des tableaux

        glDisableVertexAttribArray(2);

        glDisableVertexAttribArray(0);

    // Désactivation du shader

    glUseProgram(0);

}

Le sol

Le header

La correction du sol va être un peu spécial. En fait, tout dépend de la manière dont vous l'avez codé. Je vais considérer le fait que vous n'ayez fait qu'une seule classe pour gérer les deux types de sol (herbeux et terreux). Bien entendu, si vous en avez fait deux ça fonctionne quand même, il n'y a pas qu'une seule solution je le répète.

Commençons par le header qui ressemble quasiment trait pour trait à celui de la classe Cabane

#ifndef DEF_SOL

#define DEF_SOL

// Includes OpenGL

#ifdef WIN32

#include <GL/glew.h>

#else

#define GL3_PROTOTYPES 1

#include <GL3/gl3.h>

#endif

// Includes GLM

#include <glm/glm.hpp>

#include <glm/gtx/transform.hpp>

#include <glm/gtc/type_ptr.hpp>

// Autres includes

#include "Shader.h"

#include "Texture.h"

// Classe Sol

class Sol

{

    public:

    Sol(float longueur, float largeur, int repetitionLongueur, int repetitionLargeur, std::string const vertexShader, std::string const fragmentShader, std::string const texture);

    ~Sol();

    void afficher(glm::mat4 &projection, glm::mat4 &modelview);

    private:

    Shader m_shader;

    Texture m_texture;

    float m_vertices[18];

    float m_coordTexture[12];

};

#endif

La seule différence par rapport au précédent est le fait que nous n'avons besoin qu'une d'une seule texture ici.

Le constructeur

Si vous n'avez utilisé qu'une seule classe pour le sol, vous vous êtes peut-être confrontés au problème de la répétition de texture. Je vous avais donné une petite piste avec le constructeur suivant :

Sol(float longueur, float largeur, int repetitionLongueur, int repetitionLargeur, std::string const vertexShader, std::string const fragmentShader, std::string const texture);

Celui-ci prend 7 paramètres :

  • longueur et largeur : pour définir la taille du sol

  • repitionLongueur et repitionLargeur : pour définir la répétition de la texture

  • vertexShader et fragmentShader : qui représentent les fichiers sources du shader

  • texture : qui représente le chemin vers l'image à utiliser

Je passe très rapidement sur le début du constructeur qui ne fait qu'initialiser le shader et la texture :

Sol::Sol(float longeur, float largeur, int repetitionLongueur, int repetitionLargeur, std::string const vertexShader, std::string const fragmentShader, std::string const texture) :

         m_shader(vertexShader, fragmentShader), m_texture(texture)

{

    // Chargement du shader

    m_shader.charger();

    // Chargement de la texture

    m_texture.charger();

}

Les deux premiers paramètres de la liste ci-dessus servent à initialiser le tableau de vertices en créant deux triangles. Ces derniers formeront eux-mêmes un rectangle qui représentera le sol :

// Vertices temporaires

float verticesTmp[] = {-longueur, 0, -largeur,   longueur, 0, -largeur,   longueur, 0, largeur,     // Triangle 1

                       -longueur, 0, -largeur,   -longueur, 0, largeur,   longueur, 0, largeur};    // Triangle 2

Le petit piège ici serait de ne pas toucher aux paramètres longueur et largeur avant d'initialiser les vertices. En effet, si on ne le fait pas, le sol sera doublé :

Pour éviter cela, il faut diviser par 2 les paramètres longueur et largeur avant de les utiliser dans le tableau :

Nous avons fait la même chose dans la classe Cube pour éviter de doubler la taille de nos modèles.

// Division de la taille

longueur /= 2.0;

largeur /= 2.0;

// Vertices temporaires

float verticesTmp[] = {-longueur, 0, -largeur,   longueur, 0, -largeur,   longueur, 0, largeur,     // Triangle 1

                       -longueur, 0, -largeur,   -longueur, 0, largeur,   longueur, 0, largeur};    // Triangle 2

A noter que si vos vertices partent d'un coin, comme le coin inférieur gauche par exemple, alors vous ne devez pas faire ces divisions :

En ce qui concerne le tableau de coordonnées de texture, il suffit de reprendre celui que nous avons utilisé pour les carrés dans les chapitres précédents et d'y remplacer les valeurs 1 par les paramètres repitionLongueur et repitionLargeur :

// Coordonnées de texture temporaires

float coordTexture[] = {0, 0,   repetitionLongueur, 0,   repetitionLongueur, repetitionLargeur,      // Triangle 1

                        0, 0,   0, repetitionLargeur,   repetitionLongueur, repetitionLargeur};      // Triangle 2

Une fois les tableaux définis, on copie leurs valeurs dans les attributs :

// Copie des vertices

for(int i(0); i < 18; i++)

    m_vertices[i] = verticesTmp[i];

// Copie des coordonnées

for(int i(0); i < 12; i++)

    m_coordTexture[i] = coordTexture[i];

Ce qui donne :

Sol::Sol(float longueur, float largeur, int repetitionLongueur, int repetitionLargeur, std::string const vertexShader, std::string const fragmentShader, std::string const texture) :

         m_shader(vertexShader, fragmentShader), m_texture(texture)

{

    // Chargement du shader

    m_shader.charger();

    // Chargement de la texture

    m_texture.charger();

    // Division de la taille

    longueur /= 2.0;

    largeur /= 2.0;

    // Vertices temporaires

    float verticesTmp[] = {-longueur, 0, -largeur,   longueur, 0, -largeur,   longueur, 0, largeur,     // Triangle 1

                           -longueur, 0, -largeur,   -longueur, 0, largeur,   longueur, 0, largeur};    // Triangle 2

    // Coordonnées de texture temporaires

    float coordTexture[] = {0, 0,   repetitionLongueur, 0,   repetitionLongueur, repetitionLargeur,      // Triangle 1

                            0, 0,   0, repetitionLargeur,   repetitionLongueur, repetitionLargeur};      // Triangle 2

    // Copie des vertices

    for(int i(0); i < 18; i++)

        m_vertices[i] = verticesTmp[i];

    // Copie des coordonnées

    for(int i(0); i < 12; i++)

        m_coordTexture[i] = coordTexture[i];

}

La méthode afficher()

Pour l'affichage, il suffit juste de reprendre la méthode afficher() de la classe Caisse, et non Cube cette fois-ci, et d'y modifier le nombre de vertices à afficher. Il y a 2 triangles dans notre cas, il en faut donc 6 :

void Sol::afficher(glm::mat4 &projection, glm::mat4 &modelview)

{

    // Activation du shader

    glUseProgram(m_shader.getProgramID());

        // Envoi des vertices

        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, m_vertices);

        glEnableVertexAttribArray(0);

        // Envoi des coordonnées de texture

        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, m_coordTexture);

        glEnableVertexAttribArray(2);

        // Envoi des matrices

        glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));

        glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));

        // Verrouillage de la texture

        glBindTexture(GL_TEXTURE_2D, m_texture.getID());

        // Rendu

        glDrawArrays(GL_TRIANGLES, 0, 6);

        // Déverrouillage de la texture

        glBindTexture(GL_TEXTURE_2D, 0);

        // Désactivation des tableaux

        glDisableVertexAttribArray(2);

        glDisableVertexAttribArray(0);

    // Désactivation du shader

    glUseProgram(0);

}

Le cristal

Le header

Le cristal est le modèle le plus simple du TP. Il suffit de reprendre le même principe que la classe Cube en ajoutant la gestion de texture. Voici le header :

#ifndef DEF_CRISTAL

#define DEF_CRISTAL

// Includes OpenGL

#ifdef WIN32

#include <GL/glew.h>

#else

#define GL3_PROTOTYPES 1

#include <GL3/gl3.h>

#endif

// Includes GLM

#include <glm/glm.hpp>

#include <glm/gtx/transform.hpp>

#include <glm/gtc/type_ptr.hpp>

// Autres includes

#include "Shader.h"

#include "Texture.h"

// Classe Cristal

class Cristal

{

    public:

    Cristal(std::string const vertexShader, std::string const fragmentShader, std::string const texture);

    Cristal();

    void afficher(glm::mat4 &projection, glm::mat4 &modelview);

    private:

    Shader m_shader;

    Texture m_texture;

    float m_vertices[72];

    float m_coordTexture[48];

};

#endif

Le constructeur

Le tableau de vertices :

// Vertices temporaires

float verticesTmp[] = {-0.5, 0, -0.5,   0.5, 0, -0.5,   0, 1, 0,      // Triangle 1

                       0.5, 0, -0.5,   0.5, 0, 0.5,  0, 1, 0,         // Triangle 2

                       0.5, 0, 0.5,   -0.5, 0, 0.5,   0, 1, 0,        // Triangle 3

                       -0.5, 0, 0.5,   -0.5, 0, -0.5,   0, 1, 0,      // Triangle 4

                       -0.5, 0, -0.5,   0.5, 0, -0.5,   0, -1, 0,     // Triangle 5

                       0.5, 0, -0.5,   0.5, 0, 0.5,  0, -1, 0,        // Triangle 6

                       0.5, 0, 0.5,   -0.5, 0, 0.5,   0, -1, 0,       // Triangle 7

                       -0.5, 0, 0.5,   -0.5, 0, -0.5,   0, -1, 0};    // Triangle 8

Et le tableau de coordonnées de texture :

// Coordonnées de texture temporaires

float coordTexture[] = {0, 0,   0.5, 0,   0.5, 0.5,      // Triangle 1

                        0, 0,   0.5, 0,   0.5, 0.5,      // Triangle 2

                        0, 0,   0.5, 0,   0.5, 0.5,      // Triangle 3

                        0, 0,   0.5, 0,   0.5, 0.5,      // Triangle 4

                        0, 0,   0.5, 0,   0.5, 0.5,      // Triangle 5

                        0, 0,   0.5, 0,   0.5, 0.5,      // Triangle 6

                        0, 0,   0.5, 0,   0.5, 0.5,      // Triangle 7

                        0, 0,   0.5, 0,   0.5, 0.5};     // Triangle 8

Ce qui donne le constructeur :

Cristal::Cristal(std::string const vertexShader, std::string const fragmentShader, std::string const texture) : m_shader(vertexShader, fragmentShader), m_texture(texture)

{

    // Chargement du shader

    m_shader.charger();

    // Chargement de la texture

    m_texture.charger();

    // Vertices temporaires

    float verticesTmp[] = {-0.5, 0, -0.5,   0.5, 0, -0.5,   0, 1, 0,      // Triangle 1

                           0.5, 0, -0.5,   0.5, 0, 0.5,  0, 1, 0,         // Triangle 2

                           0.5, 0, 0.5,   -0.5, 0, 0.5,   0, 1, 0,        // Triangle 3

                           -0.5, 0, 0.5,   -0.5, 0, -0.5,   0, 1, 0,      // Triangle 4

                           -0.5, 0, -0.5,   0.5, 0, -0.5,   0, -1, 0,     // Triangle 5

                           0.5, 0, -0.5,   0.5, 0, 0.5,  0, -1, 0,        // Triangle 6

                           0.5, 0, 0.5,   -0.5, 0, 0.5,   0, -1, 0,       // Triangle 7

                           -0.5, 0, 0.5,   -0.5, 0, -0.5,   0, -1, 0};    // Triangle 8

    // Coordonnées de texture temporaires

    float coordTexture[] = {0, 0,   0.5, 0,   0.5, 0.5,      // Triangle 1

                            0, 0,   0.5, 0,   0.5, 0.5,      // Triangle 2

                            0, 0,   0.5, 0,   0.5, 0.5,      // Triangle 3

                            0, 0,   0.5, 0,   0.5, 0.5,      // Triangle 4

                            0, 0,   0.5, 0,   0.5, 0.5,      // Triangle 5

                            0, 0,   0.5, 0,   0.5, 0.5,      // Triangle 6

                            0, 0,   0.5, 0,   0.5, 0.5,      // Triangle 7

                            0, 0,   0.5, 0,   0.5, 0.5};     // Triangle 8

    // Copie des vertices

    for(int i(0); i < 72; i++)

        m_vertices[i] = verticesTmp[i];

    // Copie des coordonnées

    for(int i(0); i < 48; i++)

        m_coordTexture[i] = coordTexture[i];

}

La méthode afficher()

Le reste de la classe est identique à celle du sol. La seule différence concerne la fonction glDrawArrays() qui doit afficher 24 vertices et non 6 :

void Cristal::afficher(glm::mat4 &projection, glm::mat4 &modelview)

{

    // Activation du shader

    glUseProgram(m_shader.getProgramID());

        // Envoi des vertices

        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, m_vertices);

        glEnableVertexAttribArray(0);

        // Envoi des coordonnées de texture

        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, m_coordTexture);

        glEnableVertexAttribArray(2);

        // Envoi des matrices

        glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));

        glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));

        // Verrouillage de la texture

        glBindTexture(GL_TEXTURE_2D, m_texture.getID());

        // Rendu

        glDrawArrays(GL_TRIANGLES, 0, 24);

        // Déverrouillage de la texture

        glBindTexture(GL_TEXTURE_2D, 0);

        // Désactivation des tableaux

        glDisableVertexAttribArray(2);

        glDisableVertexAttribArray(0);

    // Désactivation du shader

    glUseProgram(0);

}

Afficher le tout

Maintenant que nous avons toutes nos classes, il ne nous reste plus qu'à les instancier pour remplir notre scène. Celle-ci doit contenir une cabane, deux types de sol, un cristal et quelques caisses :

// Cabanne

Cabane cabane("Shaders/texture.vert", "Shaders/texture.frag");

// Sols

Sol solHerbeux(30.0, 30.0, 15, 15, "Shaders/texture.vert", "Shaders/texture.frag", "Textures/Herbe.jpg");

Sol solTerreux(10.0, 10.0, 5, 5, "Shaders/texture.vert", "Shaders/texture.frag", "Textures/Sol.jpg");

// Caisses

Caisse caisse(2.0, "Shaders/texture.vert", "Shaders/texture.frag", "Textures/Caisse2.jpg");

Caisse caisseDanger(2.0, "Shaders/texture.vert", "Shaders/texture.frag", "Textures/Caisse.jpg");

// Cristal

Cristal cristal("Shaders/texture.vert", "Shaders/texture.frag", "Textures/Cristal.tga");

float angle(0.0);

Si vous souhaitez inclure plusieurs caisses ayant la même texture alors vous n'avez pas besoin d'en déclarer plusieurs, une seule suffit. Vous pouvez ensuite appeler sa méthode afficher() plusieurs fois.

Au niveau de l'affichage, il suffit d'appeler la méthode afficher() pour tous les objets. On commence avec la cabane et le sol :

// Affichage de la cabane

cabane.afficher(projection, modelview);

// Affichage du sol terreux

solTerreux.afficher(projection, modelview);

// Affichage du sol herbeux

mat4 sauvegardeModelview = modelview;

    modelview = translate(modelview, vec3(0, -0.01, 0));

    solHerbeux.afficher(projection, modelview);

modelview = sauvegardeModelview;

Il y a deux détails qui doivent retenir notre attention ici :

  • La translation de 0.01 sur l'axe Y qui permet d'éviter un gros bug de texture.

  • Les objets cabane et solTerreux qui ne sont pas encadrés une sauvegarde de la matrice. Vu que nous n'utilisons aucune transformation pour les afficher, nous n'en avons pas besoin.

Il ne manque plus qu'à placer les caisses et le cristal à l'aide de différentes translations. On commence avec les caisses sans oublier de sauvegarder et restaurer la matrice modelview. D'ailleurs, il n'y a pas besoin de redéclarer la sauvegarde vu qu'elle l'a déjà été avant. ;)

// Sauvegarde de la matrice

sauvegardeModelview = modelview;

    // Première caisse

    modelview = translate(modelview, vec3(-2.5, 1, -3));

    caisse.afficher(projection, modelview);

    // Deuxième caisse

    modelview = translate(modelview, vec3(5, 0, 1));

    caisseDanger.afficher(projection, modelview);

    // Troisième caisse

    modelview = translate(modelview, vec3(-2.5, 0, 4));

    caisse.afficher(projection, modelview);

// Restauration de la matrice

modelview = sauvegardeModelview;

Vu que les caisses sont assez proches, nous pouvons nous permettre de ne sauvegarder et de ne restaurer la matrice modelview qu'une seule fois.

On termine l'affichage par le fameux cristal qui doit pivoter sur lui-même indéfiniment. On utilise pour cela la même technique que celle du cube dans le chapitre sur la 3D, à savoir l'utilisation d'un angle incrémenté de 1 à chaque tour de boucle. Si cet angle dépasse 360° alors on lui soustrait justement 360° :

// Sauvegarde de la matrice

sauvegardeModelview = modelview;

    // Affichage des caisses

    ....

    // Rotation du cristal

    angle++;

    if(angle > 360)

        angle -= 360;

    // Affichage du cristal

    modelview = translate(modelview, vec3(0, 2.1, 0));

    modelview = rotate(modelview, angle, vec3(0, 1, 0));

    cristal.afficher(projection, modelview);

// Restauration de la matrice

modelview = sauvegardeModelview;

Faites attention à placer ce bout de code dans le bloc "sauvegarde/restauration de la matrice" des caisses, ne refaites pas de sauvegarde juste pour afficher le cristal. ;)

Téléchargement

Comme toute fin de chapitre, je vous propose de télécharger une archive contenant le code que nous avons fait. Ou plutôt que vous avez fait dans ce cas. :p

Télécharger (Windows - UNIX/Linux) : Correction du TP - Une relique retrouvée

Nous venons de terminer notre premier TP, celui-ci nous a permis d'utiliser toutes les notions que avons vues sur OpenGL. :D

Je vous conseille de mettre de coté le code que vous avez réalisé vous-même, le TP de la deuxième partie sera basé dessus. Bien évidemment, nous y rajouterons une multitude de nouveautés, nous les verrons toutes ensemble.

Je vous invite d'ailleurs à lire la deuxième partie de ce tutoriel qui sera consacrée aux notions avancées d'OpenGL. Je vous parlerai enfin des shaders en détails, vous saurez tout à leur sujet depuis la classe qui leur est dédiée jusqu'aux petits fichiers sources du dossier Shaders. Nous verrons également pas mal d'autres choses qui seront principalement axées autour de la carte graphique.


Les Vertex Buffer Objects

On attaque cette deuxième partie du tutoriel avec des objets OpenGL qui vont nous être très utiles par la suite : les Vertex Buffer Objects. Ils sont très liés à la carte graphique car ils permettent d'héberger des données directement dessus (vertex, coordonnées de texture, ...).

Vu que nous avons déjà eu l'occasion de manipuler les objets OpenGL avec les textures, vous retrouverez pas mal de notions similaires, et tant mieux parce que ça nous facilite la vie. :p

C'est quoi un VBO ?

Définition

Je vous ai déjà donné une rapide définition des Vertex Buffer Objects (ou VBO) dans l'introduction mais faisons comme si vous n'avez rien vu. :p

Un Vertex Buffer Object qu'est ce c'est ?

C'est un objet OpenGL qui contient des données relatives à un modèle 3D comme les vertices, les coordonnées de texture, les normales (pour les lumières), ... Sa particularité vient du fait que les données qu'il contient se trouvent non pas dans la RAM mais directement dans la carte graphique.

Lorsque vous achetez une carte graphique, je suis sûr que la majorité d'entre vous ne regarde qu'une seule caractéristique : la quantité de mémoire RAM (ou plutôt VRAM pour Vidéo Random Access Memory). Cette mémoire se comporte exactement comme la RAM classique, elle stocke des informations qui seront utilisées plus tard par un programme.

Un Vertex Buffer Object est donc une zone mémoire (buffer) appartenant à la carte graphique dans laquelle on peut stocker des données.

Utilité

Vous vous posez peut-être la question de savoir quel intérêt peut-on avoir à stocker des données directement dans la carte graphique ?

L'intérêt n'est peut-être pas évident pour le moment car nous n'avons pas de projets d'envergure pour le moment, mais sachez que les VBO permettent de gagner beaucoup de temps calculs en évitant à OpenGL des aller et retours inutiles.

En effet, lorsque vous affichez un modèle 3D (comme une maison par exemple), vous devez spécifier ses vertices, ses coordonnées de texture, ... Toutes ces données se trouvent automatiquement dans la RAM. Or, à chaque fois que vous appelez la fonction glDrawArrays() ou glDrawElements() pour afficher votre modèle, OpenGL va chercher toutes les données correspondantes dans la RAM pour les transférer dans la mémoire graphique pour qu'ensuite seulement il puisse travailler avec :

Je pense que vous aurez compris que ces transferts ralentissent pas mal le temps de calcul, surtout quand vous transférez des centaines de milliers de données 60 fois par seconde !

Pour éviter de perdre du temps avec ces transferts, OpenGL nous propose d'utiliser les Vertex Buffer Objects pour accéder directement à la mémoire vidéo. Au moment du chargement d'un modèle, on envoie toutes les données au VBO qui va se contenter de les stocker dans la carte graphique. Il n'y a donc plus qu'un seul transfert :

On économise ainsi le transfert de centaines de milliers de données à chaque affichage, la carte graphique peut travailler plus rapidement.

Attention cependant, on conserve tout de même une copie des données dans la RAM. Elles ne sont plus copiées à chaque affichage mais elles sont quand même là. On doit les converser pour pouvoir effectuer des calculs dessus, comme un déplacement de personnage par exemple.

D'ailleurs, lorsqu'on les mettra à jour il faudra les re-transférer dans le VBO, mais ça ce sera pour la fin du chapitre. :p

Création

Le header

Introduction

On sait maintenant ce que sont les Vertex Buffer Objects, on peut donc passer à leur implémentation. Mais avant tout je voudrais faire une petite comparaison avec les textures.

Souvenez-vous que les textures sont des objets OpenGL eux-aussi et que les fonctions qui permettent de les gérer sont quasiment les mêmes pour tous les objets. Ainsi, on retrouvera des fonctions identiques aux textures pour gérer les VBO comme glGenXXX(), glBindXXX() et glDeleteXXX(). De plus, on retrouvera également les variables de type GLuint qui les représentera et qu'on utilisera notamment dans le verrouillage.

Dans ça chapitre, nous travaillerons sur la classe Cube en intégrant un Vertex Buffer Objet pour l'afficher. Nous ferons de même par la suite pour la classe Caisse en prenant en compte l'héritage C++. ;)

Nouvel attribut

Pour commencer, nous allons ajouter à notre liste d'attributs une nouvelle variable de type GLuint que nous appellerons m_vboID et qui représentera l'identifiant du VBO. Nous en aurons besoin pour sa configuration et son utilisation.

#ifndef DEF_CUBE

#define DEF_CUBE

// Includes

....

// Classe Cube

class Cube

{

    public:

    Cube(float taille, std::string const vertexShader, std::string const fragmentShader);

    ~Cube();

    void afficher(glm::mat4 &projection, glm::mat4 &modelview);

    protected:

    Shader m_shader;

    float m_vertices[108];

    float m_couleurs[108];

    GLuint m_vboID;

};

#endif

Comme tout nouvel attribut, il faut penser à l'initialiser dans le constructeur. Nous lui donnerons la valeur 0 vu qu'il est vide pour le moment :

Cube::Cube(float taille, std::string const vertexShader, std::string const fragmentShader) : m_shader(vertexShader, fragmentShader), m_vboID(0)

{

    // Initialisation

    ....

}

La méthode charger()

Génération de l'identifiant

Nous n'allons pas gérer la création du VBO dans le constructeur car cela poserait problème avec l'héritage dans les classes filles. A la place, nous allons créer une méthode à part que nous appellerons simplement charger() comme pour les textures et le shader. Son prototype ne sera pas compliqué vu qu'il n'y aura aucun paramètre à gérer :

void charger();

Ne l'appelez pas chargerVBO() car nous mettrons autre chose à l'intérieur dans le chapitre suivant. ;)

Pour en revenir à notre nouvel attribut, rappelez-vous que les identifiants (ou ID) sont créés grâce à la fonction glGenXXX() et représentent l'objet OpenGL auquel ils sont rattachés. Pour créer celui des textures par exemple, nous utilisions la fonction glGenTextures().

Pour les VBO, nous utiliserons la même fonction sauf que l'on remplacera le mot Texture par Buffer (ou zone mémoire). La fonction à utiliser devient donc glGenBuffers() :

GLuint glGenBuffers(GLsizei number, GLuint *buffers);

  • number : Le nombre d'ID à initialiser. Nous lui donnerons toujours la valeur 1 comme pour les textures

  • buffers : Un tableau de type GLuint. On peut également mettre l'adresse d'une variable GLuint pour n'initialiser qu'un seul ID

Donc pour générer un nouvel ID, nous devons appeler cette fonction en donnant en paramètre 1 (pour n'en créer qu'un seul) ainsi que l'adresse de l'attribut m_vboID pour récupérer la valeur retournée :

void Cube::charger()

{

    // Génération de l'ID

    glGenBuffers(1, &m_vboID);

}
Le verrouillage

Si je vous parle de la notion de verrouillage, vous vous souvenez de ce que ce que ça signifie ? ;)

A chaque fois que l'on veut configurer ou utiliser un objet OpenGL il faut le verrouiller car OpenGL a justement besoin de savoir sur quelle chose il doit travailler.
Pour configurer les textures par exemple, il fallait les verrouiller avant de leur donner les pixels d'une image. Si nous ne l'avions pas fait, nous aurions envoyé les pixels on ne sait où dans la mémoire.

Avec les VBO c'est la même chose, avant de les configurer ou de les utiliser il faut les verrouiller.

La fonction permettant de faire cette opération s'appelle glBindXXX(), avec les VBO ce sera glBindBuffer().

void glBindBuffer(GLenum target, GLuint buffer);

  • target : type de l'objet que l'on veut verrouiller. Comme d'habitude, à chaque fois que l'on verra ce paramètre on lui donnera toujours la même valeur, et dans notre cas ce sera GL_ARRAY_BUFFER

  • buffer : ID qui représente le VBO. On lui donnera la valeur de l'ID et pas son adresse cette fois. ;)

On appelle donc cette fonction en donnant en paramètre GL_ARRAY_BUFFER pour target et l'attribut m_vboID pour buffer :

void Cube::charger()

{

    // Génération de l'ID

    glGenBuffers(1, &m_vboID);

    // Verrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, m_vboID);

}

Je profite également de cette partie pour vous montrer le déverrouillage d'objet. Vous savez qu'une fois que nous n'utilisons plus nos objets il faut les déverrouiller, pour éviter d'écrire par erreur dedans par exemple.

Pour déverrouiller un VBO, il suffit d'appeler la fonction glBindbuffer() avec un ID de 0 (valeur nulle).

void Cube::charger()

{

    // Génération de l'ID

    glGenBuffers(1, &m_vboID);

    // Verrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, m_vboID);

        // Configuration

        ....

    // Déverrouillage de l'objet

    glBindBuffer(GL_ARRAY_BUFFER, 0);

}

Je vous conseille d'indenter le code qui se trouve entre les appels à glBindBuffer() de la même manière que glUseProgram(), ça vous permettra de mieux voir ce que vous lui envoyez.

Allocation de la mémoire vidéo (Partie 1/2)

Maintenant que nous avons un VBO créé et verrouillé, nous allons pouvoir lui transférer les données stockées dans la RAM. Les transferts seront un peu déroutants car nous ne transférons pas directement des float ou des unsigned int mais des bytes.

Mais avant cela, il va falloir définir une zone mémoire à l'intérieur-même de la carte graphique. Les VBO se comportent un peu comme l'allocation dynamique, il faut allouer dynamiquement de la place dans la mémoire en fonction des données à transférer.

Pour allouer de la mémoire normale (RAM), nous devons utiliser le mot-clef new[] avec la taille désirée. Dans notre cas, il va falloir passer par une fonction dédiée qui s'appelle glBufferData() :

glBufferData(GLenum target, GLsizeiptr size, const GLvoid *data, GLenum  usage)

  • target : On ne le présente plus, on lui donnera la valeur GL_BUFFER_DATA

  • size : C'est la taille mémoire à allouer (en bytes), il faut prendre en compte TOUTES les données à envoyer (vertices, coordonnées de texture, ...) correspondant à un modèle 3D

  • data : Ce sont les données à transférer. On n'enverra rien du tout pour le moment car on ne peut pas envoyer plusieurs tableaux en un seul paramètre, nous lui affecterons la valeur 0

  • usage : Permet de définir la fréquence de mise à jour des données

Le deuxième et le quatrième paramètres sont les plus importants, nous allons passer un peu plus de temps dessus.

Le paramètre size est un peu particulier, il correspond à la taille de la mémoire à allouer en bytes (ou octets). Pourquoi en bytes ? Parce que les variables primitives ne font pas forcément la même taille dans la RAM que dans la VRAM, une variable de type float par exemple peut prendre plus de place dans l'une et moins dans l'autre.

Pour transférer des variables correctement, il faut trouver un moyen de communication commun entre les deux mémoires, et ce moyen c'est le byte.
Il nous faut donc demander à la carte graphique une zone mémoire exprimée en bytes, elle devra être assez grande pour pouvoir contenir toutes les données. Dans la classe Cube, nous avons un tableau pour les vertices et un autre pour les couleurs. La zone devra donc faire la taille de ces deux-la.

Pour avoir la taille d'un tableau, il suffit de multiplier le nombre de ses cases par la taille en bytes du type de variable utilisée. Je vous rappelle que la fonction permettant d'avoir la taille d'un type de variable s'appelle sizeof(type).

Pour calculer la taille des vertices par exemple, nous savons que nous en avons 108 donc nous faisons 108 * sizeof(float) :

// Taille du tableau de vertices

int tailleVerticesBytes = 108 * sizeof(float);

Pour le tableau de couleurs, on fait exactement le même calcul. Nous avons 108 cases donc nous faisons 108 * sizeof(float) :

// Taille du tableau de couleurs

int tailleCouleursBytes = 108 * sizeof(float);

La taille de la zone mémoire à allouer dans la carte graphique vaudra donc tailleVerticesBytes + tailleCouleursBytes. :)

De nouveaux attributs

Je profite de cette partie pour faire une petite parenthèse. Nous aurons besoin des attributs précédents plusieurs fois dans la classe Cube (et ses filles). Il vaut donc mieux créer des attributs les représentant plutôt que de les redéclarer à la main à chaque fois.

Rajoutons donc deux nouvelles variables m_tailleVerticesBytes et m_tailleCouleursBytes dans le header :

#ifndef DEF_CUBE

#define DEF_CUBE

// Includes

....

// Classe Cube

class Cube

{

    public:

    Cube(float taille, std::string const vertexShader, std::string const fragmentShader);

    ~Cube();

    void afficher(glm::mat4 &projection, glm::mat4 &modelview);

    protected:

    Shader m_shader;

    float m_vertices[108];

    float m_couleurs[108];

    GLuint m_vboID;

    int m_tailleVerticesBytes;

    int m_tailleCouleursBytes;

};

#endif

Initialisons-les ensuite dans le constructeur en leur donnant la taille des vertices et des couleurs en bytes :

Cube::Cube(float taille, std::string const vertexShader, std::string const fragmentShader) : m_shader(vertexShader, fragmentShader), m_vboID(0),

                                                                                m_tailleVerticesBytes(108 * sizeof(float)),

                                                                                m_tailleCouleursBytes(108 * sizeof(float))

{

    // Initialisation

    ....

}

Le paramètre size de la fonction glBufferData() prendra donc la valeur m_tailleVerticesBytes + m_tailleCouleursBytes.

Allocation de la mémoire vidéo (Partie 2/2)

On ferme la parenthèse et on revient à l'étude des autres paramètres. Le prochain sur la liste est usage et il sera plus rapide à comprendre. :p

Il permet à OpenGL de savoir si les données que nous stockerons dans le VBO seront mises à jour rarement, fréquemment ou tout le temps.

Par exemple, un personnage qui bouge devra quasiment tout le temps mettre à jour ses vertices, une caisse en revanche ne devra jamais le faire. OpenGL à besoin de connaitre cette fréquence de mise à jour, c'est pour ça qu'il nous donne le paramètre usage.

En théorie, il existe 9 valeurs possibles pour ce paramètre mais nous n'en retiendrons que 3 :

  • GL_STATIC_DRAW : pour les données très peu mises à jour

  • GL_DYNAMIC_DRAW : pour les données mises à jour fréquemment (plusieurs fois par seconde mais pas à chaque frame)

  • GL_STREAM_DRAW : pour les données mises à jour tout le temps (A chaque frame cette fois-ci)

Vu que le cube ne bouge pas, nous mettrons la valeur GL_STATIC_DRAW. ;)

En définitif, avec tous les paramètres que l'on vient de voir, nous appellerons la fonction glBufferData() de cette façon :

void Cube::charger()

{

    // Génération de l'ID

    glGenBuffers(1, &m_vboID);

    // Verrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, m_vboID);

        // Allocation de la mémoire

        glBufferData(GL_ARRAY_BUFFER, m_tailleVerticesBytes + m_tailleCouleursBytes, 0, GL_STATIC_DRAW);

    // Déverrouillage de l'objet

    glBindBuffer(GL_ARRAY_BUFFER, 0);

}

Avec ça, on vient de communiquer directement avec la carte graphique pour lui demander si elle pouvait nous réserver un petit espace mémoire. ;)

Transfert des données

La fonction glBufferData() nous a permis d'allouer un espace mémoire pour y stocker nos données, et vous avez vu que nous n'avons transféré aucune donnée même si elle nous le proposait. Nous ne l'avons pas fait car il est impossible d'envoyer plusieurs tableaux dans un seul paramètre.

Pour transférer les données, nous allons utiliser une autre fonction OpenGL dont le nom ressemble étonnamment à celui de la fonction précédente : glBufferSubData().

glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid *data);

Petite précision au passage, cette fonction est un poil plus rapide que sa grande sœur glBufferData() pour transférer des données. Donc même si nous n'avons qu'un seul tableau à envoyer au VBO on préférera utiliser glBufferSubData().

  • target : Toujours le même, on lui donnera la valeur GL_BUFFER_DATA

  • offset : Case en mémoire où on va commencer le transfert dans la VRAM

  • size : La taille des données à copier (en bytes)

  • data : Les données à copier, par exemple le tableau de vertices

Les paramètres importants ici sont l'offset et le size.

Je commence par le paramètre size, il correspond à la taille (en bytes) des données à envoyer. Si nous envoyons le tableau de vertices, nous donnerons la taille du tableau que nous avons calculée juste avant. Même chose pour le tableau de couleurs.

Le paramètre offset correspond quant à lui à la case mémoire dans laquelle va commencer la copie. Pour le transfert des vertices, ce paramètre sera de 0 vu que l'on commence à copier au début de la zone mémoire :

En revanche pour le transfert des couleurs, on ne va pas commencer la copie à la case 0 sinon on va écraser les valeurs transférées juste avant :

Il faudra commencer la copie à la fin du tableau de vertices, soit à la case tailleVerticesBytes :

A partir de cette case, on n'écrase plus rien, on copie au bon endroit.
D'ailleurs, offset signifie décalage en français, on renseigne un décalage dans la plage mémoire. ;)

Au final, pour envoyer toutes nos données à la carte graphique nous devons appeler la fonction glBindSubData() deux fois en faisant attention à copier le bon volume de données au bon endroit.
Le premier transfert s'occupera de copier le tableau de vertices dans la mémoire vidéo à partir de la case 0 :

// Transfert des vertices

glBufferSubData(GL_ARRAY_BUFFER, 0, m_tailleVerticesBytes, m_vertices);

Le second transfert s'occupera de copier le tableau de couleurs dans la mémoire vidéo juste après les vertices, soit à partir de la case tailleVerticesBytes :

// Transfert des couleurs

glBufferSubData(GL_ARRAY_BUFFER, m_tailleVerticesBytes, m_tailleCouleursBytes, m_couleurs);

On fait un petit récapitulatif de la création d'un VBO :

void Cube::charger()

{

    // Génération de l'ID

    glGenBuffers(1, &m_vboID);

    // Verrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, m_vboID);

        // Allocation de la mémoire vidéo

        glBufferData(GL_ARRAY_BUFFER, m_tailleVerticesBytes + m_tailleCouleursBytes, 0, GL_STATIC_DRAW);

        // Transfert des données

        glBufferSubData(GL_ARRAY_BUFFER, 0, m_tailleVerticesBytes, m_vertices);

        glBufferSubData(GL_ARRAY_BUFFER, m_tailleVerticesBytes, m_tailleCouleursBytes, m_couleurs);

    // Déverrouillage de l'objet

    glBindBuffer(GL_ARRAY_BUFFER, 0);

}
Éviter les fuites de mémoire

Si vous vous souvenez de ce que l'on a vu sur le changement de texture, vous vous souviendrez que nous avons ajouté une petite ligne de code au début de la méthode charger(). En effet, je vous avais parlé de ce qui se passait si on appelait deux fois cette méthode sur un même objet : cela entrainait une fuite de mémoire car le premier chargement était perdu dans la carte graphique.

Avec les VBO, nous avons le même problème : si on en charge un deux fois alors le premier chargement effectué sera perdu et les ressources seront considérées comme étant "toujours utilisées". Pour éviter cette fuite de mémoire, nous allons faire la même chose que les textures en vérifiant d'une part si le VBO a déjà été chargé, puis en le en détruisant si c'est le cas. Le tout au début de la méthode charger() avant l'initialisation.

La fonction permettant de savoir si un VBO a déjà été chargé s'appelle glIsBuffer() :

GLboolean glIsBuffer(GLuint buffer);

Elle ne prend en paramètre que l'identifiant à vérifier et renvoie la valeur GL_TRUE si le VBO a déjà été chargé ou GL_FALSE s'il ne l'a pas été.

La fonction de destruction quant à elle s'appelle glDeleteBuffers() :

void glDeleteBuffers(GLsizei number, const GLuint *buffers);

  • number : Le nombre d'ID à initialiser. Nous lui donnerons la valeur 1

  • buffers : Un tableau de type GLuint ou l'adresse d'une variable GLuint. Nous lui donnerons l'ID du VBO à détruire.

Nous appellerons ces deux fonctions dans un bloc if au début de la méthode charger() :

void Cube::charger()

{

    // Destruction d'un éventuel ancien VBO

    if(glIsBuffer(m_vboID) == GL_TRUE)

        glDeleteBuffers(1, &m_vboID);

    // Génération de l'ID

    glGenBuffers(1, &m_vboID);

    ....

}

Ce qui donne le code source définitif :

void Cube::charger()

{

    // Destruction d'un éventuel ancien VBO

    if(glIsBuffer(m_vboID) == GL_TRUE)

        glDeleteBuffers(1, &m_vboID);

    // Génération de l'ID

    glGenBuffers(1, &m_vboID);

    // Verrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, m_vboID);

        // Allocation de la mémoire vidéo

        glBufferData(GL_ARRAY_BUFFER, m_tailleVerticesBytes + m_tailleCouleursBytes, 0, GL_STATIC_DRAW);

        // Transfert des données

        glBufferSubData(GL_ARRAY_BUFFER, 0, m_tailleVerticesBytes, m_vertices);

        glBufferSubData(GL_ARRAY_BUFFER, m_tailleVerticesBytes, m_tailleCouleursBytes, m_couleurs);

    // Déverrouillage de l'objet

    glBindBuffer(GL_ARRAY_BUFFER, 0);

}

Notre premier VBO est prêt à l'emploi. :D

Le destructeur

Encore une fois, tout comme les textures, les VBO doivent être détruits lorsque nous n'en avons plus besoin. Vu que nous sommes dans une classe, nous devons gérer cette destruction dans la méthode prévue pour ça : le destructeur.

Cette tâche se fait avec la fonction glDeleteBuffers() que nous avons vue précédemment. Nous l’appellerons dans le destructeur en lui donnant l'adresse de l'attribut m_vboID :

Cube::~Cube()

{

    // Destruction du VBO

    glDeleteBuffers(1, &m_vboID);

}

Utilisation

Utilisation d'un VBO

Maintenant que nous avons un VBO créé et initialisé, on ne va pas se priver du plaisir de l'utiliser dans nos programmes. :)

Pour utiliser un VBO on fait exactement la même chose qu'avec les textures. D'ailleurs, vous vous souvenez de ce qu'on doit faire pour envoyer une texture à OpenGL ?

....

On la verrouille !

On va faire exactement la même chose avec les VBO, pour les utiliser on va les verrouiller. Comme tous les objets OpenGL j'ai envie de dire. :p

Ok mais euh ... On le verrouille où ?

Ah très bonne question.

Jusqu'à ce chapitre, pour afficher un modèle 3D nous devions envoyer les données à l'aide de la fonction glVertexAttribPointer() puis nous affichions le tout avec la fonction glDrawArrays(). Nous continuerons à procéder ainsi sauf que nous ajouterons le verrouillage du VBO juste avant la fonction glVertexAttribPointer().

En effet même si les données se trouvent dans la mémoire vidéo, OpenGL ne sait pas où elles se situent exactement, c'est à nous de lui dire. Pour ça, on va toujours utiliser la fonction glVertexAttribPointer() sauf qu'on va modifier son dernier paramètre. Je vous redonne son prototype pour que vous visualisiez le paramètre en question :

void glVertexAttribPointer(GLuint index, GLuint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer);

Le paramètre pointer permet à OpenGL de savoir où se trouvent nos données (vertices et toute la clique je le rappelle). Avant nous donnions directement les tableaux à ce paramètre, ils étaient stockés dans la RAM ça ne posait pas de problème. Mais maintenant nous devons lui donner l'adresse des tableaux à l'intérieur de la mémoire vidéo.

Lorsque le VBO est verrouillé, OpenGL sait qu'il doit aller chercher les données dans la plage mémoire qui lui est consacrée. A partir de maintenant, le paramètre pointer n'attend plus un tableau mais un offset.

Offset ça ne vous rappelle pas un paramètre ? C'est celui de la fonction glBufferSubData(), il représente la case (le décalage en mémoire) où le transfert doit commencer.
Et bien c'est justement ce qu'attend OpenGL maintenant. Nous lui donnerons donc l'offset correspondant aux tableaux de données dans la mémoire vidéo.

Pour les vertices, cet offset sera de 0 car ils se situent au début de la zone mémoire.
Pour les couleurs, cet offset sera égal à l'attribut m_tailleVerticesBytes car elles sont situées juste après les vertices dans la zone mémoire.

L'appel à la fonction glVertexAttribPointer() devient donc :

// Verrouillage du VBO

glBindBuffer(GL_ARRAY_BUFFER, m_vboID);

    // Accès aux vertices dans la mémoire vidéo

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);

    glEnableVertexAttribArray(0);

    // Accès aux couleurs dans la mémoire vidéo

    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, m_tailleVerticesBytes);

    glEnableVertexAttribArray(1);

// Déverrouillage du VBO

glBindBuffer(GL_ARRAY_BUFFER, 0);

Au niveau de la méthode afficher(), ça donnerait ceci :

void Cube::afficher(glm::mat4 &projection, glm::mat4 &modelview)

{

    // Activation du shader

    glUseProgram(m_shader.getProgramID());

        // Verrouillage du VBO

        glBindBuffer(GL_ARRAY_BUFFER, m_vboID);

            // Accès aux vertices dans la mémoire vidéo

            glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);

            glEnableVertexAttribArray(0);

            // Accès aux couleurs dans la mémoire vidéo

            glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, m_tailleVerticesBytes);

            glEnableVertexAttribArray(1);

        // Déverrouillage du VBO

        glBindBuffer(GL_ARRAY_BUFFER, 0);

        ....

    // Désactivation du shader

    glUseProgram(0);

}

Techniquement, on ne peut pas compiler ce code car le paramètre pointer attend un décalage (représenté par une adresse et non une variable). Or nous, nous lui donnons une variable de type integer. Alors bon, pour le 0 ça passe encore mais pour tailleVerticesBytes notre compilateur va nous râler dessus. :lol:

Pour utiliser les variables tailleXXX en décalage, OpenGL nous demande de les encadrer avec la macro suivante :

// Macro utile au VBO

#ifndef BUFFER_OFFSET

    #define BUFFER_OFFSET(offset) ((char*)NULL + (offset))

#endif

Ce code permet de spécifier un décalage offset en partant de l'adresse NULL (ou 0) qui indique le début du VBO. Le mot-clef char permet simplement de faire le lien avec les bytes, je vous rappelle que les variables de type char sont codées sur 1 byte. ;)

Pensez à inclure ce code dans chaque fichier où vous utiliserez les VBO.

En utilisant cette macro, le compilateur comprend bien qu'on veut lui envoyer une adresse à l'aide d'une variable. Nous encadrons donc le paramètre pointer par BUFFER_OFFSET() :

// Verrouillage du VBO

glBindBuffer(GL_ARRAY_BUFFER, m_vboID);

    // Accès aux vertices dans la mémoire vidéo

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

    glEnableVertexAttribArray(0);

    // Accès aux couleurs dans la mémoire vidéo

    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(m_tailleVerticesBytes));

    glEnableVertexAttribArray(1);

// Déverrouillage du VBO

glBindBuffer(GL_ARRAY_BUFFER, 0);

Petit point important avant de compiler tout ça, faites attention à bien appeler la méthode charger() au moment de créer votre cube. Si vous ne le faites pas, votre application va joliment planter devant vos yeux ébahies.

// Déclaration d'un objet Cube

Cube cube(2.0, "Shaders/couleur3D.vert", "Shaders/couleur3D.frag");

cube.charger();

Cette fois, vous pouvez compiler tranquillement.

Vous avez le même rendu qu'avant sauf que maintenant OpenGL ne fait plus d'aller-retours pour chercher les données dans la RAM. Tout se trouve déjà dans la carte graphique et ça lui fait gagner du temps. :)

Utilisation de plusieurs VBO

On termine cette sous-partie avec une petite problématique ? Comment je fais si je veux utiliser plusieurs VBO pour afficher quelque chose ?

En temps normal, je vous dirais qu'il est inutile d'en utiliser plusieurs pour afficher un unique modèle, un seul fait parfaitement l'affaire. Cependant, il existe des cas où il est utile de séparer les données dans plusieurs VBO, en général ce sera pour faire de l'optimisation.

Si ça vous arrive un jour, sachez qu'il suffit simplement de verrouiller le premier VBO avant votre premier envoi de données, puis de verrouiller le deuxième avec le deuxième envoi, et ainsi de suite :

// Accès au premier VBO

glBindBuffer(GL_ARRAY_BUFFER, premierVBO);

    // Accès à la mémoire vidéo

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

    glEnableVertexAttribArray(0);

// Déverrouillage du VBO

glBindBuffer(GL_ARRAY_BUFFER, 0);

// Accès au deuxième VBO

glBindBuffer(GL_ARRAY_BUFFER, deuxiemeVBO);

    // Accès à la mémoire vidéo

    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(m_tailleVerticesBytes));

    glEnableVertexAttribArray(2);

// Déverrouillage du VBO

glBindBuffer(GL_ARRAY_BUFFER, 0);

Petit info au passage, si vous enchainez plusieurs verrouillages sur des objets OpenGL de même type (et j'insiste bien dessus) alors vous n'êtes pas obligés de tous les déverrouiller à chaque fois, ils le seront automatiquement. Seul le dernier déverrouillage est important.

On peut donc enlever le déverrouillage du premier objet dans ce cas :

// Accès au premier VBO

glBindBuffer(GL_ARRAY_BUFFER, premierVBO);

    // Accès à la mémoire vidéo

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

    glEnableVertexAttribArray(0);

// Accès au deuxième VBO

glBindBuffer(GL_ARRAY_BUFFER, deuxiemeVBO);

    // Accès à la mémoire vidéo

    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(m_tailleVerticesBytes));

    glEnableVertexAttribArray(2);

// Déverrouillage du VBO

glBindBuffer(GL_ARRAY_BUFFER, 0);

Mise à jour des données

La méthode updateVBO()

Pourquoi mettre à jour les données ?

Transférer les données dans la carte graphique c'est bien, mais les mettre à jour quand un objet bouge c'est quand même mieux. :p

Notre caisse n'en a pas vraiment besoin car elle ne bouge pas. Mais lorsque nous animerons des personnages, il faudra bien mettre à jour leurs vertices pour voir leurs déplacements.

La modification de données dans la mémoire vidéo se fait un peu comme l'écriture de texte dans un fichier.
Avec les fichiers, on commence par en ouvrir un avec un mode d'accès (lecture, écriture ou les deux), puis on écrit les données, et une fois qu'on a fini on ferme le fichier.

Avec les VBO c'est un peu la même chose, on va commencer par récupérer l'espace mémoire qu'on a allouée, puis on écrira les données à l'intérieur et enfin on fermera l'accès à la zone mémoire.
La seule véritable différence avec les fichiers c'est que les VBO il faut les verrouiller. ;)

Pour concrétiser ce qu'on va voir sur ça, nous allons coder une méthode qui nous permettra de mettre à jour nos données. Celle-ci s’appellera updateVBO() :

void updateVBO(void *donnees, int tailleBytes, int decalage);

  • donnees : Un pointeur sur les données à envoyer, comme un tableau par exemple. Il est de type void* car on ne connaitra pas forcement le type de donnée que l'on enverra

  • tailleByes : La taille en bytes des données

  • decalage : Le décalage en mémoire où commencer la copie, nous allons voir ça un peu plus bas

Le code

Pour accéder à la mémoire vidéo, nous devons utiliser une fonction qui permet de retourner un pointeur un peu spéciale. Celui-ci forme en quelque sorte une passerelle entre le VBO et la RAM. Grâce à lui, nous pourrons accéder aux données de la mémoire vidéo comme s'il s'agissait de données présentes dans la RAM.

La fonction en question s’appelle glMapBuffer().

void* glMapBuffer(GLenum target, GLenum access);

  • target : Toujours le même, on lui donne la valeur GL_ARRAY_BUFFER

  • mode : Mode d'accès aux données du VBO (lecture, écriture ou les deux)

Contrairement à ce qu'on pourrait penser, la fonction renvoie bien quelque chose. L'utilisation du type void* permet de spécifier un pointeur. Un pointeur qui, ici, forme la passerelle dont nous avons parlée à l'instant.

Le paramètre mode peut prendre 3 valeurs :

  • GL_READ_ONLY : Lecture seulement

  • GL_WRITE_ONLY : Écriture seulement

  • GL_READ_WRITE : Lecture et écriture

Dans la plupart des cas, nous utiliserons la deuxième valeur car on ne fera que transférer des données, pas besoin de lecture dans ce cas. :)

Ainsi, pour mettre à jour les informations contenues dans un VBO on commence par le verrouiller puis on récupère l'adresse de sa zone mémoire :

void Cube::updateVBO(void *donnees, int tailleBytes, int decalage)

{

    // Verrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, m_vboID);

        // Récupération de l'adresse du VBO

        void *adresseVBO = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);

    // Déverrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, 0);

}

Il peut arriver que la récupération de l'adresse échoue, ce n'est pas très courant mais ça peut arriver.

Si c'est le cas alors la fonction glMapBuffer() renvoie un pointeur NULL, il faut donc vérifier sa valeur avant de continuer. Si elle est nulle, alors on déverrouille le VBO et on annule le transfert. S'il n'y a aucune erreur on peut continuer :

void Cube::updateVBO(void *donnees, int tailleBytes, int decalage)

{

    // Verrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, m_vboID);

        // Récupération de l'adresse du VBO

        void *adresseVBO = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);

        // Si l'adresse retournée est nulle alors on arrête le transfert

        if(adresseVBO == NULL)

        {

            std::cout << "Erreur au niveau de la récupération du VBO" << std::endl;

            glBindBuffer(GL_ARRAY_BUFFER, 0);

            return;

        }

    // Déverrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, 0);

}

Une fois notre adresse trouvée et validée nous pouvons commencer le transfert, et comme d'habitude avec les VBO les transferts se font en bytes. Le seul problème ici, c'est qu'on n'a pas de fonction OpenGL pour mettre à jour nos données, il va falloir le faire par nous-même avec comme seul outil l'adresse de destination dans la mémoire vidéo.

Heureusement pour nous, nous n'aurons pas à faire ça à la main. Il existe une fonction C (oui oui C pas C++) qui permet de copier tout un tas de bytes d'un coup. Cette fonction s'appelle memcpy() :

void* memcpy(void *destination, const void *source, size_t num);

  • destination : L'adresse où on écrira les données, ici ce sera l'adresse mémoire que l'on a récupérée

  • source : La source de données, ici ce sera les tableaux de vertices et de coordonnées de texture

  • num : La taille des données à copier (en bytes), c'est la même chose que le paramètre size de la fonction glBufferSubData()

Vous devez inclure l’en-tête cstring et je dis bien cstring pour pouvoir utiliser cette fonction.

Le seul problème avec cette fonction, c'est qu'elle ne prend pas en compte le décalage dans une zone mémoire, elle n'est capable d'écrire qu'à partir du début.

Pour contourner ceci, nous allons reprendre la macro BUFFER_OFFSET() que nous utilisons pour l'affichage :

// Macro utile au VBO

#ifndef BUFFER_OFFSET

    #define BUFFER_OFFSET(offset) ((char*)NULL + (offset))

#endif

Le code qui nous intéresse ici c'est la définition de la macro :

(char*)NULL + (offset)

Ce code permet de spécifier un décalage offset à partir de l'adresse NULL. Ce que nous voulons nous, c'est spécifier un décalage à partir de l'adresse adresseVBO.

Pour ce faire, nous devons simplement remplacer le mot-clef NULL par le pointeur adresseVBO et l'offset par la variable decalage :

(char*)adresseVBO + decalage

Simple n'est-ce pas ? :)

Alors bon, nous n'allons pas recréer une macro pour ça, ce ne serait pas utile et ça alourdirait le header. A la place, nous allons juste donner ce code au paramètre destination de la fonction memcpy(). Quant aux autres, nous leur donnerons en valeur la taille des données à copier et le pointeur source.

L'appel à la fonction ressemblera au final à ceci :

// Mise à jour des données

memcpy((char*)adresseVBO + decalage, donnees, tailleBytes);

Avec ça, nous pouvons mettre à jour n'importe quel VBO.

Il ne manque plus qu'une seule chose à faire. Pour sécuriser le VBO, il faut invalider le pointeur retourné par glMapBuffer(). Si on ne le fait pas il y a un risque d'écraser les données présentes à l'intérieur, on peut se retrouver avec un magnifique bug d'affichage avec ça. :lol:

La fonction permettant d'invalider ce pointeur s'appelle glUnmapBuffer() :

GLboolean glUnmapBuffer(GLenum target);

  • target : Encore celui-là. :p On lui donnera comme d'hab la valeur GL_ARRAY_BUFFER

La fonction renvoie un GLboolean pour savoir si tout s'est bien passé.

On l'appelle juste après les transferts précédents. On en profite au passage pour affecter la valeur 0 au pointeur pour une double sécurisation :

// Annulation du pointeur

glUnmapBuffer(GL_ARRAY_BUFFER);

adresseVBO = 0;

Si on récapitule tout ça, on a un beau code de mise à jour de VBO :

void Cube::updateVBO(void *donnees, int tailleBytes, int decalage)

{

    // Verrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, m_vboID);

        // Récupération de l'adresse du VBO

        void *adresseVBO = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);

        // Si l'adresse retournée est nulle alors on arrête le transfert

        if(adresseVBO == NULL)

        {

            std::cout << "Erreur au niveau de la récupération du VBO" << std::endl;

            glBindBuffer(GL_ARRAY_BUFFER, 0);

            return;

        }

        // Mise à jour des données

        memcpy((char*)adresseVBO + decalage, donnees, tailleBytes);

        // Annulation du pointeur

        glUnmapBuffer(GL_ARRAY_BUFFER);

        adresseVBO = 0;

    // Déverrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, 0);

}

L'avantage avec cette méthode, c'est qu'elle n'aura pas besoin d'être réécrite dans les classes filles, elle est universelle. :)

D'ailleurs, comme promis au début du chapitre, nous allons maintenant passer à la gestion du VBO dans la classe fille Caisse.

Un autre exemple

Le header

Comme promis, nous nous occupons maintenant de l'implémentation des VBO dans la classe Caisse. Il n'y aura rien de nouveau à apprendre, nous connaissons déjà tout. :p Le seul point auquel il faudra faire attention est le fait qu'elle hérite déjà des attributs de sa classe mère.

Ainsi, nous ne devrons pas redéclarer une variable GLuint pour le VBO car nous héritons naturellement de celui de la classe Cube. Même chose pour la taille du tableau de vertices en bytes.

En revanche, nous devrons déclarer une variable m_tailleCoordTextureBytes car nous en aurons besoin pour définir la taille de la mémoire à allouer. En effet, nous n'enverrons pas des couleurs mais des coordonnées de texture, il faudra donc utiliser une autre variable. Nous devrons par ailleurs redéfinir la méthode charger() pour les envoyer à la place des couleurs.

Au final, on commence par déclarer, dans le header de la classe Caisse, une variable m_tailleCoordTextureBytes de type int :

#ifndef DEF_CAISSE

#define DEF_CAISSE

// Includes

....

// Classe Caisse

class Caisse : public Cube

{

    public:

    Caisse(float taille, std::string const vertexShader, std::string const fragmentShader, std::string const texture);

    ~Caisse();

    void afficher(glm::mat4 &projection, glm::mat4 &modelview);

    private:

    Texture m_texture;

    float m_coordTexture[72];

    int m_tailleCoordTextureBytes;

};

#endif

Une fois déclarée, nous l'initialisons dans le constructeur en lui donnant la taille du tableau de coordonnées de texture. Celui-ci fait 72 cases donc sa taille bytes fera 72 * sizeof(float) :

Caisse::Caisse(float taille, std::string const vertexShader,std::string const fragmentShader, std::string const texture) : Cube(taille, vertexShader, fragmentShader),

                                                                                m_texture(texture),

                                                                                m_tailleCoordTextureBytes(72 * sizeof(float))

{

    // Initialisation

    ....

}

La méthode charger()

La méthode charger() ne va pas beaucoup changer par rapport à son parent. En fait, nous avons juste à modifier les attributs utilisés pour définir la taille du VBO. Son prototype sera aussi le même :

void charger();

Pour son implémentation, nous n'allons pas nous prendre la tête et recopier simplement tout le contenu de la méthode charger() précédente. :p Il faudra évidemment faire des petites modifications ensuite mais au moins, nous aurons déjà le plus gros du code.

En copiant ce contenu, on se retrouve avec la méthode suivante :

void Caisse::charger()

{

    // Destruction d'un éventuel ancien VBO

    if(glIsBuffer(m_vboID) == GL_TRUE)

        glDeleteBuffers(1, &m_vboID);

    // Génération de l'ID

    glGenBuffers(1, &m_vboID);

    // Verrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, m_vboID);

        // Allocation de la mémoire vidéo

        glBufferData(GL_ARRAY_BUFFER, m_tailleVerticesBytes + m_tailleCouleursBytes, 0, GL_STATIC_DRAW);

        // Transfert des données

        glBufferSubData(GL_ARRAY_BUFFER, 0, m_tailleVerticesBytes, m_vertices);

        glBufferSubData(GL_ARRAY_BUFFER, m_tailleVerticesBytes, m_tailleCouleursBytes, m_couleurs);

    // Déverrouillage de l'objet

    glBindBuffer(GL_ARRAY_BUFFER, 0);

}

Ce qu'il faut modifier ici ce sont les appels aux fonctions glBufferData() et glBufferSubData().

Nous savons que la première des deux permet d'allouer une zone mémoire dans la carte graphique pour le VBO. Elle a besoin de connaitre la taille de la mémoire qu'elle doit allouer. Nous devons, ici, lui donner le résultat de l'addition de la taille des vertices et des coordonnées de texture. Nous utiliserons pour cela les attributs :

  • m_tailleVerticesBytes (qui est héritée)

  • m_tailleCoordTextureBytes (que nous avons créée il y a quelques instants)

Les autres paramètres eux ne changent pas :

// Allocation de la mémoire vidéo

glBufferData(GL_ARRAY_BUFFER, m_tailleVerticesBytes + m_tailleCoordTextureBytes, 0, GL_STATIC_DRAW);

La fonction glBufferSubData(), quant à elle, permet de les remplir le VBO avec les données que l'on veut envoyer. Nous l'appelons deux de façon à envoyer les vertices d'une part et les coordonnées de texture de l'autre.

// Transfert des coordonnées de texture

glBufferSubData(GL_ARRAY_BUFFER, m_tailleVerticesBytes, m_tailleCoordTextureBytes, m_coordTexture);

Vu que les coordonnées de texture se trouvent juste après les vertices dans la mémoire, nous devons donc laisser le paramètre offset (le deuxième de la fonction) à la case m_tailleVerticesBytes.

La méthode charger() ressemble au final à ceci :

void Caisse::charger()

{

    // Destruction d'un éventuel ancien VBO

    if(glIsBuffer(m_vboID) == GL_TRUE)

        glDeleteBuffers(1, &m_vboID);

    // Génération de l'ID

    glGenBuffers(1, &m_vboID);

    // Verrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, m_vboID);

        // Allocation de la mémoire vidéo

        glBufferData(GL_ARRAY_BUFFER, m_tailleVerticesBytes + m_tailleCoordTextureBytes, 0, GL_STATIC_DRAW);

        // Transfert des données

        glBufferSubData(GL_ARRAY_BUFFER, 0, m_tailleVerticesBytes, m_vertices);

        glBufferSubData(GL_ARRAY_BUFFER, m_tailleVerticesBytes, m_tailleCoordTextureBytes, m_coordTexture);

    // Déverrouillage de l'objet

    glBindBuffer(GL_ARRAY_BUFFER, 0);

}

N'oubliez pas d'appeler cette méthode lorsque vous souhaitez créer une caisse :

// Objet Caisse

Caisse caisse(2.0, "Shaders/texture.vert", "Shaders/texture.frag", "Textures/Caisse2.jpg");

caisse.charger();

La méthode afficher()

La méthode afficher() est plus simple à modifier que la précédente car il suffit juste de verrouiller le VBO et utiliser la macro BUFFER_OFFSET().

On commence donc par encadrer le code relatif aux tableaux Vertex Attrib par la fonction de verrouillage glBindBuffer() :

void Caisse::afficher(glm::mat4 &projection, glm::mat4 &modelview)

{

    // Activation du shader

    glUseProgram(m_shader.getProgramID());

        // Verrouillage du VBO

        glBindBuffer(GL_ARRAY_BUFFER, m_vboID);

            // Accès aux vertices dans la mémoire vidéo

            glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, m_vertices);

            glEnableVertexAttribArray(0);

            // Envoi des coordonnées de texture

            glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, m_coordTexture);

            glEnableVertexAttribArray(2);

        // Déverrouillage du VBO

        glBindBuffer(GL_ARRAY_BUFFER, 0);

        ....

    // Désactivation du shader

    glUseProgram(0);

}

Ensuite, on modifie le paramètre pointer pour utiliser la macro avec le bon décalage. Pour l'envoi des vertices, on lui donnera la valeur 0 vu qu'ils se trouvent au début de la zone mémoire :

// Accès aux vertices dans la mémoire vidéo

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

glEnableVertexAttribArray(0);

Les coordonnées de texture, quant à elles, sont situées après les vertices dans la mémoire. On peut représenter cette situation par un schéma :

Il faut donc utiliser la case "tailleVerticesBytes" représentée par l'attribut m_tailleVerticesBytes afin d'accéder aux coordonnées de texture. C'est donc cet attribut qu'il faut donner à la macro pour trouver le début des coordonnées de texture :

// Accès aux coordonnées de texture dans la mémoire vidéo

glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(m_tailleVerticesBytes));

glEnableVertexAttribArray(2);

Avec ces modifications, la méthode afficher() devient :

void Caisse::afficher(glm::mat4 &projection, glm::mat4 &modelview)

{

    // Activation du shader

    glUseProgram(m_shader.getProgramID());

        // Verrouillage du VBO

        glBindBuffer(GL_ARRAY_BUFFER, m_vboID);

            // Envoi des vertices

            glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

            glEnableVertexAttribArray(0);

            // Envoi des coordonnées de texture

            glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(m_tailleVerticesBytes));

            glEnableVertexAttribArray(2);

        // Déverrouillage du VBO

        glBindBuffer(GL_ARRAY_BUFFER, 0);

        // Envoi des matrices

        glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));

        glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));

        // Verrouillage de la texture

        glBindTexture(GL_TEXTURE_2D, m_texture.getID());

        // Rendu

        glDrawArrays(GL_TRIANGLES, 0, 36);

        // Déverrouillage de la texture

        glBindTexture(GL_TEXTURE_2D, 0);

        // Désactivation des tableaux

        glDisableVertexAttribArray(2);

        glDisableVertexAttribArray(0);

    // Désactivation du shader

    glUseProgram(0);

}

Vous pouvez compiler pour admirer votre caisse chargée directement depuis votre carte graphique. ;)

Télécharger : Code Source C++ des Vertex Buffer Objects

Dans ce chapitre, nous avons vu ce que sont les VBO et la manière dont ils fonctionnent. Ils permettent d'augmenter la vitesse de nos applications en évitant à OpenGL des transferts de données inutiles. Ils sont très utilisés dans toutes les applications un tant soit peu développées, ce qui en font donc des incontournables dans la programmation 3D. ;)

A partir de maintenant, nous utiliserons toujours les VBO dans nos codes sources. Après tout, la deuxième partie du tuto a pour but de nous aider à maitriser notre carte graphique. L'utilisation directe de la mémoire vidéo en est la première étape.


Les Vertex Array Objects

Nous avons vu dans le chapitre précédent comment utiliser directement la mémoire de notre carte graphique en y stockant les données de nos modèles 3D. Aujourd'hui, nous allons continuer sur cette lancée en stockant autre chose dans cette mémoire, ce qui permettra de faire gagner encore plus de temps à OpenGL.

Ne pensez pas que l'on maltraite notre carte graphique en la sollicitant ainsi, elle est justement faite pour ça et préfère même gérer ces informations elle-même plutôt que de devoir aller les chercher dans la RAM.

Ce chapitre sera très court car il se rapproche beaucoup du précédent, vous n'allez pas être dépaysés. :p

Encore un objet OpenGL ?

Les Vertex Array Object (ou VAO) sont des objets OpenGL relativement proches des Vertex Buffer Object (VBO) et font donc à peu près la même chose : stocker des informations sur la carte graphique.

Avec les VBO, nous facilitons la vie à OpenGL en lui évitant des transferts de données inutiles avec la RAM. Nous lui donnons toutes nos données une seule fois pendant le chargement et nous n'en parlons plus.
Avec les VAO, c'est un peu la même chose sauf que nous allons stocker des appels de fonctions.

Hein ? Ça veut dire qu'on va pouvoir stocker du code source dans la carte graphique ?

Alors oui et non. Nous n'allons pas lui envoyer directement du code, elle ne comprendrait pas et nous enverrait gentiment paitre. :-°

En fait, nous allons " enregistrer " certains appels de fonctions à l'intérieur de la carte graphique de façon à ce qu' OpenGL ne fasse pas des aller-retours répétitifs (à chaque frame donc 60 fois par seconde) avec l'application. Les appels concernés sont ceux qui s'occupent de l'envoi de données (vertices, ...) :

void Cube::afficher(glm::mat4 &projection, glm::mat4 &modelview)
{
    .....
    // Verrouillage du VBO
    glBindBuffer(GL_ARRAY_BUFFER, m_vboID);
        // Accès aux vertices dans la mémoire vidéo
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
        glEnableVertexAttribArray(0);
        // Accès aux couleurs dans la mémoire vidéo
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(m_tailleVerticesBytes));
        glEnableVertexAttribArray(1);
    // Déverrouillage du VBO
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    .....
}

Malgré l'utilisation l'utilisation des VBO, OpenGL fait toujours quelques aller-retours entre la carte graphique et l'application pour savoir où chercher les données. Certes, il n'y a plus le transfert de millions de données mais il y existe quand même des appels répétitifs qui ne font que spécifier un emplacement dans la mémoire vidéo.

Pour éviter ça, OpenGL propose d'utiliser les Vertex Array Objects pour enregistrer un petit bout de code dans la carte graphique, ce qui lui permet d'aller chercher ses données dans la mémoire vidéo sans avoir à demander à l'application où elles se trouvent.

Grâce aux VAO, OpenGL sait exactement où aller chercher ses données sans avoir à le mander à personne. Tout se passe directement dans la carte graphique.
On ne sait pas vraiment ce qu'il se passe à l'intérieur des VAO et d'ailleurs on s'en moque on veut seulement que ça fonctionne, peu importe comment. :p
Ceux qui connaissent OpenGL 2.1 feront surement l'analogie avec les Display List , on peut dire que les VAO sont leurs petits frères.

Pour vous donner une petite info, l'utilisation combinée des VAO et des VBO nous permettra de passer à OpenGL 3.3 . Dans la dernière partie de ce chapitre, nous modifierons notre contexte pour profiter de la dernière version d' OpenGL 3 . :D
Les Mac-Users sont eux-aussi concernés car vous pourrez utiliser directement OpenGL 3 sur votre Mac, vous n'aurez plus besoin de passer par un autre OS.

Création et Utilisation

Création

Le header

Contrairement aux VBO, les VAO seront très simples à configurer et à utiliser. Nous n'aurons pas besoin d'allouer de mémoire vidéo ni de faire des transferts de données, il suffira juste de faire des opérations que l'on connait déjà.

Nous aurons tout d'abord besoin d'une variable de type GLuint pour représenter notre VAO au sein du code C++. Comme d'habitude, nous travaillerons avec la classe Cube (puis la classe Caisse ) pour illustrer ce que nous verrons dans ce chapitre. Nous commençons donc pas déclarer un attribut de type GLuint dans la header, on l'appellera m_vaoID :

#ifndef DEF_CUBE
#define DEF_CUBE
// Includes
....
// Classe Cube
class Cube
{
    public:
    Cube(float taille, std::string const vertexShader, std::string const fragmentShader);
    ~Cube();
    void charger();
    void afficher(glm::mat4 &projection, glm::mat4 &modelview);
    protected:
    Shader m_shader;
    float m_vertices[108];
    float m_couleurs[108];
    GLuint m_vboID;
    int m_tailleVerticesBytes;
    int m_tailleCouleursBytes;
    GLuint m_vaoID;
};
#endif

Bien entendu, on pense à l'initialiser dans le constructeur :

Cube::Cube(float taille, std::string const vertexShader, std::string const fragmentShader) : m_shader(vertexShader, fragmentShader), m_vboID(0),
                                                                                m_tailleVerticesBytes(108 * sizeof(float)),
                                                                                m_tailleCouleursBytes(108 * sizeof(float)), m_vaoID(0)
{
    // Initialisation
    ....
}
Chargement

Dans l'introduction, nous avons vu que les VAO permettent d'enregistrer certains appels de fonction dans la mémoire de la carte graphique. Ces appels concernent surtout l'affichage avec les tabl qui sont concernés Les appels qui sont concernés sont ceux relatifs aux tableaux Vertex Attrib. Ils vont donc disparaitre de la méthode afficher() pour se retrouver dans la configuration des VAO. Nous verrons cela en détail dans un instant car avant, nous devons créer un objet OpenGL valide et le verrouiller pour le configurer.

Cela se fera dans la méthode charger() exactement comme le VBO. Nous placerons notre code après sa configuration.

On commence d'ailleurs avec la création du VAO grâce l'habituelle fonction de génération d'identifiant glGenXXX(). Elle s'appelle ici glGenVertexArrays() :

void glGenVertexArrays(GLsizei number, GLuint *arrays);

  • number : Le nombre d'ID à initialiser, nous lui donnerons la valeur 1

  • arrays : Un tableau de GLuint ou l'adresse d'une seule variable du même type. Nous lui donnerons l'adresse du VAO

Nous devons l'appeler juste après la configuration du VBO avec les paramètres donnés ci-dessus :

void Cube::charger()
{
    // Création du VBO
    ....
    // Génération de l'ID du VAO
    glGenVertexArrays(1, &m_vaoID);
}

Comme toujours avec les objets OpenGL , nous devons les verrouiller lorsque nous voulons les configurer ou les utiliser. Tout ceux que nous vus jusqu'à maintenant (texture, VBO, etc.) nécessitaient cette opération, les VAO n’échappent pas à cette règle. Nous utiliserons pour cela la fonction glBindVertexArray() :

void glBindVertexArray(GLuint array);

  • array : Le VAO à verrouiller

Remarquez que cette fonction ne prend pas de paramètre target contrairement aux textures et aux VBO. En temps normal, il y en a toujours un mais dans certains cas il arrive qu'il n'y en ait pas. :)

On verrouille donc notre VAO en appelant la fonction glBindVertexArray(), on lui donnera en paramètre l'attribut m_vaoID. On en profitera au passage pour le déverrouiller immédiatement en utilisant la même fonction mais avec le paramètre 0 :

void Cube::charger()
{
    // Gestion du VBO
    ....
    // Génération de l'identifiant du VAO
    glGenVertexArrays(1, &m_vaoID);
    // Verrouillage du VAO
    glBindVertexArray(m_vaoID);
        // Vide pour le moment
        ....
    // Déverrouillage du VAO
    glBindVertexArray(0);
}

Notre VAO est maintenant prêt à être configuré.

Nous savons que celui-ci devra contenir le code relatif aux tableaux Vertex Attrib, il devra enregistrer tous les appels de fonctions qui y sont associés comme glVertexAttribPointer(), glEnableVertexAttribPointer(), glBindBuffer(), etc.

Pour ce faire, il suffit simplement de couper le code d'envoi de données se trouvant dans la méthode afficher() et de le placer au moment de configurer le VAO (pendant le verrouillage).

Ça veut dire qu'on n'envoie plus les vertices et tout dans la méthode afficher() ?

Oui tout à fait.

Le but est de sauvegarder ces appels dans la carte graphique, on les place donc dans le VAO. Nous devons donc les supprimer de la méthode afficher() pour les placer chez lui. Ne vous inquiétez pas cependant, nous ajouterons un petit bout de code dans cette méthode pour qu'OpenGL sache où chercher les données.

Au niveau de code, on coupe simplement le code suivant :

void Cube::afficher(glm::mat4 &projection, glm::mat4 &modelview)
{
    .....
    // Verrouillage du VBO
    glBindBuffer(GL_ARRAY_BUFFER, m_vboID);
        // Accès aux vertices dans la mémoire vidéo
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
        glEnableVertexAttribArray(0);
        // Accès aux couleurs dans la mémoire vidéo
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(m_tailleVerticesBytes));
        glEnableVertexAttribArray(1);
    // Déverrouillage du VBO
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    .....
}

Que l'on colle ici (entre les lignes surlignées) :

void Cube::charger()
{
    // Gestion du VBO
    ....
    // Génération de l'identifiant du VAO
    glGenVertexArrays(1, &m_vaoID);
    // Verrouillage du VAO
    glBindVertexArray(m_vaoID);
        // Verrouillage du VBO
        glBindBuffer(GL_ARRAY_BUFFER, m_vboID);
            // Accès aux vertices dans la mémoire vidéo
            glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
            glEnableVertexAttribArray(0);
            // Accès aux couleurs dans la mémoire vidéo
            glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(m_tailleVerticesBytes));
            glEnableVertexAttribArray(1);

        // Déverrouillage du VBO
        glBindBuffer(GL_ARRAY_BUFFER, 0);
    // Déverrouillage du VAO
    glBindVertexArray(0);
}

De cette façon, le processus d'envoi de données est enregistré sur la carte graphique et OpenGL n'a pas besoin d'interroger l'application C++ pour savoir ce qu'il doit faire.

Petit détail, je vous conseille d'indenter le code qui se trouve dans le VAO tout comme les shaders et les VBO. :)

Attention cependant, n'appelez pas d'autres fonctions à l'intérieur pour tenter de les enregistrer sur la carte graphique. Il n'y a que celles qui concernent les tableaux Vertex Attrib qui le peuvent, le reste ne fonctionnera pas.

Éviter les fuites de mémoire

Vous vous souvenez de ce que nous avons vu dans le chapitre précédent sur les fuites de mémoire des VBO ? Que si on en chargeait un deux fois alors le premier chargement était perdu et les ressources considérées comme étant "toujours utilisées".

Le problème est le même ici, si on charge un VAO plusieurs fois alors on gâche de la mémoire. Pour éviter cela, nous allons utiliser la même astuce que celle que nous avons utilisée pour les textures et les VBO, à savoir : appeler une fonction du type glIsXXX() pour savoir si une initialisation a déjà eu lieu, puis en appeler une autre du type glDeleteXXX() si c'est le cas.

La première de ces fonctions s'appelle dans notre cas :

GLboolean glIsVertexArray(GLuint array);

Elle prend en paramètre l'identifiant à vérifier et renvoie comme d'habitude GL_TRUE ou GL_FALSE pour confirmer ou non une précédente initialisation.

La fonction de destruction de VAO quant à elle s'appelle glDeleteVertexArrays() :

void glDeleteVertexArrays(GLsizei n·umber, const GLuint *arrays·);

  • number : Le nombre d'ID à initialiser, nous lui donnerons la valeur 1

  • arrays : Un tableau de GLuint ou l'adresse d'une seule variable du même type. Nous lui donnerons l'adresse de l'attribut m_vaoID

Nous devons les appeler dans un bloc if juste avant la génération d'ID :

void Cube::afficher(glm::mat4 &projection, glm::mat4 &modelview)
{
    // Gestion du VBO
    ....
    // Destruction d'un éventuel ancien VAO
    if(glIsVertexArray(m_vaoID) == GL_TRUE)
        glDeleteVertexArrays(1, &m_vaoID);
    // Génération de l'identifiant du VAO
    glGenVertexArrays(1, &m_vaoID);
    // Verrouillage du VAO
    glBindVertexArray(m_vaoID);
        // Verrouillage du VBO
        glBindBuffer(GL_ARRAY_BUFFER, m_vboID);
            // Accès aux vertices dans la mémoire vidéo
            glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
            glEnableVertexAttribArray(0);
            // Accès aux couleurs dans la mémoire vidéo
            glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(m_tailleVerticesBytes));
            glEnableVertexAttribArray(1);

        // Déverrouillage du VBO
        glBindBuffer(GL_ARRAY_BUFFER, 0);
    // Déverrouillage du VAO
    glBindVertexArray(0);
}

De cette façon, on évite tout éventuel chargement perdu dans la mémoire.

Le destructeur

Tout comme le VBO, nous devons détruire le VAO dans le destructeur (qui lui-même est appelé au moment de détruire un objet). Nous utiliserons une fois de plus la fonction glDeleteVertexArrays(), je pense que vous n'avez pas besoin de plus d'explication. :p

Cube::~Cube()
{
    // Destruction du VBO
    glDeleteBuffers(1, &m_vboID);
    // Destruction du VAO
    glDeleteVertexArrays(1, &m_vaoID);
}

Utilisation

L'utilisation des VAO est encore plus simple que leur création puisqu'il suffit juste de les verrouiller exactement comme si on utilisait une texture. C'est-à-dire que nous devons le verrouiller avant et le déverrouiller après la fonction glDrawArrays(). Bien évidemment, il faut supprimer toute référence aux tableaux Vertex Attrib maintenant qu'ils sont utilisés autre part :

void Cube::afficher(glm::mat4 &projection, glm::mat4 &modelview)
{
    // Activation du shader
    glUseProgram(m_shader.getProgramID());
        // Verrouillage du VAO
        glBindVertexArray(m_vaoID);
            // Envoi des matrices
            glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));
            glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));
            // Rendu
            glDrawArrays(GL_TRIANGLES, 0, 36);
        // Déverrouillage du VAO
        glBindVertexArray(0);
    // Désactivation du shader
    glUseProgram(0);
}

Grâce à ce verrouillage, OpenGL saura automatiquement où se trouveront les données dont il a besoin.

Petit détail : vous n'êtes pas obligés d'insérer l'envoi des matrices pendant le verrouillage du VAO, ça fonctionnera très bien si vous le faites avant. Personnellement, j'aime bien tout mettre à l'intérieur (textures comprises). :)

Tout code faisant référence aux Vertex Attrib dans la méthode afficher() doit totalement disparaitre et doit maintenant se trouver dans la méthode charger().

Au fait, on ne déverrouille pas les tableaux Vertex Attrib ? On les a activés pendant la configuration du VAO mais on ne les désactive à aucun moment.

C'est une question que je me suis aussi posée la première fois. Apparemment, les appels aux fonctions glDisableVertexAttribPointer() sont inutiles avec les VAO, ils se désactivent en même temps que le déverrouillage du VAO. Pas besoin de se triturer la tête avec ça.

Un autre exemple

Le header

Nous avons vu, dans la partie précédentes, comment implémenter un VAO dans la classe Cube , nous allons maintenant faire la même chose dans sa classe fille ( Caisse ). Ça va nous donner l'occasion de voir un autre exemple pour mieux intégrer les notions que nous avons abordées.

Avant de commencer, nous savons maintenant que nous avons besoin d'une variable de type GLuint pour gérer le VAO. Cependant, vu que la classe Caisse hérite de celui de la classe Cube alors il est inutile d'en utiliser une nouvelle. De plus, contrairement aux VBO, nous n'avons pas besoin d'attribut supplémentaire pour définir la taille des tableaux de données.

Il n'y a donc aucune modification à faire dans le header. :)

La méthode charger()

Au niveau de la méthode charger(), on commence par générer un nouvel ID pour le VAO grâce à la fonction glGenVertexArrays(). On le verrouille ensuite à l'aide de glBindVertexarray() :

void Caisse::charger()
{
    // Gestion du VBO
    ....
    // Destruction d'un éventuel ancien VAO
    if(glIsVertexArray(m_vaoID) == GL_TRUE)
        glDeleteVertexArrays(1, &m_vaoID);
    // Verrouillage du VAO
    glBindVertexArray(m_vaoID);
        // Vide pour le moment
        ....
    // Déverrouillage du VAO
    glBindVertexArray(0);
}

Il ne faut pas oublier de rajouter les fonctions qui permettent d'éviter les fuites de mémoire en détruisant un éventuel ancien VAO :

void Caisse::charger()
{
    // Gestion du VBO
    ....
    // Destruction d'un éventuel ancien VAO
    if(glIsVertexArray(m_vaoID) == GL_TRUE)
        glDeleteVertexArrays(1, &m_vaoID);
    // Génération de l'ID du VAO
    glGenVertexArrays(1, &m_vaoID);
    // Verrouillage du VAO
    glBindVertexArray(m_vaoID);
        // Vide pour le moment
        ....
    // Déverrouillage du VAO
    glBindVertexArray(0);
}

Comme nous l'avons vu depuis le début du chapitre, le VAO doit contenir le code relatif aux tableaux Vertex Attrib. On coupe donc le code concerné dans la méthode afficher() pour le transférer ici. Ce qui donne la méthode charger() suivante :

void Caisse::charger()
{
    // Gestion du VBO
    ....
    // Destruction d'un éventuel ancien VAO
    if(glIsVertexArray(m_vaoID) == GL_TRUE)
        glDeleteVertexArrays(1, &m_vaoID);
    // Génération de l'ID du VAO
    glGenVertexArrays(1, &m_vaoID);
    // Verrouillage du VAO
    glBindVertexArray(m_vaoID);
        // Verrouillage du VBO
        glBindBuffer(GL_ARRAY_BUFFER, m_vboID);
            // Accès aux vertices dans la mémoire vidéo
            glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
            glEnableVertexAttribArray(0);
            // Accès aux coordonnées de texture dans la mémoire vidéo
            glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(m_tailleVerticesBytes));
            glEnableVertexAttribArray(2);
        // Déverrouillage du VBO
        glBindBuffer(GL_ARRAY_BUFFER, 0);
    // Déverrouillage du VAO
    glBindVertexArray(0);
}

Configuration terminée. ;)

La méthode afficher()

Pour afficher notre caisse au final, il suffit simplement de remplacer ce que l'on a supprimé dans la méthode afficher() par le verrouillage du VAO. La seule différence avec la classe Cube c'est qu'ici on utilise ici une texture :

// Verrouillage du VAO
glBindVertexArray(m_vaoID);
    // Envoi des matrices
    glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));
    glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));
    // Verrouillage de la texture
    glBindTexture(GL_TEXTURE_2D, m_texture.getID());
    // Rendu
    glDrawArrays(GL_TRIANGLES, 0, 36);
    // Déverrouillage de la texture
    glBindTexture(GL_TEXTURE_2D, 0);
// Déverrouillage du VAO
glBindVertexArray(0);

Le code final de la méthode afficher() est maintenant :

void Caisse::afficher(glm::mat4 &projection, glm::mat4 &modelview)
{
    // Activation du shader
    glUseProgram(m_shader.getProgramID());
        // Verrouillage du VAO
        glBindVertexArray(m_vaoID);
            // Envoi des matrices
            glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));
            glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));
            // Verrouillage de la texture
            glBindTexture(GL_TEXTURE_2D, m_texture.getID());
            // Rendu
            glDrawArrays(GL_TRIANGLES, 0, 36);
            // Déverrouillage de la texture
            glBindTexture(GL_TEXTURE_2D, 0);
        // Déverrouillage du VAO
        glBindVertexArray(0);
    // Désactivation du shader
    glUseProgram(0);
}

Vous pouvez maintenant compiler votre projet.

Ce chapitre est maintenant terminé. :-°

Enfin juste en théorie, car nous allons voir maintenant comment passer à la version 3.3 d'OpenGL. Comme je vous l'ai dit tout à l'heure, l'utilisation combinée des VBO et des VAO nous permettent de profiter pleinement d'OpenGL 3. Les mac-users sont également concernés car vous pourrez maintenant coder vos applications directement sur votre Mac.

OpenGL 3.3 et Mac OS X

Passer à OpenGL 3.3

Mine de rien, nous avons fait pas mal de chemin depuis le début de ce tutoriel. Nous avons vu les principes de base d'OpenGL et nous venons de voir deux fonctionnalités avancées (ou plutôt extensions) exploitant directement notre carte graphique. Et croyez-moi ce n'est pas fini.

Grâce aux deux derniers chapitres, nous connaissons tout ce qui est nécessaire pour mettre à jour notre version d'OpenGL. Vous n'êtes pas sans savoir que depuis le début du tutoriel, nous utilisons la version 3.1. Cependant, je vous avais dit que nous passerions à OpenGL 3.3 dans le futur, et c'est justement ce que nous allons faire.

J'ai préféré utiliser la 3.1 jusqu'à maintenant car avec cette version, nous n'étions pas obligé d'utiliser les VBO et les VAO pour chaque rendu. Imaginez si je vous avais montré ça dès le début, vous auriez fuis en courant. :p
Mais maintenant que nous connaissons tout ça, nous pouvons enfin passer à la 3.3.

Pour faire cela, la SDL va beaucoup nous aider puisqu'elle fera tout pour nous. Il suffira de lui demander la version qui nous intéresse. Vous vous souvenez du moment où nous initialisions la SDL avec des paramètres OpenGL ? Nous utilisions le code suivant dans la méthode initialiserFenetre() :

bool SceneOpenGL::initialiserFenetre()
{
    // Initialisation de la SDL

    if(SDL_Init(SDL_INIT_VIDEO) < 0)
    {
        std::cout << "Erreur lors de l'initialisation de la SDL : " << SDL_GetError() << std::endl;
        SDL_Quit();

        return false;
    }

    // Version d'OpenGL

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);

    // Double Buffer

    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);

    // Création de la fenêtre

    m_fenetre = SDL_CreateWindow(m_titreFenetre.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, m_largeurFenetre, m_hauteurFenetre, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL);
    m_contexteOpenGL = SDL_GL_CreateContext(m_fenetre);
    return true;
}

Les lignes intéressantes dans ce code sont celles-ci :

// Version d'OpenGL

SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);

Elles permettent de demander à la SDL la version 3.1 d'OpenGL. ;)

Pour passer à la 3.3, il suffit de changer la valeur du paramètre SDL_GL_CONTEXT_MINOR_VERSION pour lui affecter la valeur 3 :

// Version d'OpenGL

SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);

Mais ce n'est pas tout. En effet, à partir d'OpenGL 3.2 nous pouvons demander deux profils OpenGL : le profil de compatibilité (aussi appelé Legacy ou Compatibility) et le profil Core .

La premier permet de conserver une compatibilité avec OpenGL 2.1. Il a été créé à l'origine pour ceux qui utilisent de gros moteurs 3D pour qu'ils n'aient pas à tout recoder.

Le second, lui, purge toutes les fonctionnalités inutiles pour ne garder que celles qui sont intéressantes. Il bénéficie en plus de pas mal de nouveautés spécifiques à OpenGL 3 dont un nouveau pipeline plus souple et plus performant. Nous travaillerons toujours avec ce profil.

Pour le dire à la SDL, il suffit juste d'appeler la fonction SDL_GL_SetAttribut() avec en premier paramètre la valeur SDL_GL_CONTEXT_PROFILE_MASK. Le second quant à lui pourra prendre deux valeurs :

  • SDL_GL_CONTEXT_PROFILE_COMPATIBILITY : Pour le profil de Compatibilité

  • SDL_GL_CONTEXT_PROFILE_CORE : Pour le profil Core

Vous l'aurez compris, nous utiliserons la seconde valeur.

Au final, l'appel à la fonction SDL_GL_SetAttribute() ressemble à ceci :

// Utilisation du profil Core
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);

Ce qui donne en somme les paramètres suivants :

// Version d'OpenGL

SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);

Maintenant, nous sommes totalement à jour avec OpenGL 3. :D

OpenGL 3 sous Mac OS X

Si vous n'êtes pas utilisateurs de Mac OS X, vous n'êtes pas obligés de lire cette partie mais je vous conseille quand même de le faire (au moins à partir du titre "Version d'OpenGL") car si vous voulez créer des jeux multiplateformes, il vous suffira juste d'inclure quelques lignes de code pour les rendre compatibles avec l'OS d'Apple.

Si vous êtes utilisateurs de Mac OS X vous avez dû vous sentir frustrés depuis le début du tutoriel vu qu'il était impossible d'utiliser OpenGL 3. :(

Le problème vient du fait qu'Apple n'utilise que deux versions pour OpenGL : la 2.1 et la 3.2, donc pas de 3.1. De plus, pour profiter de la dernière version vous devez au moins être sous OS X Lion (10.7) vu qu'Apple ne soutient plus ses anciens OS.

Mais bon, si vous êtes au moins sous Lion et que vous avez suivi le tutoriel jusqu'ici, vous êtes donc capables d'utiliser OpenGL 3 directement sur votre Mac. Vous n'aurez pas la dernière version mais ce n'est pas grave, la plus importante étant la 3.2. ;)

Je vais vous expliquer comment faire en utilisant Xcode qui est un IDE beaucoup plus adapté pour vous. Pour commencer, je vais vous demander de télécharger les deux DMG suivants qui contiennent respectivement les Frameworks de SDL2 et de SDL2_image ainsi que la librairie GLM :

  • Télécharger : Framework SDL2

  • Télécharger : Framework SDL2_image

  • Télécharger : GLM

Copiez les Frameworks se trouvant dans ces deux DMG dans le répertoire /Library/Frameworks/ (ou /Bibliothèque/Frameworks/).

Copiez le dossier glm (en minuscules) se trouvant dans l'archive OpenG Mathematics.zip dans le répertoire /usr/local/include. Si vous ne voyez pas ce dossier, regardez dans le menu du Finder et cliquez sur "Aller" puis sur "Aller au dossier" et renseignez le chemin que je vous ai donné.

Créer un projet SDL2 sous Mac OS X

Le projet

La création de projet SDL2 avec Xcode est quelque chose d'assez folklo vu les changements fréquents de cet IDE. En effet, en temps normal vous devriez utiliser un template spécial qui crée votre projet SDL2 automatiquement. Cependant, Apple change tellement le modèle de ses templates qu'il est devenu ingérable de fournir les fichiers nécessaires pour tout le monde.

A la place, nous allons utiliser une petite astuce qui, au moins, fonctionnera pour tout le monde. :)

Pour commencer, vous devez créer un projet " Cocoa Application " :

Une fois le projet créé, vous devez supprimer les fichiers suivants :

  • AppDelegate.h

  • AppDelegate.m

  • main.m

Ensuite, ajoutez toutes les sources à votre projet (dossiers Shaders et Textures inclus). Au moment de cet ajout, faites attention à bien spécifier les options suivantes :

Une fois les fichiers ajoutés, vous devriez avoir :

Une fois que vous avez importé les sources, vous devez importer les Frameworks qui vous permettront de compiler vos applications :

  • SDL2.framework : Vous l'avez normalement installé dans le dossier /Library/Frameworks

  • SDL2_image.framework : Même chose

  • OpenGL.framework : Lui, il se trouve dans le répertoire /System/Library/Frameworks

Là par contre, il faut décocher la case "Copy items into destination group's folder" (Laissez le paramètre Folders au deuxième choix). Ceci est valable uniquement pour les Frameworks, pour le reste il faut qu'elle soit cochée.

Enfin dernier point pour l'importation, vérifiez la présence des dossiers Textures et Shaders dans la liste "Copy Bundle Resources" dans l'onglet Build Phases :

S'ils n'y sont pas, faites un glisser-coller depuis le menu de navigation à gauche.

La librairie GLM

Pour vous permettre d'utiliser la librairie GLM dans vos projets, il va falloir ajouter un paramètre dans le fichier de votre projet.

Commencez par cliquer sur le nom de votre projet sous le menu Targets :

Puis cliquez sur le menu "Build Settings" et sur le bouton "Basic" :

Repérez ensuite la ligne contenant le paramètre "Header Search Paths" et renseignez le chemin où vous avez copié le dossier glm tout à l'heure (/usr/local/include) :

Version d'OpenGL

N'oubliez pas que votre version d'OpenGL est la 3.2, vous devez ajouter un bloc de type #ifdef dans votre code pour gérer le cas des Mac. Si la define __APPLE__ est définie, alors l'application doit passer en mode OpenGL 3.2, sinon elle reste en 3.3 :

#ifdef __APPLE__
    // Version d'OpenGL

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
#else
    // Version d'OpenGL

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
#endif

Notez que si vous souhaitez faire une application multiplateforme (Mac compris), il est préférable de créer un contexte 3.2 pour tout le monde.

Les ressources

A ce stade vous devriez être capables de compiler votre application. Cependant, vous aurez encore un problème pour trouver les ressources. En effet, Mac OS X ne se place pas dans le bon dossier lorsque vous lancez une application, ce qui fait qu'il est impossible pour lui de trouver les ressources que nous lui demandons.

Pour corriger ce problème, nous allons ajouter un petit bout de code spécifique à Mac OS X dans le même bloc #ifdef que précédemment.

Ce code est un peu spécial parce qu'il s'agit à l'origine d'un code Objective-C qui a été transposé en C. L'objective-C est le langage de base utilisé par les appareils Apple. Je ne vous expliquerai pas ce code car il faudrait faire un cours sur le Cocoa mais sachez simplement qu'il permet à Mac OS X de se placer dans le bon dossier des ressources.

Mais avant de copier ce code, vous devez inclure l'en-tête CoreFoundation.h toujours dans un bloc #ifdef à l'intérieur de la classe SceneOpenGL :

#ifdef __APPLE__
    #include <CoreFoundation/CoreFoundation.h>
#endif

Maintenant, copiez le code suivant ...

// Récupération du Bundle
CFURLRef URLBundle = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle());
char *cheminResources = new char[PATH_MAX];
// Changement du 'Working Directory'
if(CFURLGetFileSystemRepresentation(URLBundle, 1, (UInt8*)cheminResources, PATH_MAX))
    chdir(cheminResources);
// Libération de la mémoire
delete[] cheminResources;
CFRelease(URLBundle);

... juste après la spécification de la version d'OpenGL :

#ifdef __APPLE__
    // Version d'OpenGL

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
    // Récupération du Bundle
    CFURLRef URLBundle = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle());
    char *cheminResources = new char[PATH_MAX];
    // Changement du 'Working Directory'
    if(CFURLGetFileSystemRepresentation(URLBundle, 1, (UInt8*)cheminResources, PATH_MAX))
        chdir(cheminResources);
    // Libération de la mémoire
    delete[] cheminResources;
    CFRelease(URLBundle);
#else
    // Version d'OpenGL

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
#endif

Grâce à ce bout de code, vous n'aurez plus de problème pour trouver vos ressources. :D

Les headers

Pour terminer, faites attention aux en-têtes de la SDL et d'OpenGL qui diffèrent des autres OS :

// Include d'OpenGL
#include <OpenGL/gl3.h>
// Include de la SDL
#include <SDL2/SDL.h>
// Include de la SDL_image
#include <SDL2_image/SDL_image.h>

Ainsi, l'inclusion d'OpenGL ressemblera maintenant à ceci :

// Include Windows
#ifdef WIN32
#include <GL/glew.h>
// Include Mac
#elif __APPLE__
#define GL3_PROTOTYPES 1
#include <OpenGL/gl3.h>
// Include UNIX/Linux
#else
#define GL3_PROTOTYPES 1
#include <GL3/gl3.h>
#endif

Celui de la classe SceneOpenGL va légèrement changer par rapport aux autres puisqu'il faudra inclure l'en-tête CoreFoundation.h en plus :

// Include Windows
#ifdef WIN32
#include <GL/glew.h>
// Include Mac
#elif __APPLE__
#define GL3_PROTOTYPES 1
#include <OpenGL/gl3.h>
#include <CoreFoundation/CoreFoundation.h>
// Include UNIX/Linux
#else
#define GL3_PROTOTYPES 1
#include <GL3/gl3.h>
#endif

Et enfin, celui de la classe Texture sera :

// Include Windows
#ifdef WIN32
#include <GL/glew.h>
#include <SDL2/SDL_image.h>
// Include Mac
#elif __APPLE__
#define GL3_PROTOTYPES 1
#include <OpenGL/gl3.h>
#include <SDL2_image/SDL_image.h>
// Include UNIX/Linux
#else
#define GL3_PROTOTYPES 1
#include <GL3/gl3.h>
#include <SDL2/SDL_image.h>
#endif

Voilà pour les explications.

Ce qu'il faut retenir

Si on résume la création de projet SDL 2 sous Mac OS X :

  • Création d'un projet Cocoa Application

  • Suppression de l'AppDelegate et du main.m

  • Ajout des sources C++

  • Ajout du code concernant les ressources dans le fichier SceneOpenGL.cpp

  • Modification des en-têtes

Vous êtes maintenant capables de coder avec OpenGL 3.2 sur votre Mac. ;)

Télécharger (Windows, UNIX/Linux et Mac) : Code Source C++ des Vertex Array Objects

Nous avons vu ce que sont les VAO et comment ils fonctionnement. Ils sont très proches des VBO et s'utilisent même conjointement avec eux.

Grâce à ces deux notions, OpenGL possède tout ce qu'il faut à portée de main vu que tout se trouve dans la carte graphique. Les seules choses qu'il a besoin d'aller chercher dans la RAM, ce sont les textures. Cependant nous pouvons régler ce problème en utilisant les Texture Buffer Objects (TBO) qui permettent de stocker des textures directement dans la carte graphique. Mais bon, nous ne verrons pas ça pour le moment vous allez me faire une overdose sinon. :p

Si je peux vous donner une bonne nouvelle c'est que vous avez acquis assez d'expérience avec OpenGL pour pouvoir apprendre les shaders. Nous commencerons à les attaquer en profondeur dès le prochain chapitre. Vous serez alors capables de créer votre propre classe Shader .


La compilation de shaders

Re-bonjour à tous !

Aujourd'hui, nous allons attaquer un gros morceau de la programmation OpenGL, je vous conseille de préparer le café, le chocolat, les biscuits, etc. :p

Les shaders sont quelque chose d'assez monstrueux, nous allons les étudier à travers 4 chapitres (et encore sans parler des effets que l'on pourra faire avec) dont le premier est consacré à une classe que l'on utilise presque depuis le début du tutoriel sans jamais avoir vu son fonctionnement. ;)

Piqûre de rappel

Introduction

Depuis quasiment le début du tutoriel, nous utilisons une classe dans tous nos programmes sans même que nous sachions ce qu'il y a à l'intérieur. Je parle évidemment de la classe Shader.

En effet, que ce soit des couleurs ou des textures nous utilisons toujours cette classe pour afficher quelque chose à l'écran :

// Shader pour la colorisation

Shader shaderCouleur("Shaders/couleurs.vert", "Shaders/couleurs.frag");
shaderCouleur.charger();

.....

// Shader pour les textures

Shader shaderTexture("Shaders/texture.vert", "Shaders/texture.frag");
shaderTexture.charger();

La question que vous vous êtes déjà probablement posée est : que fait cette mystérieuse classe ?
Je vous rassure elle ne fait rien de compliqué. En fait, elle ne fait qu'une seule chose : compiler les fichiers sources qu'on lui envoie.

L'objectif de ce chapitre va être la reprogrammation complète de cette classe de façon à ce que vous appreniez comment créer et utiliser un shader. A la fin, vous serez en mesure de comprendre totalement votre code source, il n'y aura plus de code mystère. ;) Mais avant cela, nous allons faire un petit rappel sur ce que nous savons déjà sur le shaders.

Rappel

Vertex et Fragment Shader

Comme nous l'avons vu dans le chapitre 4, nous savons que les shaders sont des programmes qui sont exécutés non pas par le CPU mais par la carte graphique. Il en existe deux types :

  • Le Vertex Shader : qui prend chaque vertex à part pour calculer sa position à l'écran. Si un vertex possède 3 coordonnées, alors le Vertex Shader aura besoin des matrices projection et modelview pour faire le calcul.

  • Le Fragment Shader (ou Pixel Shader) : qui prend à part chaque pixel des triangles formés par les vertices pour définir sa couleur. Si on utilise une texture, alors le Fragment Shader va chercher à l'intérieur de cette texture la couleur dont il a besoin.

Ces deux types de shader sont utilisés à deux moments différents du pipeline 3D :

Pour rappel :

  • Définition des coordonnées : Ce sont les vertices et toutes les données qui y sont associées. Depuis peu, tout ça se trouve dans la carte graphique grâce aux VBO.

  • Vertex Shader : Shader qui travaille sur les vertices

  • Pixelisation des triangles : Moment où une forme géométrique (formée par les vertices) est convertie en pixels

  • Fragment Shader : Shader qui travaille sur les pixels

  • Test de profondeur : Test qui permet d'afficher ou de cacher un pixel

  • Affichage : Sortie de la carte graphique (en général, il s'agit de l'écran)

Au final, nous voyons que les shaders sont exécutés à deux moments : une fois pour transformer les vertices et une autre pour définir leur couleur.

Geometry Shader

En réalité, les shaders devraient être exécutés 3 fois car il existe un troisième type qui s'appelle le Geometry Shader qui vient se placer juste entre les deux premiers. Celui-ci permet de modifier les primitives (triangles, ...) formées par les vertices. Cependant, nous ne l'utiliserons pas d'une part parce qu'il est optionnel et d'autre part car il est quasiment inutile pour nous. :p

Soit dit en passant, ce nouveau type a été introduit avec la version 3.2 d'OpenGL.

Utilité

Les shaders ne se limitent pas au positionnement de vertices ou à la colorisation de pixels, ils permettent aussi de créer une multitude d'effets réalistes comme l'eau, la lumière, le feu, ... (ce que vous n'avez surement pas oublié je pense :p ). D'ailleurs, c'est certainement leur première utilité dans le monde du jeu-vidéo. Nous consacrerons une partie entière de ce tutoriel à l'élaboration de ces effets.

Depuis la version 3.1 d'OpenGL, l'utilisation des shaders est devenue obligatoire car les développeurs de l'API ont introduit une nouvelle philosophie : celle du 'tout shader'. Dans les précédentes versions, OpenGL gérait lui-même tout le pipeline 3D sans rien demander à personne mises à part les données de base comme les vertices. Seulement, cette gestion est devenue trop lourde aujourd'hui, les développeurs de l'API préfèrent nous laisser gérer le maximum de tâches de façon à ce que nous les optimisions mieux en fonction de ce que nous voulons faire.

Cette philosophie complique grandement l'apprentissage d'OpenGL, c'est pour ça que nous n'avons pas vu les shaders tout de suite, sinon le tuto aurait été beaucoup plus complexe à suivre. Vous m'auriez surement fait une overdose avant même d'avoir atteint le chapitre sur les matrices. :lol:

La classe Shader

Enfin, maintenant que vous avez acquis assez d'expérience avec la programmation OpenGL, nous allons pouvoir voir en détails le fonctionnement des shaders. Nous commencerons en premier par étudier la fameuse classe Shader. Et comme je vous l'ai dit précédemment, elle ne fait rien de magique, elle ne fait que compiler du code source.

Car oui, comme tout programme qui se respecte, les shaders possèdent bel et bien un code source. La seule différence avec les programmes classiques c'est qu'il est compilé juste avant d'être utilisé et pas avant, il n'y a donc pas d'application de type .exe à donner à votre carte graphique. D'ailleurs en parlant de ça, ces codes sources sont écrits avec un langage proche du C++ (ou plutôt du C) qui s'appelle l'OpenGL Shading Language (ou GLSL). Nous l'étudierons dans le prochain chapitre, nous devons savoir avant comment le compiler. ;)

Il faut bien faire la différence entre la COMPILATION de shaders et leur PROGRAMMATION. Ce que nous voyons dans ce chapitre concerne uniquement la compilation, ne confondez pas.

Compilation des sources

La classe Shader

Bien, après cette brève introduction j'espère vous avoir mis un peu l'eau à la bouche. :p

Il est temps de passer à la reprogrammation complète de la classe Shader, je vais vous demander de supprimer les fichiers Shader.h et Shader.cpp que vous utilisez actuellement et d'en recréer deux nouveaux (avec le même nom bien sûr). Si vous essayez de compiler votre code après ça, vous devriez avoir une belle petite erreur de compilation.

Bref, on commence cette reprogrammation par le header en incluant tous les en-têtes nécessaires : ceux d'OpenGL, d'iostream et de string. Nous n'aurons pas besoin de ceux de la SDL car ils n'ont aucun rapport avec les shaders :

#ifndef DEF_SHADER
#define DEF_SHADER

// Include Windows

#ifdef WIN32
#include <GL/glew.h>

// Include Mac

#elif __APPLE__
#define GL3_PROTOTYPES 1
#include <OpenGL/gl3.h>

// Include UNIX/Linux

#else
#define GL3_PROTOTYPES 1
#include <GL3/gl3.h>

#endif

// Includes communs

#include <iostream>
#include <string>

#endif

Remarquez l'en-tête pour Mac OS X qui permet de rendre nos projets compatibles avec cet OS. ;)

Après les inclusions, on passe à la classe en elle-même. Elle possèdera 3 attributs majeurs : des variables de type GLuint représentant le Vertex Shader, le Fragment Shader et un programme. Nous ajouterons également le constructeur par défaut ainsi que le destructeur :

// Classe Shader

class Shader
{
    public:

    Shader();
    ~Shader();

    private:

    GLuint m_vertexID;
    GLuint m_fragmentID;
    GLuint m_programID;
};

Ah programID ? C'est le truc qu'on utilise dans la fonction glUseProgram() non ?

Oui tout à fait.

Comme vous le savez un shader est un programme, c'est pour cette raison que nous avons cet attribut. Remarquez aussi que les 3 variables présentées sont en fait des objets OpenGL car elles ont le type GLuint au même titre que les textures, les VBO ou les VAO.

Nous allons également rajouter un autre constructeur à cette classe. C'est un constructeur que vous connaissez par cœur car c'est celui qui demande en paramètre le chemin vers les deux codes sources shaders. Son prototype est le suivant :

Shader(std::string vertexSource, std::string fragmentSource);

Il prend en paramètre deux string représentant le chemin vers les codes sources spécifiés. Nous aurons besoin de deux attributs supplémentaires pour les gérer dans la classe. ;)

Nous rajoutons donc deux variables m_vertexSource et m_fragmentSource de type string dans le header :

// Attributs

GLuint m_vertexID;
GLuint m_fragmentID;
GLuint m_programID;

std::string m_vertexSource;
std::string m_fragmentSource;

Si on récapitule tout ça :

#ifndef DEF_SHADER
#define DEF_SHADER

// Include Windows

#ifdef WIN32
#include <GL/glew.h>

// Include Mac

#elif __APPLE__
#define GL3_PROTOTYPES 1
#include <OpenGL/gl3.h>

// Include UNIX/Linux

#else
#define GL3_PROTOTYPES 1
#include <GL3/gl3.h>

#endif

// Includes communs

#include <iostream>
#include <string>

// Classe Shader

class Shader
{
    public:

    Shader();
    Shader(std::string vertexSource, std::string fragmentSource);
    ~Shader();

    private:

    GLuint m_vertexID;
    GLuint m_fragmentID;
    GLuint m_programID;

    std::string m_vertexSource;
    std::string m_fragmentSource;
};

#endif

Header terminé.

Les constructeurs et le destructeur

Maintenant que le header de la classe est terminé, on va pouvoir passer à son implémentation. Et comme d'habitude, on commence par les constructeurs et le destructeur.

Le premier est le constructeur par défaut, qui ne fait rien de particulier d'ailleurs :

// Constructeur par défaut

Shader::Shader() : m_vertexID(0), m_fragmentID(0), m_programID(0), m_vertexSource(), m_fragmentSource()
{
}

Le second, lui, fera la même chose sauf qu'il affectera les sources avec les string donnés en paramètres :

// Constructeur

Shader::Shader(std::string vertexSource, std::string fragmentSource) : m_vertexID(0), m_fragmentID(0), m_programID(0),
                                                                       m_vertexSource(vertexSource), m_fragmentSource(fragmentSource)
{
}

Quant au destructeur, vous savez maintenant ce qui se trouve à l'intérieur. Mais attention ! Celui-ci ne restera pas vide longtemps. :p

// Destructeur

Shader::~Shader()
{
}

La méthode getProgramID

L'attribut m_programID est quelque chose que l'on utilise assez souvent, notamment pour activer les shaders. Nous allons recoder le getter qui permet de le récupérer et qui s'appelle getProgramID() :

GLuint getProgramID() const;

Son implémentation se passe de commentaire :

GLuint Shader::getProgramID() const
{
    return m_programID;
}

La méthode charger (Partie 1/2)

La méthode charger() va être la principale méthode de cette classe. En effet, vous avez surement remarqué qu'on l'utilise à chaque fois que l'on veut initialiser un shader :

// Shader Texture

shaderTexture("Shaders/texture.vert", "Shaders/texture.frag");
shaderTexture.charger();

Son prototype est assez simple vu qu'elle ne demande rien en paramètre :

bool charger();

Elle renverra un booléen pour confirmer ou non le chargement du shader.

Cependant, contrairement à son prototype, la méthode charger() ne sera pas aussi simple à programmer. Elle devra être capable de faire plusieurs choses :

  • Compiler le code source du Vertex Shader

  • Compiler le code source du Fragment Shader

  • Réunir les fichiers compilés dans un seul et même programme

Pour effectuer les deux premières étapes, nous allons créer une autre méthode qui permettra de compiler le code source d'un shader. Il vaut mieux séparer la compilation du reste du code car vous allez voir que c'est un peu ... long. :p Cette nouvelle méthode s'appellera simplement compilerShader() :

bool compilerShader(GLuint &shader, GLenum type, std::string const &fichierSource);

Elle aura besoin de 3 paramètres :

  • shader : Le shader à compiler

  • type : Un paramètre semblable au paramètre target des objets OpenGL (comme GL_TEXTURE_2D) pour définir le type de shader. Il peut prendre la valeur GL_VERTEX_SHADER ou GL_FRAGMENT_SHADER

  • fichierSource : Le code source associé au shader

Un booléen sera également retourné pour confirmer ou non la réussite de la compilation.

Nous devons appeler cette nouvelle méthode deux fois afin de compiler nos deux codes sources. Le premier appel devra compiler un shader contenu dans l'attribut m_vertexID et dont le code source sera évidemment identifié par m_vertexSource. Le paramètre type sera quant à lui égal à la constante GL_VERTEX_SHADER. On n'oublie pas de vérifier le retour dans un bloc if :

bool Shader::charger()
{
    // Compilation du Vertex Shader

    if(!compilerShader(m_vertexID, GL_VERTEX_SHADER, m_vertexSource))
        return false;
}

Le second appel à cette méthode s'occupera cette fois du Fragment Shader. On lui donnera en paramètres les attributs m_fragmentID et m_fragmentSource. Le paramètre type sera égal à la constante GL_FRAGMENT_SOURCE :

bool Shader::charger()
{
    // Compilation des shaders

    if(!compilerShader(m_vertexID, GL_VERTEX_SHADER, m_vertexSource))
        return false;

    if(!compilerShader(m_fragmentID, GL_FRAGMENT_SHADER, m_fragmentSource))
        return false;
}

On ne retourne pas de message d'erreur ?

Si justement, les messages d'erreurs seront en fait affichés par la méthode compilerShader() car c'est à elle de dire si quelque chose ne va pas. Il est très important de vérifier les erreurs de compilation avec les shaders car nous n'avons pas d'IDE pour nous dire où elles seraient. Ce peut devenir vite embêtant si vous perdez trop de temps à chercher les erreurs par vous-même.

D'ailleurs la vérification de messages d'erreurs est quelque chose ... d'assez folklo à programmer vous verrez. :lol:

La méthode compilerShader

Créer un shader

Nous connaissons déjà le prototype ce cette méthode et nous savons également qu'elle a besoin de 3 paramètres :

bool compilerShader(GLuint &shader, GLenum type, std::string const &fichierSource);

Cette méthode devra être capable de faire plusieurs choses :

  • Générer un ID de shader

  • Lire son code source

  • Le compiler

La première étape va être très simple à réaliser car elle est très similaire à la création des autres objets OpenGL. Si je dis similaire, c'est parce que nous n'allons pas utiliser les fonctions classiques du type glGenXXX(), glBindXXX(), ... comme nous avons l'habitude de faire. Les shaders sont des objets un peu particuliers qui vont utiliser leurs propres fonctions. De plus, ils ne doivent pas être verrouillés pendant leur configuration mais seulement au moment de leur utilisation.

En définitif, nous ne devons pas utiliser la fonction glGenShaders() pour générer un ID mais la fonction glCreateShader() :

GLuint glCreateShader(GLenum shaderType);

Elle prend en paramètre une constante qui peut prendre la valeur GL_VERTEX_SHADER ou GL_FRAGMENT_SHADER. Nous lui donnerons la valeur du paramètre type de la méthode compilerShader().

Nous appellerons cette fonction en affectant la valeur retournée au paramètre shader :

bool Shader::compilerShader(GLuint &shader, GLenum type, std::string const &fichierSource)
{
    // Création du shader

    shader = glCreateShader(type);
}

Vu que nous utilisons un paramètre externe à la méthode pour définir le type de shader, il est donc possible possible que ce paramètre contienne une valeur erronée. Nous devons donc vérifier l'ID retourné par la fonction glCreateShader(). S'il est égal à 0 c'est qu'il y a eu un problème, il faut donc arrêter le chargement :

// Vérification du shader

if(shader == 0)
{
    std::cout << "Erreur, le type de shader (" << type << ") n'existe pas" << std::endl;
    return false;
}
Lecture du code source

La lecture du coude source est quelque chose de capital dans la création d'un shader. Sans lui, notre programme ne serait pas quoi faire et la carte graphique nous enverrait gentiment paitre. :-°

Nous devons lire les codes sources dont les chemins sont contenus dans les attributs m_vertexSource et m_fragmentSource. Pour cela, nous allons utiliser la bibliothèque fstream qui permet de la lecture et l'écriture de fichier en C++, je suppose que vous la connaissez déjà. :p Pensez à inclure l'en-tête fstream dans le fichier Shader.h.

Pour utiliser cette bibliothèque, nous allons commencer par déclarer un objet de type ifstream que nous appellerons simplement fichier. Son constructeur demande une chaine C représentant le chemin vers le fichier à lire, nous lui donnerons donc la chaine C du paramètre fichierSource de la méthode compilerShader() :

// Flux de lecture

std::ifstream fichier(fichierSource.c_str());

Avant de continuer, nous devons tester l'ouverture du fichier au cas où celui-ci n'existerait pas. Si c'est le cas alors on arrête le chargement, on affiche un message d'erreur et on détruit le shader que l'on a créé juste avant.

La fonction permettant de détruire un shader ressemble fortement aux fonctions du type glDeleteXXX() sauf qu'elle ne prend pas deux paramètres mais un seul, elle n'a également pas de s à la fin de son nom :

glDeleteShader(GLuint shader);

Bien évidemment, elle prend en paramètre le shader à détruire.

Avec cette fonction et le code précédent, notre test d'ouverture ressemble donc à ceci :

// Flux de lecture

std::ifstream fichier(fichierSource.c_str());

// Test d'ouverture

if(!fichier)
{
    std::cout << "Erreur le fichier " << fichierSource << " est introuvable" << std::endl;
    glDeleteShader(shader);

    return false;
}

Si le fichier s'est ouvert correctement alors nous pouvons copier son contenu sans problème. Pour cela, nous allons utiliser la fonction getline() qui permet de copier une ligne entière d'un fichier. Nous l’utiliserons à l'intérieur d'une boucle while, ce qui permettra de copier le code source ligne par ligne. ;)

Nous aurons besoin de deux string pour cette boucle : une qui contiendra chaque ligne lue, et une autre qui contiendra le code source final :

// Strings permettant de lire le code source

std::string ligne;
std::string codeSource;

// Lecture

while(getline(fichier, ligne))
    codeSource += ligne + '\n';

N'oubliez pas l'inclure le caractère de saut de ligne '\n' à la fin de chaque copie, sinon votre code source ne sera constitué que d'une gigantesque ligne incompréhensible. :p

Une fois la lecture terminée, on n'oublie pas de fermer le fichier pour éviter les fuites de mémoire :

// Strings permettant de lire le code source

std::string ligne;
std::string codeSource;

// Lecture

while(getline(fichier, ligne))
    codeSource += ligne + '\n';

// Fermeture du fichier

fichier.close();

On fait un petit récap de ce que nous avons fait jusqu'à maintenant :

bool Shader::compilerShader(GLuint &shader, GLenum type, std::string const &fichierSource)
{
    // Création du shader

    shader = glCreateShader(type);

    // Vérification du shader

    if(shader == 0)
    {
        std::cout << "Erreur, le type de shader (" << type << ") n'existe pas" << std::endl;
        return false;
    }

    // Flux de lecture

    std::ifstream fichier(fichierSource.c_str());

    // Test d'ouverture

    if(!fichier)
    {
        std::cout << "Erreur le fichier " << fichierSource << " est introuvable" << std::endl;
        glDeleteShader(shader);

        return false;
    }

    // Strings permettant de lire le code source

    std::string ligne;
    std::string codeSource;

    // Lecture

    while(getline(fichier, ligne))
        codeSource += ligne + '\n';

    // Fermeture du fichier

    fichier.close();
}
Compilation du shader

Faisons le point sur ce que nous avons fait jusqu'à présent : nous avons créé un shader et lu un code source qui se trouve maintenant en mémoire. Le problème c'est que les deux sont totalement dissociés pour le moment. Notre prochain objectif est donc d'envoyer ce code source à notre shader pour qu'il puisse enfin recevoir ses instructions.

Pour cela, nous allons utiliser une fonction OpenGL qui s'appelle glShaderSource() :

void glShaderSource(GLuint shader, GLsizei count, const GLchar **string, const GLint *length)

  • shader : Le shader concerné

  • count : Paramètre indiquant le nombre de chaines de caractère à envoyer. Nous lui donnerons la valeur 1 car notre code source n'est composé que d'une seule string

  • string : Sorte de "double pointeur" représentant un tableau de sous-chaines de caractère. :-° Nous utiliserons une petite astuce pour ne lui donner qu'une seule chaine

  • length : Tableau de taille des sous-chaines. Heureusement pour nous, nous n'aurons pas à utiliser d'astuce car OpenGL nous autorise à envoyer la valeur 0. Nous n'allons donc pas nous en priver :p

Cette fonction est un peu spéciale vous l'aurez remarqué.

Cependant, nous pouvons contourner son trop-plein de tableaux en ne fournissant qu'une seule et unique chaine de caractère. Il suffira d'envoyer l'adresse d'une chaine C. En effet, si on envoie l'adresse d'une chaine alors on se retrouve avec un pointeur du type **chaine. La fonction glShaderSource() demande justement cette sorte de double pointeur. ;)

On commence donc pas récupérer la chaine C du code source dans un pointeur grâce à la méthode c_str() :

// Récupération de la chaine C du code source

const GLchar* chaineCodeSource = codeSource.c_str();

Ensuite, nous appelons la fonction glShaderSource() avec les paramètres que nous venons de voir. Bien entendu, on pense à donner l'adresse du pointeur chaineCodeSource pour contenter le paramètre string :

// Récupération de la chaine C du code source

const GLchar* chaineCodeSource = codeSource.c_str();

// Envoi du code source au shader

glShaderSource(shader, 1, &chaineCodeSource, 0);

Notre shader vient enfin de récupérer son code source. :D Il ne manque plus qu'à le compiler.

Et pour ça, on va utiliser une fonction toute simple (ce qui fait du bien par rapport à la précédente) qui s'appelle glCompileShader() :

void glCompileShader(GLuint shader);

Elle ne prend qu'un seul paramètre : le shader à compiler. Nous l’appelons juste après avoir envoyé le code source :

// Récupération de la chaine C du code source

const GLchar* chaineCodeSource = codeSource.c_str();

// Envoi du code source au shader

glShaderSource(shader, 1, &chaineCodeSource, 0);

// Compilation du shader

glCompileShader(shader);
Vérification de la compilation

La vérification de la compilation est certainement l'étape la plus importante et la plus chiante de cette partie. Imaginez que vous compiliez un code source et qu'il y ait une erreur à l'intérieur, comment pouvez-vous savoir où elle se trouve ? C'est un problème qui peut devenir vite énervant surtout quand votre erreur n'est qu'un bête oubli de point-virgule. :p Pour éviter d'avoir à relire vos codes sources à chaque fois, nous allons vérifier l'état de leur compilation grâce à quelques fonctions OpenGL.

La première de ces fonctions va nous permettre de renvoyer pas mal d'informations sur un shader donné, la vérification d'erreur au moment de la compilation en fait justement partie. Elle s'appelle glGetShaderiv() :

void glGetShaderiv(GLuint shader, GLenum pname, GLint *params);

  • shader : Comme toujours, le shader sur lequel on travaille

  • pname : Le nom de l'information demandée

  • params : L'adresse d'une variable qui accueillera cette information

Pour connaitre l'état de la compilation, nous allons créer une variable de type GLint que l'on appellera erreurCompilation. Nous donnerons son adresse à la fonction glGetShaderiv() pour qu'elle puisse stocker l'information que l'on recherche. D'ailleurs, cette information se nomme GL_COMPILE_STATUS et sera la valeur du paramètre pname :

// Vérification de la compilation

GLint erreurCompilation(0);

glGetShaderiv(shader, GL_COMPILE_STATUS, &erreurCompilation);

L'appel à cette fonction seule ne sert pas à grand chose, il faut maintenant vérifier la valeur de la variable erreurCompilation. ;) Si elle différente de la constante GL_TRUE c'est qu'il y a eu une erreur, sinon c'est que tout va bien on peut retourner le booléen true pour clore le chargement :

// Vérification de la compilation

GLint erreurCompilation(0);
glGetShaderiv(shader, GL_COMPILE_STATUS, &erreurCompilation);

// S'il y a eu une erreur

if(erreurCompilation != GL_TRUE)
{
}

// Sinon c'est que tout s'est bien passé

else
    return true;

Si la fonction glGetShaderiv() a détecté une erreur alors il faut la récupérer.

Malheureusement, nous ne pouvons pas utiliser les objets string pour cette récupération car les fonctions OpenGL ne gère pas les objets C++. Il faut donc faire à l'ancienne et allouer de la mémoire à la main pour une chaine de caractère. :(

Mais avant ça, nous devons connaitre la taille du message d'erreur pour pouvoir allouer assez de mémoire. Pour ce faire, nous allons à nouveau utiliser la fonction glGetShaderiv() sauf que l'on demandera cette fois le paramètre GL_INFO_LOG_LENGTH qui correspond à la taille recherchée. Nous utiliserons la variable tailleErreur pour stocker la valeur qui sera retournée :

// Vérification de la compilation

GLint erreurCompilation(0);
glGetShaderiv(shader, GL_COMPILE_STATUS, &erreurCompilation);

// S'il y a eu une erreur

if(erreurCompilation != GL_TRUE)
{
    // Récupération de la taille de l'erreur

    GLint tailleErreur(0);
    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &tailleErreur);
}

// Sinon c'est que tout s'est bien passé

else
    return true;

Une fois que l'on connait la taille de l'erreur, nous pouvons allouer de la mémoire pour une chaine de caractère grâce au mot-clef new[]. On ajoute au passage 1 case pour gérer le caractère de fin de chaine '\0' qui n'est pas fourni avec le message d'erreur :

// Vérification de la compilation

GLint erreurCompilation(0);
glGetShaderiv(shader, GL_COMPILE_STATUS, &erreurCompilation);

// S'il y a eu une erreur

if(erreurCompilation != GL_TRUE)
{
    // Récupération de la taille de l'erreur

    GLint tailleErreur(0);
    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &tailleErreur);

    // Allocation de mémoire

    char *erreur = new char[tailleErreur + 1];
}

// Sinon c'est que tout s'est bien passé

else
    return true;

Maintenant que l'on a une chaine allouée, nous pouvons récupérer la fameuse erreur. Nous utiliserons pour cela la fonction glGetShaderInfoLog() :

void glGetShaderInfoLog(GLuint shader, GLsizei maxLength, GLsizei *length, GLchar *infoLog);

  • shader : Le shader sur lequel on travaille

  • maxLength : Taille de la chaine de caractère qui va accueillir l'erreur. Pour nous, il s'agit de la variable tailleErreur

  • length : Adresse de la variable qui contiendra la taille précédente. Je n'ai jamais compris l'utilité de ce paramètre mais bon, nous lui donnerons l'adresse de la variable tailleErreur

  • infolog : La chaine de caractère qui contiendra le message final. Nous lui donnerons la chaine erreur

L'appel à cette fonction ressemblera à ceci :

// Récupération de l'erreur

glGetShaderInfoLog(shader, tailleErreur, &tailleErreur, erreur);

Il faut également penser à rajouter le caractère de fin de chaine '\0' pour compléter le message :

// Récupération de l'erreur

glGetShaderInfoLog(shader, tailleErreur, &tailleErreur, erreur);
erreur[tailleErreur] = '\0';

Ce qui donne au final :

// Vérification de la compilation

GLint erreurCompilation(0);
glGetShaderiv(shader, GL_COMPILE_STATUS, &erreurCompilation);

// S'il y a eu une erreur

if(erreurCompilation != GL_TRUE)
{
    // Récupération de la taille de l'erreur

    GLint tailleErreur(0);
    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &tailleErreur);

    // Allocation de mémoire

    char *erreur = new char[tailleErreur + 1];

    // Récupération de l'erreur

    glGetShaderInfoLog(shader, tailleErreur, &tailleErreur, erreur);
    erreur[tailleErreur] = '\0';
}

// Sinon c'est que tout s'est bien passé

else
    return true;

On arrive à la fin courage. :p

Il ne nous reste plus qu'à afficher le message d'erreur et à libérer la mémoire prise par la chaine de caractère. On n'oublie pas de détruire le shader car celui-ci est inutilisable puis on retourne le booléen false pour terminer la méthode :

// Vérification de la compilation

GLint erreurCompilation(0);
glGetShaderiv(shader, GL_COMPILE_STATUS, &erreurCompilation);

// S'il y a eu une erreur

if(erreurCompilation != GL_TRUE)
{
    // Récupération de la taille de l'erreur

    GLint tailleErreur(0);
    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &tailleErreur);

    // Allocation de mémoire

    char *erreur = new char[tailleErreur + 1];

    // Récupération de l'erreur

    glGetShaderInfoLog(shader, tailleErreur, &tailleErreur, erreur);
    erreur[tailleErreur] = '\0';

    // Affichage de l'erreur

    std::cout << erreur << std::endl;

    // Libération de la mémoire et retour du booléen false

    delete[] erreur;
    glDeleteShader(shader);

    return false;
}

// Sinon c'est que tout s'est bien passé

else
    return true;

Pfiouu on est enfin arrivé au bout ... Tout ce code pour vérifier une simple erreur ! Et oui, je vous avais prévenus que le chargement des shaders était quelque chose d'assez folklo. :p

Faisons un petit récapitulatif final de la méthode compilerShader() :

bool Shader::compilerShader(GLuint &shader, GLenum type, std::string const &fichierSource)
{
    // Création du shader

    shader = glCreateShader(type);

    // Vérification du shader

    if(shader == 0)
    {
        std::cout << "Erreur, le type de shader (" << type << ") n'existe pas" << std::endl;
        return false;
    }

    // Flux de lecture

    std::ifstream fichier(fichierSource.c_str());

    // Test d'ouverture

    if(!fichier)
    {
        std::cout << "Erreur le fichier " << fichierSource << " est introuvable" << std::endl;
        glDeleteShader(shader);

        return false;
    }

    // Strings permettant de lire le code source

    std::string ligne;
    std::string codeSource;

    // Lecture

    while(getline(fichier, ligne))
        codeSource += ligne + '\n';

    // Fermeture du fichier

    fichier.close();

    // Récupération de la chaine C du code source

    const GLchar* chaineCodeSource = codeSource.c_str();

    // Envoi du code source au shader

    glShaderSource(shader, 1, &chaineCodeSource, 0);

    // Compilation du shader

    glCompileShader(shader);

    // Vérification de la compilation

    GLint erreurCompilation(0);
    glGetShaderiv(shader, GL_COMPILE_STATUS, &erreurCompilation);

    // S'il y a eu une erreur

    if(erreurCompilation != GL_TRUE)
    {
        // Récupération de la taille de l'erreur

        GLint tailleErreur(0);
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &tailleErreur);

        // Allocation de mémoire

        char *erreur = new char[tailleErreur + 1];

        // Récupération de l'erreur

        glGetShaderInfoLog(shader, tailleErreur, &tailleErreur, erreur);
        erreur[tailleErreur] = '\0';

        // Affichage de l'erreur

        std::cout << erreur << std::endl;

        // Libération de la mémoire et retour du booléen false

        delete[] erreur;
        glDeleteShader(shader);

        return false;
    }

    // Sinon c'est que tout s'est bien passé

    else
        return true;
}

Grâce à tout ce code, nous sommes maintenant capables de compiler tous nos shaders. Il valait mieux séparer ce code de la méthode charger() sinon il aurait fallu coder ça deux, imaginez un peu le truc ! :lol:

Cependant nous n'avons pas encore fini, il nous faut encore terminer la méthode charger(). Continuons. :)

Création du programme

La méthode charger (Partie 2/2)

Création du programme

Maintenant que nous avons codé une méthode permettant de compiler un shader, nous pouvons tranquillement retourner à la méthode charger() que nous avons laissée précédemment. D'ailleurs, elle est un peu vide pour le moment :

bool Shader::charger()
{
    // Compilation des shaders

    if(!compilerShader(m_vertexID, GL_VERTEX_SHADER, m_vertexSource))
        return false;

    if(!compilerShader(m_fragmentID, GL_FRAGMENT_SHADER, m_fragmentSource))
        return false;
}

Grâce à ce mini bout de code, nous avons enfin notre duo de shader compilé et validé. Cependant, ces shaders ne sont pas encore inutilisables pour le moment car ce ne sont que des objets intermédiaires qui ne peuvent pas être exécutés par la carte graphique, elle ne sait pas quoi faire avec. Pour régler le problème, il nous faut les réunir à l’intérieur d'un programme qui, lui, sera exécutable par la carte.

Bien entendu, il ne s'agit pas d'un programme classique que vous avez l'habitude d'exécuter sur votre ordinateur. :p C'est en fait un objet OpenGL qui va faire la passerelle entre les shaders et leur exécution au sein de la carte graphique. C'est pour cette raison que nous avons besoin du fameux attribut programID - attribut qu'on utilise depuis un bout de temps maintenant dans la fonction glUseProgram().

Pour la suite de la méthode charger(), il nous faut donc créer ce programme. Nous ferons cela grâce à une fonction qui ressemble fortement à glCreateShader() et qui s'appelle glCreateProgram() :

GLuint glCreateProgram();

C'est l'une des rares fonctions OpenGL qui ne demande aucun paramètre, profitez-en. :p Elle retourne juste l'ID d'un nouveau programme.

Nous l'appellerons donc pour créer un programme qui sera accessible via l'attribut m_programID :

bool Shader::charger()
{
    // Compilation des shaders

    if(!compilerShader(m_vertexID, GL_VERTEX_SHADER, m_vertexSource))
        return false;

    if(!compilerShader(m_fragmentID, GL_FRAGMENT_SHADER, m_fragmentSource))
        return false;

    // Création du programme

    m_programID = glCreateProgram();
}
Association des shaders au programme

Comme je vous l'ai dit précédemment, le Vertex et Fragment Shader sont inutilisables en l'état car ils ne peuvent pas être exécutés par la carte graphique. En revanche, le programme lui peut l'être. Il nous faut donc associer les shaders avec celui-ci de façon à ce que nos fichiers compilés puissent servir à quelque chose.

Cette étape d'association se fait très simplement puisqu'il suffit d'utiliser une seule fonction OpenGL : glAttachShader().

void glAttachShader(GLuint program, GLuint shader)

  • program : Le programme sur lequel on travaille

  • shader : Le shader à associer

Nous appellerons cette fonction deux fois pour associer nos deux shaders :

bool Shader::charger()
{
    // Compilation des shaders

    if(!compilerShader(m_vertexID, GL_VERTEX_SHADER, m_vertexSource))
        return false;

    if(!compilerShader(m_fragmentID, GL_FRAGMENT_SHADER, m_fragmentSource))
        return false;

    // Création du programme

    m_programID = glCreateProgram();

    // Association des shaders

    glAttachShader(m_programID, m_vertexID);
    glAttachShader(m_programID, m_fragmentID);
}

Étape terminée. :p

Verrouillage des entrées shader

Si je vous parle d'entrées shader, ça vous dit quelque chose ? Normalement vous devriez me répondre non. :p Et pourtant si je vous disais que vous savez déjà ce que c'est, vous me croiriez ?

En fait, les entrées shader sont tout simplement les tableaux Vertex Attrib que l'on utilise depuis le début du tuto ! ;) Et oui, les shaders ont besoin d'eux pour travailler sinon ils ne pourraient pas afficher grand chose. Les tableaux Vertex Attrib constituent donc leurs sources de données (vertices, couleurs, ...) que l'on appelle plus communément leurs entrées. On parle bien d'entrées parce qu'il existe aussi des sorties, mais ça ça sera pour le chapitre suivant.

Le verrouillage des entrées consiste simplement à dire au shader :

  • Le tableau Vertex Attrib 0 correspondra aux vertices

  • Le tableau Vertex Attrib 1 correspondra aux couleurs

  • ....

Pour faire ça, nous devons utiliser la fonction glBindAttribLocation() :

void glBindAttribLocation(GLuint program, GLuint index, const GLchar *name);

  • program : Le programme sur lequel on travaille

  • index : Le numéro du tableau Vertex Attrib à verrouiller

  • name : Le nom de la donnée dans le code source du shader

Le dernier paramètre peut vous sembler un peu confus pour le moment mais sachez juste qu'il s'agit d'une variable, dans le code source du shader, qui permet d'accéder aux données des tableaux Vertex Attrib. Par exemple, pour accéder aux vertices dans les shaders, nous utiliserons une variable qui s’appellera in_Vertex :

Pour les couleurs ce sera in_Color et pour les coordonnées de texture ce sera in_TexCoord0 :

Vu que nous utilisons 3 tableaux Vertex Attrib (vertices, couleurs et coordonnées de texture), nous devrons utiliser 3 fois la fonction glBindAttribLocation() pour verrouiller toutes les entrées. Pour le tableau d'indice 0, l'appel ressemblera à ceci :

// Verrouillage des entrées shader (Vertices)

glBindAttribLocation(m_programID, 0, "in_Vertex");

Pour les deux autres, ce sera :

// Verrouillage des entrées shader (Couleurs et Coordonnées de texture)

glBindAttribLocation(m_programID, 1, "in_Color");
glBindAttribLocation(m_programID, 2, "in_TexCoord0");

Euh j'ai une petite question, pourquoi il y a un 0 à la fin de la variable in_TexCoord0 ?

Ça peut paraitre un peu troublant mais le 0 est là pour indiquer au shader que les coordonnées de texture qu'il reçoit correspondent simplement à la première texture verrouillée (indice 0). Quand nous utiliserons le multitexturing, nous enverrons plusieurs textures au shader et celui-ci devra faire la différence entre les coordonnées reçues pour la première, celles reçues pour la deuxième, etc. Le chiffre de la variable in_TexCoord permet de faire cette différence. Enfin, ne vous triturez pas la tête avec ça pour le moment, ce n'est pas important. :)

Au final, nous plaçons les 3 appels à la fonction glBindAttribLocation() juste après l'association des shaders :

bool Shader::charger()
{
    // Compilation des shaders

    if(!compilerShader(m_vertexID, GL_VERTEX_SHADER, m_vertexSource))
        return false;

    if(!compilerShader(m_fragmentID, GL_FRAGMENT_SHADER, m_fragmentSource))
        return false;

    // Création du programme

    m_programID = glCreateProgram();

    // Association des shaders

    glAttachShader(m_programID, m_vertexID);
    glAttachShader(m_programID, m_fragmentID);

    // Verrouillage des entrées shader

    glBindAttribLocation(m_programID, 0, "in_Vertex");
    glBindAttribLocation(m_programID, 1, "in_Color");
    glBindAttribLocation(m_programID, 2, "in_TexCoord0");
}
Linkage

Le linkage constitue l'étape finale de la création d'un programme. Jusqu'à présent, nous avons compilé nos shaders séparément puis nous les avons associés à notre programme. Il ne reste plus maintenant qu'à le finaliser pour le rendre exécutable par la carte graphique.

Cette étape se fait grâce à la fonction glLinkProgram() :

void glLinkProgram(GLuint program);

Bien entendu, le paramètre program correspond au programme à linker. On appelle cette fonction juste après le verrouillage des entrées shader :

// Verrouillage des entrées shader

glBindAttribLocation(m_programID, 0, "in_Vertex");
glBindAttribLocation(m_programID, 1, "in_Color");
glBindAttribLocation(m_programID, 2, "in_TexCoord0");

// Linkage du programme

glLinkProgram(m_programID);
Vérification du linkage

Le linkage d'un programme est un processus qui peut malheureusement échouer, nous devons donc vérifier si tout s'est bien passé. La mauvaise nouvelle, c'est qu'il va falloir écrire un gros bloc de vérification comme pour la compilation de shader. :p

L'avantage cependant c'est que cet énorme bloc de vérification ressemble exactement au premier, seules quelques fonctions OpenGL vont devoir changer. Ainsi, pour vérifier si le linkage s'est bien passé nous devrons :

  • Vérifier s'il y a eu un problème

  • Si oui, récupérer la taille du message d'erreur

  • Allouer une chaine de caractère grâce à cette taille

  • Récupérer l'erreur et l'afficher

Pour la compilation de shader, nous avions utilisé la fonction glGetShaderiv() pour savoir s'il y avait eu une erreur. Pour le linkage d'un programme, la fonction est exactement identique, il n'y a que le nom qui change :

void glGetProgramiv(GLuint program, GLenum pname, GLint *params);

Les paramètres sont également identiques :

  • program : Le programme sur lequel on travaille

  • pname : Le nom de l'information demandée

  • params : L'adresse d'une variable qui accueillera cette information

On doit utiliser cette fonction, dans un premier temps, pour vérifier s'il y a eu une erreur au moment du linkage. Le nom du paramètre pname qui nous permet cela sera la constante GL_LINK_STATUS. On doit aussi créer une variable de type GLint pour contenir la valeur retournée, on l'appellera erreurLink. On en profite au passage pour implémenter que le début du bloc if :

// Linkage du programme

glLinkProgram(m_programID);

// Vérification du linkage

GLint erreurLink(0);
glGetProgramiv(m_programID, GL_LINK_STATUS, &erreurLink);

// S'il y a eu une erreur

if(erreurLink != GL_TRUE)
{
}

// Sinon c'est que tout s'est bien passé

else
    return true;

Si on se retrouve dans le bloc if, c'est que quelque chose s'est mal passé. Nous devons donc récupérer la taille du message d'erreur grâce à la fonction glGetProgramiv(). Elle prendra exactement les mêmes paramètres que la fonction glGetShaderiv() soient : une constante GL_INFO_LOG_LENGTH et l'adresse d'une variable tailleErreur de type GLint :

// Vérification du linkage

if(erreurLink != GL_TRUE)
{
    // Récupération de la taille de l'erreur

    GLint tailleErreur(0);
    glGetProgramiv(m_programID, GL_INFO_LOG_LENGTH, &tailleErreur);
}

Une fois la taille de l'erreur récupérée, on peut allouer une chaine de caractère pour la contenir sans oublier le caractère '\0' :

// Vérification du linkage

if(erreurLink != GL_TRUE)
{
    // Récupération de la taille de l'erreur

    GLint tailleErreur(0);
    glGetProgramiv(m_programID, GL_INFO_LOG_LENGTH, &tailleErreur);

    // Allocation de mémoire

    char *erreur = new char[tailleErreur + 1];
}

Maintenant que l'on a une chaine de caractère assez grande, on peut récupérer le message d'erreur grâce à une fonction très similaire à glGetShaderInfoLog() qui s'appelle glGetProgramInfoLog() :

void glGetProgramInfoLog(GLuint program, GLsizei maxLength, GLsizei *length, GLchar *infoLog);

Je vous rappelle que cette fonction permet de récupérer le message d'erreur. ;) D'ailleurs, elle prend elle-aussi les mêmes paramètres que sa sœur-jumelle :

  • program : Le programme sur lequel on travaille

  • maxLength : Taille de la chaine de caractère qui va accueillir l'erreur. Pour nous, il s'agit de la variable tailleErreur

  • length : Adresse de la variable qui contiendra la taille précédente. Ce paramètre est encore une fois bizarre, nous lui donnerons l'adresse de la variable tailleErreur

  • infoLog : La chaine de caractère qui contiendra le message final. Nous lui donnerons la chaine erreur

Nous appelons donc cette fonction avec les paramètres que nous venons juste de citer. N'oubliez pas de rajouter le caractère '\0' à la fin de la chaine :

// Vérification du linkage

if(erreurLink != GL_TRUE)
{
    // Récupération de la taille de l'erreur

    GLint tailleErreur(0);
    glGetProgramiv(m_programID, GL_INFO_LOG_LENGTH, &tailleErreur);

    // Allocation de mémoire

    char *erreur = new char[tailleErreur + 1];

    // Récupération de l'erreur

    glGetShaderInfoLog(m_programID, tailleErreur, &tailleErreur, erreur);
    erreur[tailleErreur] = '\0';
}

Pour terminer cette gestion d'erreur, il ne reste plus qu'à afficher le message tant convoité. Une fois que c'est fait, on libère la mémoire prise par la chaine de caractère puis on détruit le programme vu que celui-ci est inutilisable.

La fonction permettant de détruire un programme s'appelle simplement glDeleteProgram() :

void glDeleteProgram(GLuint program);

Elle prend en paramètre le programme à détruire.

On appelle donc cette fonction en plus du mot-clef delete[] pour détruire la chaine. On renverra également le booléen false pour indiquer que le chargement s'est mal passé :

// Vérification du linkage

if(erreurLink != GL_TRUE)
{
    // Récupération de la taille de l'erreur

    GLint tailleErreur(0);
    glGetProgramiv(m_programID, GL_INFO_LOG_LENGTH, &tailleErreur);

    // Allocation de mémoire

    char *erreur = new char[tailleErreur + 1];

    // Récupération de l'erreur

    glGetShaderInfoLog(m_programID, tailleErreur, &tailleErreur, erreur);
    erreur[tailleErreur] = '\0';

    // Affichage de l'erreur

    std::cout << erreur << std::endl;

    // Libération de la mémoire et retour du booléen false

    delete[] erreur;
    glDeleteProgram(m_programID);

    return false;
}

Récapitulatif de la méthode charger() :

bool Shader::charger()
{
    // Compilation des shaders

    if(!compilerShader(m_vertexID, GL_VERTEX_SHADER, m_vertexSource))
        return false;

    if(!compilerShader(m_fragmentID, GL_FRAGMENT_SHADER, m_fragmentSource))
        return false;

    // Création du programme

    m_programID = glCreateProgram();

    // Association des shaders

    glAttachShader(m_programID, m_vertexID);
    glAttachShader(m_programID, m_fragmentID);

    // Verrouillage des entrées shader

    glBindAttribLocation(m_programID, 0, "in_Vertex");
    glBindAttribLocation(m_programID, 1, "in_Color");
    glBindAttribLocation(m_programID, 2, "in_TexCoord0");

    // Linkage du programme

    glLinkProgram(m_programID);

    // Linkage du programme

    glLinkProgram(m_programID);

    // Vérification du linkage

    GLint erreurLink(0);
    glGetProgramiv(m_programID, GL_LINK_STATUS, &erreurLink);

    // S'il y a eu une erreur

    if(erreurLink != GL_TRUE)
    {
        // Récupération de la taille de l'erreur

        GLint tailleErreur(0);
        glGetProgramiv(m_programID, GL_INFO_LOG_LENGTH, &tailleErreur);

        // Allocation de mémoire

        char *erreur = new char[tailleErreur + 1];

        // Récupération de l'erreur

        glGetShaderInfoLog(m_programID, tailleErreur, &tailleErreur, erreur);
        erreur[tailleErreur] = '\0';

        // Affichage de l'erreur

        std::cout << erreur << std::endl;

        // Libération de la mémoire et retour du booléen false

        delete[] erreur;
        glDeleteProgram(m_programID);

        return false;
    }

    // Sinon c'est que tout s'est bien passé

    else
        return true;
}

Cette fois la méthode est bel et bien terminée, et je dirai même que l'implémentation de la classe Shader est également terminée. Vous êtes maintenant en mesure de comprendre tout le code C++ de vos applications, plus de classe mystère !

Après toutes ces péripéties, vous avez bien mérité une petite pause moi j'dis. :-°

D'ailleurs, vous pouvez continuer à lire la dernière partie de ce chapitre tout en faisant une pause car nous n'allons voir que quelques points mineurs.

Utilisation et améliorations

Utilisation

La partie utilisation est vraiment quelque chose d’anecdotique car vous savez déjà comment utiliser un shader, mais nous allons tout de même faire un petit point dessus. Après tout, les rappels ça n'a jamais fait de mal. :p

Enfin bref. Pour créer un objet de type Shader, vous devez simplement déclarer un nouvel objet en lui donnant en paramètre le chemin vers deux codes sources : celui du Vertex Shader et celui du Fragment Shader, puis d'appeler la méthode charger(). Par exemple, pour charger le shader de texture :

// Shader Texture

Shader shaderTexture("Shaders/texture.vert", "Shaders/texture.frag");
shaderTexture.charger();

Une fois chargé, vous n'avez plus qu'à l'activer au moment d'afficher votre modèle grâce à la fonction glUseProgram() :

void glUseProgram(GLuint program);

La paramètre program correspond évidemment au shader à activer.

Cette fonction est vraiment similaire aux fonctions du type glBindXXX() puisqu'elle permet de dire à OpenGL : "Je souhaite utiliser ce shader-ci pendant toute la durée où il est activé". Ainsi, tous les modèles qui se trouvent à l'intérieur des deux appels à glUseProgram() seront obligés de l'utiliser pour être affichés.

// Activation du shader

glUseProgram(m_shader.getProgramID());

    // Verrouillage du VAO

    glBindVertexArray(m_vaoID);

        // Envoi des matrices

        glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));
        glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));

        // Verrouillage de la texture

        glBindTexture(GL_TEXTURE_2D, m_texture.getID());

        // Rendu

        glDrawArrays(GL_TRIANGLES, 0, 36);

        // Déverrouillage de la texture

        glBindTexture(GL_TEXTURE_2D, 0);

    // Déverrouillage du VAO

    glBindVertexArray(0);

// Désactivation du shader

glUseProgram(0);

D'ailleurs en parlant de ça, je vous conseille fortement d'activer votre shader avant de verrouiller tous vos autres objets OpenGL. Si vous ne le faites pas, vous risquerez d'envoyer des données dans le vide (comme les matrices ou les textures) car aucun shader ne sera là pour les recevoir. ;)

Les dernières lignes de code C++ qui peuvent vous gêner sont celles de l'envoi des matrices. Nous allons les voir dans le prochain chapitre car elles font partie intégrante de l'utilisation des shaders.

Améliorations

Le destructeur

Dans cette petite partie améliorations, nous allons surtout parler de méthodes qui seront utiles si nous voulons copier des objets de type Shader entre eux. Mais avant ça, nous allons parler un peu de libération de mémoire car jusqu'à maintenant, lorsqu'un objet Shader est détruit, il ne libère à aucun moment les ressources qu'il a prises.

Pour l'aider à le faire, nous allons simplement détruire tous les attributs dans le destructeur. Sachant que les string savent le faire toutes seules, il ne reste plus qu'à gérer les attributs m_vertexID, m_fragmentID et m_programID. Les fonctions permettant de détruire ces objets OpenGL sont glDeleteShader() et glDeleteProgram().

Nous les appelons donc le destructeur :

Shader::~Shader()
{
    // Destruction du shader

    glDeleteShader(m_vertexID);
    glDeleteShader(m_fragmentID);
    glDeleteProgram(m_programID);
}

Comme quoi, le destructeur ne reste pas vide à chaque fois. :p

Le constructeur de copie

On continue cette dernière partie avec le constructeur de copie. J'espère que vous savez ce que c'est quand même, surtout qu'on l'utilise depuis un moment maintenant. :p

En temps normal, il est inutile de le coder tant qu'on n'utilise pas de pointeurs car seuls eux peuvent poser des problèmes. Cependant, les objets OpenGL peuvent être rapprochés aux pointeurs car leur ID se comporte un peu de la même façon. Si vous essayez de copier un ID dans un autre objet, alors les deux copies auront accès exactement aux mêmes données alors qu'ils devraient avoir chacun les leurs.

Il nous faut ré-écrire ce constructeur pour pouvoir être capables de copier deux shaders correctement sans avoir à nous soucier de ce problème. Voici d'ailleurs le prototype de cette pseudo-méthode :

Shader(Shader const &shaderACopier);

La référence constante est importante ici, ne l'oubliez pas.

Pour la classe Shader, nous avons 5 attributs à gérer. Les deux strings peuvent être copiées directement sans problème :

Shader::Shader(Shader const &shaderACopier)
{
    // Copie des fichiers sources

    m_vertexSource = shaderACopier.m_vertexSource;
    m_fragmentSource = shaderACopier.m_fragmentSource;
}

Les 3 derniers attributs concernent les shaders et le programme. Ils ne peuvent pas être copiés de la même façon, il va falloir re-générer de nouvelles valeurs (de nouveaux ID) à partir des fichiers sources. Pour ce faire, il suffit juste d'appeler la méthode charger() car c'est elle qui s'occupe de ça :

Shader::Shader(Shader const &shaderACopier)
{
    // Copie des fichiers sources

    m_vertexSource = shaderACopier.m_vertexSource;
    m_fragmentSource = shaderACopier.m_fragmentSource;

    // Chargement du nouveau shader

    charger();
}

Le constructeur de copie est terminé. ;)

L'opérateur =

L'opérateur d'affectation = est un opérateur très pratique en C++ car il permet de copier un objet directement en utilisant le symbole = comme si on faisait une banale opération arithmétique. Nous allons l'implémenter, au même titre que le constructeur de copie, dans le cas où nous aurions à copier un shader.

Voici son prototype :

Shader& operator=(Shader const &shaderACopier);

Cette méthode prend et renvoie une référence constante.

Son contenu est strictement identique au constructeur de copie car elle fait exactement la même chose. :p On rajoutera juste le retour du pointeur *this pour retourner proprement notre objet :

Shader& Shader::operator=(Shader const &shaderACopier)
{
    // Copie des fichiers sources

    m_vertexSource = shaderACopier.m_vertexSource;
    m_fragmentSource = shaderACopier.m_fragmentSource;

    // Chargement du nouveau shader

    charger();

    // Retour du pointeur this

    return *this;
}

Encore une fois, je vous repose la question pour ce chapitre ( :p ) : vous vous souvenez de ce qui se passait si on chargeait deux fois un même objet OpenGL (texture, VBO, etc.) d'un coup ? Que cela entrainait une fuite de mémoire ? Et bien nous avons encore le même problème ici. Si vous chargez deux fois un shader, lors d'une copie par exemple, alors vous risquez de gâcher de la mémoire qui ne pourra pas être libérée avant la fermeture de votre application.

Pour éviter cela, nous devons appeler les fonctions de vérification d'objet OpenGL du type glIsXXX(), puis de les détruire si besoin est. Elles s'appellent ici glIsShader() et glIsProgram() :

// Vérification de shader

GLboolean glIsShader(GLuint shader);

// Vérification de programme

GLboolean glIsProgram(GLuint program);

Elles prennent en paramètre respectivement un identifiant de shader et un identifiant de programme pour savoir s'ils ont déjà été chargés. Elles renvoient toutes les deux la valeur GL_TRUE si c'est le cas, sinon c'est GL_FALSE.

Au final, pour les attributs m_vertexID, m_fragmentID et m_programID, nous devons faire un bloc if en vérifiant la valeur retournée par la fonction glIsXXX(). Si c'est GL_TRUE, alors on appelle leur fonction de destruction :

bool Shader::charger()
{
    // Destruction d'un éventuel ancien Shader

    if(glIsShader(m_vertexID) == GL_TRUE)
        glDeleteShader(m_vertexID);

    if(glIsShader(m_fragmentID) == GL_TRUE)
        glDeleteShader(m_fragmentID);

    if(glIsProgram(m_programID) == GL_TRUE)
        glDeleteProgram(m_programID);

    // Compilation des shaders

    if(!compilerShader(vertexShader, GL_VERTEX_SHADER, m_vertexSource)
        return false;

    if(!compilerShader(fragmentShader, GL_FRAGMENT_SHADER, m_fragmentSource)
        return false;

    ....
}

Grâce à ce bout de code, on évite toute fuite de mémoire car les éventuels anciens ID seront détruits avant d'être ré-initialisés.

Télécharger (Windows, UNIX/Linux, Mac OS X) : Code Source C++ du chapitre sur la compilation de Shader

A travers ce premier chapitre consacré aux shaders, nous avons pu voir comment compiler n'importe quel code source. Tous ceux que nous avons utilisés depuis la partie 1 sont passés par cette classe - classe dont nous sommes enfin capables de comprendre le fonctionnement.

Les notions que nous avons abordées sont assez costauds au niveau technique, je vous conseille de relire à tête reposée ceux qui vous paraissent encore un peu sombres.

Néanmoins, après le code de ce pseudo-compilateur, nous pouvons enfin passer aux codes sources mêmes des shaders. Les 3 prochains chapitres leur seront entièrement consacrés. Refaite le stock de café ou de chocolat moi j'dis ! :p


Développez vos applications 3D avec OpenGL 3.3

OpenGL Shading Language

Dans le chapitre précédent, nous avons vu ce qu'étaient les shaders et comment les implémenter au sein de nos applications. Nous avons même écrit, en quelque sorte, notre propre compilateur qui permet désormais de compiler absolument tous leurs codes sources. :p

Aujourd'hui, nous allons justement passer aux codes sources mêmes et étudier le langage de programmation avec lequel ils sont faits : l'OpenGL Shading Language. Nous nous concentrerons principalement sur sa syntaxe qui se rapproche fortement de celle du C mais qui contient tout de même quelques différences.

Si vous vous sentez prêts alors on peut commencer. :magicien:

Un peu d'histoire

L’histoire des shaders

La création

Ce qu'il faut savoir avec les shaders, c'est que leur intégration au sein des jeux-vidéo est une chose assez récente par rapport à leur création. En effet, ceux-ci ont été créés en 1988 par les studios d'animation Pixar qui avaient remarqué qu'il était possible de traiter tous les pixels un par un pour leur appliquer des instructions spécifiques. Cela leur permettait de manipuler leurs rendus non seulement pour les améliorer mais aussi pour les rendre plus réalistes sans surcharger les ressources des ordinateurs.

Le premier standard à utiliser les shaders est le RenderMan (RIS) développé par les studios du même nom. Depuis, ils se sont développés dans tous les domaines qui traitent la 3D tels que les films d'animation, le cinéma et évidemment les jeux-vidéo. Ils permettent aujourd'hui d'exploiter efficacement la carte graphique pour effectuer une multitude de calculs mathématiques. Leur plus grand intérêt est évidemment la réalisation d'effets réalistes que l'on retrouve aujourd'hui dans tous les jeux-vidéo à la mode. ;)

Au niveau de leur fonctionnement, vous savez maintenant que les shaders sont des petits programmes qui vont traiter les vertices et les pixels. Ils sont spécialement conçus pour faire ce genre d'opération, ils n'ont donc pas peur de traiter des millions de données à chaque seconde. :p

Les différents langages

Si les shaders sont des programmes alors ils possèdent forcément un code source pour savoir ce qu'ils doivent faire. Et évidemment, ces codes sources doivent être écrits avec un langage de programmation. Depuis 1988, plusieurs entreprises ont tenté de sortir leur propre langage soit pour s'imposer dans le domaine, soit pour un type d'utilisation spécifique. Certains sont plus ou moins connus et même plus ou moins compliqués à utiliser.

Il existe deux types de langage utilisables pour la programmation des shaders. Jusqu'à récemment, il fallait en utiliser certains qui étaient proches de l'Assembleur, ils étaient donc considérés comme étant de bas-niveau. Pour ceux qui ne savent pas ce qu'est l'Assembleur, sachez qu'il s'agit d'un langage de programmation extrêmement difficile à manier pour les non-initiés. Si vous voulez faire un jeu-vidéo 3D avec ça je vous conseille de vous lever très tôt chaque matin pour avancer dans le développement. :p

Mais heureusement pour nous, il existe aussi des langages dits de haut-niveau, donc plus faciles à utiliser, qui se rapprochent fortement de ceux que l'on connait aujourd'hui. Nous pouvons en citer principalement 3 :

  • OpenGL Shading Language (GLSL) : créé par le consortium Architecture Review Board qui gère OpenGL, il est évidemment compatible avec cet API et en est même le standard actuel

  • C for Graphics (Cg) : créé par Nvidia, il est compatible non seulement avec OpenGL mais aussi avec son concurrent Microsoft Direct3D

  • High Level Shader Language (HLSL) : créé par Microsoft, il est uniquement compatible avec Direct3D

Ces langages ne sont pas compatibles avec toutes les API 3D mais ils sont tous utilisés dans le monde du jeu-vidéo. Vu que nous, nous programmons avec OpenGL, nous allons donc utiliser l'OpenGL Shading Language (GLSL) pour développer nos shaders.

OpenGL Shading Language

Le GLSL est donc un langage de programmation créé par l'Architecture Review Board pour permettre aux programmeurs d'utiliser directement la puissance des cartes graphiques sans passer par l'ancien pipeline 3D. Cela permet de gagner en rapidité car on évite tous les calculs superflus qui existaient auparavant. On gagne également en flexibilité car on peut traiter chaque pixel individuellement.

L'avantage de ce langage c'est que d'une part il est multiplateforme, c'est-à-dire qu'il est supporté par la majorité des cartes graphiques, et d'autre part sa syntaxe se rapproche énormément de celle du C. Il est plus facile d'apprendre à l'utiliser lui plutôt que d'apprendre l'Assembleur. :p

Malgré sa notoriété, le GLSL n'est né que très récemment. Comparé à la création des shaders qui date de 1988, il fait office de bambin puisqu'il n'a été accepté officiellement qu'en 2004 avec la sortie d'OpenGL 2.0. Pour information, la première version d'OpenGL est sortie en 1992, ce qui fait 12 ans de décalage entre les deux ! :lol:

Cependant, durant sa courte histoire, le GLSL n'a pas chômé puisqu'il a fournit pas moins de 10 versions en l'espace de 8 ans. En voici quelques unes :

  • 1.20 : sortie avec OpenGL 2.1, c'est la version la plus connue à ce jour.

  • 1.30 : sortie avec OpenGL 3.0, elle introduit une nouvelle façon de programmer et déprécie certaines fonctionnalités (même s'il reste toujours possible de les utiliser).

  • 1.40 : sortie avec OpenGL 3.1. Cette fois-ci, les fonctionnalités dépréciées sont totalement supprimées, la nouvelle façon de programmer devient le nouveau standard.

  • 1.50 : sortie avec OpenGL 3.2, nous utiliserons celle-ci car c'est la plus haute version avec laquelle nous pourrons programmer de façon 'multiplateforme'. Au-delà, certains OS comme Mac OS X ne supportent plus le langage.

Contrairement à ce qu'on pourrait penser, il n'existe pas de version 1.60, 1.70 ... A partir d'OpenGL 3.3, le numéro des versions devient le même que celui de l'API. Pour OpenGL 3.3 par exemple, le GLSL passe en 3.30, pour la 4.0 il passe en 4.0, ...

Enfin, vous savez maintenant ce que sont les langages de programmation shader et vous avez même eu le droit à un petit cours d'histoire pour votre culture générale. :p

Pour la suite du tutoriel, nous utiliserons le GLSL dans sa version 1.50. C'est avec lui que nous serons bientôt capables de faire des effets réalistes que l'on pourra intégrer dans nos scènes 3D. D'ailleurs, nous allons tout de suite passer à l'étude de ce langage en commençant par sa syntaxe de base.

Les variables

Le code source minimal

Comme nous l'avons vu dans l'introduction, l'OpenGL Shading Language est fortement inspiré du C, vous n'avez donc pas à être effrayés par l'apprentissage d'un nouveau langage de programmation.

Dans cette partie, nous nous concentrerons principalement sur sa syntaxe avec la gestion des variables, les fonctions, etc. Je ferai en sorte de vous montrer des exemples à chaque fois et nous en profiterons pour faire quelques exercices.

Je n'ai malheureusement pas d'IDE à vous proposer pour coder vos shaders. Par habitude, j'utilise toujours celui pour coder les applications classiques et je ne connais que très peu les autres pour pouvoir vous les conseiller efficacement. Si vous voulez ne pas être embêtés à ouvrir plusieurs programmes, je vous propose de coder également sur le même IDE que vous utilisez en temps normal.

Enfin maintenant que l'on sait tout ce que l'on à savoir, nous pouvons enfin commencer l'étude du GLSL. La première chose que nous allons voir concerne le code source minimal demandé par un shader pour pouvoir être compilé. Celui-ci est assez simple mais mérite tout de même quelques explications :

void main()
{

}

Un peu cours n'est-ce pas ? :lol:

Le code minimal contient donc simplement une fonction qui main() qui ne prend et qui ne renvoie aucun paramètre. Remarquez que la syntaxe des fonctions est strictement identiques au C. Si vous essayez de compiler ce code vous n'aurez aucun message d'erreur, le shader l'acceptera sans problème mais il n'affichera pas grand chose.

La fonction main() a la même utilité qu'en C, c'est elle qui est appelée en première au lancement d'un programme. Nous placerons donc nos premières futures instructions à l'intérieur. Cependant, contrairement au C cette fonction n'a pas besoin de renvoyer une variable pour indiquer si tout s'est bien passé (le fameux return 0;).

En définitif, ce code constitue la source minimale pour compiler un shader, nous devrons donc implémenter la fonction main() à chaque fois. ;)

Les Variables

Les variables 'classiques'

Comme tout tutoriel sur les langages de programmation, nous allons nous attaquer en premier lieu aux variables.

Il existe plusieurs types de variable avec le GLSL dont certains sont assez déroutants car ils n'ont pas leur équivalent en C. On va commencer par voir ceux qu'on a l'habitude d'utiliser car ce sont les plus simples à comprendre :

  • bool : booléen pouvant prendre la valeur true ou false

  • int : les nombres entiers

  • uint : les nombres entiers non-signés

  • float : les nombres flottants (décimaux)

Les types char, short, long et double n'existent pas. Ne cherchez donc pas à les utiliser ou votre pseudo-compilateur va vous râler dessus. :p

L'avantage des variables en GLSL c'est qu'elles se comportent exactement de la même façon qu'en C. Nous pouvons donc les additionner, les multiplier, leur affecter une valeur, etc. Le seul point auquel il faut faire attention c'est leur initialisation qui ne se fait non pas à l'aide de parenthèses mais avec le signe = .

Prenons un petit exemple en déclarant deux variables de type int, puis additionnons-les :

void main()
{
    // Déclaration de deux variables integer

    int mesChats = 5;
    int chatDuVoisin = 1;

    // Addition

    int resultat = mesChats + chatDuVoisin;
}

Ce code pourrait parfaitement servir à un programme normal. :p

Vous voyez ici que la déclaration et l'utilisation de variable se passent de la même manière qu'en C, chaque instruction se termine même par un point-virgule. Tout ce que vous connaissez sur les variables s'applique au GLSL, à savoir :

  • Les opérations arithmétiques

  • Les affectations

  • L'incrémentation et la décrémentation (++ et --)

  • ...

Vous avez peut-être même remarqué l'utilisation des commentaires dans le code source. Leur syntaxe reste aussi la même :

void main()
{
    // Petit commentaire

    /* Gros commentaire,
       Parce que j'ai beaucoup de chose à dire */
}
Les tableaux

Les tableaux, qui permettent de rassembler des données du même type en mémoire, sont aussi utilisables avec les shaders. Ils se comportent aussi de la même manière, ils possèdent donc une taille et ses cases sont accessibles à l'aide un indice.

Pour initialiser un tableau, il suffit de lui donner un nom ainsi qu'une taille entre crochets. Pour accéder ou affecter une valeur aux cases, on utilise également les crochets avec un indice qui peut commencer à 0 ou qui peut se terminer par la taille - 1.

Voici un petit exemple de l'utilisation d'un tableau de 3 flottants :

void main()
{
    // Déclaration d'un tableau

    float tableau[3] = {1.0, 2.0, 3.0};

    // Utilisation

    float premiereCase = tableau[0];
}

Les tableaux multidimensionnels du type monTableau[0][0] ne peuvent pas être utilisés.

Les vecteurs

Les variables que nous venons de voir ont l'avantage d'être faciles à comprendre car nous avons l'habitude de les utiliser dans nos codes sources classiques. Cependant, il existe encore d'autres types propres au GLSL qui sont un peu spéciaux. D'ailleurs, je dois vous avouer que vous les connaissez déjà en fait. :p Ces derniers peuvent être comparés aux structures du C mais ce sont bel et bien des types de variable.

Ils ont été créés pour faciliter la vie aux développeurs car ils permettent de faire pas mal de calculs mathématiques relatifs à la 3D sans se prendre la tête. Si je vous dis que vous les connaissez déjà c'est parce que la librairie GLM que l'on utilise depuis le début du tuto est directement inspiré du GLSL et donc de ces types de variable. C'est à dire que l'on retrouve des types communs entre les deux, même s'il s'agit d'un objet d'un coté et d'une variable de l'autre.

Le premier de ces types concerne les vecteurs, il se décline en plusieurs versions :

  • vec2 : vecteur à 2 coordonnées

  • vec3 : vecteur à 3 coordonnées

  • vec4 : vecteur à 4 coordonnées

Ces types-la sont ceux que nous utiliserons la plus avec les shaders, retenez donc bien ce que nous allons voir sur eux.

Le type vec3 devrait vous dire quelque chose il me semble. :p Et oui, l'objet vec3 qu'on utilise dans l'application C++ est inspiré directement du type de variable vec3.

Ces variables en GLSL se rapproche plus à des structures C car ils contiennent des sortes de "sous-variable". Par exemple, le type vec2 possède deux coordonnées (x, y) qui sont accessibles via l'utilisation du point '.' tout comme les structures :

void main()
{
    // Vecteur à 2 coordonnées

    vec2 monVecteur;

    // Accès aux coordonnées

    monVecteur.x = 1.0;
    monVecteur.y = 2.0;
}

Bien entendu, on peut affecter des valeurs à ces coordonnées. Elles n'auraient que bien peu d'intérêt sinon. :p Ces valeurs doivent être des float et non des int.

Les types vec3 et vec4 ont eux-aussi les coordonnées (x, y) sauf qu'ils en possèdent respectivement 1 (x, y, z) et 2 (x, y, z, w) en plus.

Petit détail : le w se trouve en dernière position et ne vient donc qu'après le z, faites attention à ça.

L'accès à ces nouvelles coordonnées se fait exactement de la même façon ben sûr :

void main()
{
    // Vecteurs à 3 et 4 coordonnées

    vec3 monVecteur;
    vec4 monGrandVecteur;

    // Accès aux coordonnées du premier vecteur

    monVecteur.x = 1.0;
    monVecteur.y = 2.0;
    monVecteur.z = 3.0;

    // Accès aux coordonnées du second vecteur

    monGrandVecteur.x = 1.0;
    monGrandVecteur.y = 2.0;
    monGrandVecteur.z = 3.0;
    monGrandVecteur.w = 4.0;
}

Vu que ces vecteurs sont des variables comme les autres, nous pouvons donc utiliser les opérateurs arithmétiques sur eux comme le +, -, *, etc :

void main()
{
    // Vecteurs

    vec3 position, orientation;

    // Affectation de valeur

    position.x = 10.0;
    orientation.x = 5.0;
    ....

    // Opérations

    vec3 resultat = position + orientation;
    resultat *= 0.5;
}

Comme d'habitude avec les vecteurs, vous devez faire attention au nombre de coordonnées avant d'effectuer des opérations entre eux. Une variable vec2 ne peut pas être multipliée par un vec4 par exemple.

Petit bonus : il est possible de faire des tableaux de vecteur vu que ce sont des variables (j'espère vous l'avoir assez rabâché [image] ) :

void main()
{
    // Vecteurs

    vec4 vecteur1, vecteur2;

    // Tableau

    vec4 montableau[2] = {vecteur1, vecteur2};
}
Les matrices

Aaaaah les matrices, je suis sûr que vous êtes tous contents de les retrouver. :p Surtout que vous pouvez l'être car le GLSL nous fournit très gentiment des types de variable qui devraient vous dire quelque chose.

Les matrices sont des outils mathématiques incontournables dans la 3D. Il était donc évident de les intégrer dans le langage de programmation des shaders, au même titre que les vecteurs. Elles sont tellement importantes qu'elles possèdent même plusieurs types de variable. Voici ceux que l'on utilisera le plus :

  • mat2 : matrice carrée d'ordre 2

  • mat3 : matrice carrée d'ordre 3

  • mat4 : matrice carrée d'ordre 4

Hum mat4, moi j'dis ça me rappellerait quelque chose. :-°

Les matrices en GLSL se déclarent simplement : il suffit d'utiliser un des types précédents suivi du nom qu'on veut donner.

void main()
{
    // Matrice carrée d'ordre 3

    mat3 matrice;
}

Leur principal avantage c'est qu'elles sont AUSSI considérées comme des variables tout comme les vecteurs. Il est donc possible de faire des opérations arithmétiques dessus :

void main()
{
    // Matrices

    mat3 matrice1, matrice2;

    // Multiplication

    mat3 resultat = matrice1 * matrice2;
}

Simple n'est-ce pas ?

Je ne vous parle pas de la façon d'accéder aux valeurs des matrices car elles fonctionnement d'une manière différente de celle que l'on connait. En fait, elles se lisent colonne par colonne plutôt que ligne par ligne. Nous verrons cela dans un prochain chapitre pour éviter de s'embrouiller. ;) En revanche, nous allons voir dans cette partie la façon de les initialiser.

Enfin, vous connaissez maintenant tous les types de variable qu'il existe en GLSL. Ou plutôt presque tous car il en existe encore plein d'autres comme les vecteurs d'entier (ivec), les matrices non-carrées (mat3x4), etc. Je ne vous ai présenté ici que ceux que nous utiliserons le plus, le reste est plus anecdotique.

Les constructeurs

L'initialisation simple

Les constructeurs sont des outils très puissants et très flexibles qui vont nous permettre de faire gagner pas mal à temps à nos shaders. C'est une des seuls points que l'on peut rapprocher au C++, même s'il ne s'agit pas de réel constructeur de classe car la notion d'objet n'existe pas en GLSL.

Leur utilité vient du fait qu'ils permettent d'initialiser les variables (vecteurs et matrices compris) en une seule ligne de code. Il devient alors inutile d'affecter toutes les valeurs à la main. Le nom du constructeur à utiliser correspond au type de variable à initialiser.

Par exemple, si on veut initialiser une variable vec3 avec les coordonnées (1.0, 2.0, 3.0), il suffit de faire :

void main()
{
    // Initialisation d'un vecteur à 3 coordonnées

    vec3 vecteur = vec3(1.0, 2.0, 3.0);
}

C'est exactement la même chose qu'avec GLM. Ce qui est normal d'ailleurs puisque les développeurs de cette librairie ont fait en sorte que le comportement C++ des objets se rapproche au maximum de celui des variables en GLSL. Même l'utilisation faite des constructeurs est recopiée.

Ce constructeur nous évite au final d'avoir à faire :

void main()
{
    // Initialisation d'un vecteur à 3 coordonnées

    vec3 vecteur;

    vecteur.x = 1.0;
    vecteur.y = 2.0;
    vecteur.z = 3.0;
}

Les constructeurs font très plaisir quand vous avez plusieurs vecteurs à initialiser dans votre shader, croyez-moi. :p

Petit bonus : Si vous voulez initialiser un vecteur nul, vous n'avez pas besoin de renseigner toutes les coordonnées. Seule la valeur 0.0 sera nécessaire, le constructeur saura qu'il doit tout initialiser avec :

void main()
{
    // Initialisation d'un vecteur nul

    vec4 vecteur = vec4(0.0);
}

Les vecteurs ne sont pas les seuls concernés par les constructeurs, les matrices aussi ont le droit d'en profiter. Voici, par exemple, comment initialiser une matrice carrée d'ordre 3 :

void main()
{
    // Initialisation d'une matrice

    mat3 matrice = mat3(0.0, 3.0, 6.0,
                        1.0, 4.0, 7.0,
                        2.0, 5.0, 8.0);
}

J'ai volontairement fait un retour à la ligne toutes les 3 valeurs pour que le code soit plus lisible, il est évidemment possible de tout mettre en une seule.

On remarque dans cet exemple la présence de 9 valeurs allant de 0.0 à 8.0 qui permettent d'initialiser la matrice. Si on avait utilisé un type mat4, alors il en aurait fallu 16. On remarque aussi que les valeurs sont arrangées en colonnes. Si vous suivez les valeurs des yeux dans l'ordre croissant, vous remarquerez cette particularité.

C'est compliqué d'utiliser les matrices comme ça, je vais devoir m'adapter à chaque fois que je vais m'en servir ?

Non pas vraiment car elles seront la plupart du temps déjà données. Nous n'aurons donc pas à jouer avec cette lecture en colonne. ;)

Petit bonus : pour initialiser une matrice nulle, c'est-à-dire qu'avec des valeurs égales à 0.0, il existe heureusement une astuce qui nous évite tous les petits soucis précédents. En fait, il suffit juste de mettre une seule valeur 0.0 dans le constructeur comme pour les vecteurs :

void main()
{
    // Matrice nulle

    mat4 matrice = mat4(0.0);
}

Plus simple que l'exemple précédent n'est-ce pas ? :p

Dernier bonus : si vous voulez initialiser une matrice d'identité, vous pouvez faire exactement la même chose qu'avec GLM en appelant le constructeur avec la valeur 1.0. Ce dernier saura automatiquement qu'il doit affecter cette valeur à la diagonale :

void main()
{
    // Matrice d'identité

    mat4 matrice = mat4(1.0);
}

Cette astuce ne fonctionne qu'avec les matrices. Pour les vecteurs, votre compilateur vous râlera dessus car il demandera la valeur des autres coordonnées.

L'initialisation à partir d'autres variables

L'utilité des constructeurs ne s'arrête pas avec des valeurs statiques, il est tout à fait possible d'initialiser une variable à partir d'autres variables pré-existantes. C'est même une chose que l'on fera très souvent dans nos codes sources. :p

Le gros avantage également c'est que vous pouvez initialiser des variables à partir de n'importe quelle autre variable quelque soit son type. Par exemple, vous pouvez très bien utiliser un vec2 pour construire un vec4 à partir de ses coordonnées :

void main()
{
    // Vecteur

    vec2 petitVecteur = vec2(1.0, 2.0);

    // Initialisation à partir d'un autre vecteur

    vec4 grandVecteur = vec4(petitVecteur);
}

Vous avez compris le principe ? Le constructeur vec4() va prendre automatiquement les coordonnées (x, y) du premier vecteur pour les copier dans le second. Nous aurions pu utiliser ce code pour faire la même chose :

void main()
{
    // Vecteur

    vec2 petitVecteur = vec2(1.0, 2.0);

    // Initialisation à partir d'un autre vecteur

    vec4 grandVecteur;

    grandVecteur.x = petitVecteur.x;
    grandVecteur.y = petitVecteur.y;
}

Alors bon, je dois vous avouer que ce code n'est pas totalement correcte. En fait, si nous le compilions nous nous retrouverions avec un beau message d'erreur car le constructeur ne sait pas comment initialiser les dernières coordonnées (z, w). Il faut donc leur affecter une valeur à elles-aussi.

Pour cela, il suffit simplement de rajouter 2 autres valeurs en paramètres :

void main()
{
    // Vecteur

    vec2 petitVecteur = vec2(1.0, 2.0);

    // Initialisation à partir de la variable

    vec4 grandVecteur = vec4(petitVecteur, 3.0, 4.0);
}

Faites attention cependant à toujours vérifier le nombre de vos paramètres. Il vaut mieux perdre un peu de temps à vérifier plutôt que d'attendre l'erreur de compilation pour vous en rendre compte.

Cette fois, notre compilateur est content car toutes les coordonnées ont le droit à une valeur.

D'ailleurs, il est même possible d'inverser les trois paramètres en plaçant les valeurs 3.0 et 4.0 au début :

void main()
{
    // Vecteur position

    vec2 vecteur = vec2(1.0, 2.0);

    // Vecteur

    vec4 vecteur2 = vec4(3.0, 4.0, vecteur);
}

Si on fait ça en revanche, les données ne seront pas initialisées de la même façon. C'est-à-dire que la valeur 3.0 sera affectée à la coordonnée x et la valeur 4.0 à y. Pour z et w, le constructeur va prendre ce qu'il lui reste, à savoir respectivement les coordonnées (x, y) de la variable vec2.

Ce code revient à faire la chose suivante :

void main()
{
    // Vecteur

    vec2 petitVecteur = vec2(1.0, 2.0);

    // Autre vecteur

    vec4 grandVecteur;

    grandVecteur.x = 3.0;
    grandVecteur.y = 4.0;
    grandVecteur.z = petitVecteur.x;
    grandVecteur.w = petitVecteur.y;
}

Allez, on prend un dernier exemple pour être sûr que vous ayez compris. Si je vous montre le bout de code suivant, seriez-vous capable de trouvez l'équivalent en utilisant un constructeur ?

void main()
{
    // Vecteur

    vec2 petitVecteur = vec2(5.0, 8.0);

    // Autre vecteur

    vec4 grandVecteur;

    grandVecteur.x = 7.0;
    grandVecteur.y = petitVecteur.x;
    grandVecteur.z = petitVecteur.y;
    grandVecteur.w = 2.0;
}

J'ai volontairement changé les valeurs pour vous déstabiliser. :p

Vous avez trouvé ?

void main()
{
    // Vecteur

    vec2 petitVecteur = vec2(5.0, 8.0);

    // Autre vecteur

    vec4 grandVecteur = vec4(7.0, petitVecteur, 2.0);
}

Bien évidemment, je passe à coté de toute la subtilité offerte par le GLSL au niveau des variables. Cependant, nous en avons vu assez pour pouvoir travailler nos shaders efficacement.

Les structures de contrôle

Les structures de contrôles

Nous avons vu le plus gros du chapitre avec la partie sur les variables, il y avait pas mal de petites notions à voir. Nous allons maintenant nous attaquer à tout ce qui concerne les structures de contrôle comme les conditions, les boucles, etc. Il n'y aura pas de surprise cette fois, vous connaissez déjà tout. :p

Les conditions

Les conditions permettent de déclencher une portion de code en fonction de la valeur d'une variable. Il y a deux manières de les utiliser :

  • Soit avec les instructions : if, else if, else

void main()
{
    // Variable

    int variable = 1;

    // Conditions

    if(variable == 0)
        ... ;

    else if(variable == 1)
        ... ;

    else
        ... ;
}

  • Soit avec les instructions : switch, case

void main()
{
    // Variable

    int variable = 1;

    // Conditions

    switch(variable)
    {
        // Cas 1

        case 0:
            ... ;
        break;

        // Cas 2

        case 1:
            ... ;
        break;

        // Sinon

        default:
            ... ;
        break;
    }
}

Les mot-clefs break et continue sont utilisables en GLSL.

Les boucles

Les boucles permettent de répéter une portion de code en fonction d'une condition donnée. Ces conditions sont les mêmes que celles vues précédemment. Il y existe trois façons d'utiliser une boucle :

  • Soit avec l'instruction while

void main()
{
    // Variable

    int variable = 0;

    // Boucle

    while(variable < 10)
        variable++;
}

Cette boucle s'exécutera tant que la variable n'aura pas dépassé la valeur 10.

  • Soit avec les instructions do, while

void main()
{
    // Variable

    int variable = 0;

    // Boucle

    do
    {
        variable++;

    }while(variable < 10);
}

  • Soit avec l'instruction for

void main()
{
    // Variable

    int variable = 0;

    // Boucle

    for(int i = 0; i < 10; i++)
        variable++;
}

Les fonctions

La déclaration

Les fonctions sont des fonctionnalités importantes avec les langages de programmation. Nous serions très malheureux sans elles puisqu'il faudrait tout coder au même endroit. Imaginez si nous devions réunir toutes les classes d'un projet C++ dans un seul fichier ! :lol:

Heureusement pour nous, ça ne sera pas le cas avec le GLSL car il permet la création et l'utilisation de fonctions. L'avantage en plus c'est qu'elles se comportent exactement comme en C il n'y a aucun différence. Nous retrouvons donc leurs caractéristiques principales :

  • Des paramètres

  • Un nom

  • Une variable retournée si besoin

Le prototype, qui représente la signature de la fonction, n'est pas obligatoire mais je vous conseille de l'utiliser pour simplifier la lecture de vos codes sources. Il se place juste avant la fonction main() pour que le compilateur puisse connaitre leur existence avant d'attaquer le programme.

Voici un exemple de prototype de fonction :

// Prototype

vec4 convertirVecteur(vec2 vecteur);

// Fonction main

void main()
{

}

L'implémentation se fait après la fonction main() de façon à ce que ce soit plus lisible :

// Implémentation de la fonction

vec4 convertirVecteur(vec2 vecteur)
{
    vec4 resultat = vec4(vecteur, 0.0, 0.0);

    return resultat;
}

Notez la présence du mot-clef return qui permet de retourner une variable.

Ce qui donnerait un shader final :

// Prototype

vec4 convertirVecteur(vec2 vecteur);

// Fonction main

void main()
{

}

// Implémentation

vec4 convertirVecteur(vec2 vecteur)
{
    vec4 resultat = vec4(vecteur, 0.0, 0.0);

    return resultat;
}
L'utilisation

Les fonctions s'utilisent également de la même manière qu'en C. Il suffit de les appeler en leur donnant les paramètres qu'elles demandent.

Si on reprend l'exemple précédent :

void main()
{
    // Vecteur

    vec2 monPetitVecteur = vec2(1.0, 2.0);

    // Appel à la fonction de conversion

    vec4 monGrandVecteur = convertirVecteur(monPetitVecteur);
}
Les surcharges

La surcharge de fonction est la deuxième notion du GLSL que l'on peut rapprocher au C++. Il est possible d'utiliser plusieurs fois le même nom pour des fonctions différentes. Les conditions qui permettent de faire cela sont les mêmes qu'avec le C++, à savoir que les paramètres doivent être différents en nombre ou en type.

On reprend une fois de plus l'exemple précédent en surchargeant la fonction convertirVecteur() afin qu'elle puisse prendre en paramètre des variables de type vec3:

// Prototype original

vec4 convertirVecteur(vec2 vecteur);

// Surcharge

vec4 convertirVecteur(vec3 vecteur);

Il faut bien évidemment faire une seconde implémentation car les deux prototypes représentent des fonctions différentes :

// Implémentation de la surcharge

vec4 convertirVecteur(vec3 vecteur)
{
    vec4 resultat = vec4(vecteur, 0.0);

    return resultat;
}

Pour utiliser cette surcharge, il suffit aussi de l'appeler par son nom. Le shader s'occupera tout seul de savoir s'il doit prendre celle-ci ou la fonction originale :

void main()
{
    // Vecteur

    vec3 monPetitVecteur = vec3(1.0, 2.0, 3.0);

    // Appel à la fonction de conversion

    vec4 monGrandVecteur = convertirVecteur(monPetitVecteur);
}
Les pointeurs

Malheureusement, les pointeurs n'existent pas en GLSL, et je ne vous parle même pas des références qui existent encore moins. Il n'est donc pas possible de modifier plusieurs variables dans une seule fonction, il faudra en utiliser plusieurs. Mais je vous rassure, nous n'aurions quasiment jamais eu besoin de pointeur de toute façon. ;)

Divers

Le préprocesseur

Le préprocesseur est un processus qui s'exécute juste avant le compilateur et qui permet de filtrer ou d'ajouter des instructions au code source original.

Sa particularité vient du fait qu'il ne lit pas les lignes de code 'normales' mais uniquement celles que l'on appelle des directives de préprocesseur. On les reconnait grâce à leur symbole #.

Avec le GLSL, il existe aussi un préprocesseur qui nous permet d'utiliser des directives comme #define, #ifdef, ... La plus importante pour nous va être celle qui permet de définir la version du GLSL avec laquelle nous coderons. Elle s'appelle #version et s'utilise de cette façon :

// Version du GLSL

#version 150 core

// Fonction main()

void main()
{

}

Elle permet de dire à OpenGL que notre code source utilisera la version 1.50 du GLSL ainsi que le profil core. Notez qu'il est possible de remplacer le mot-clef core par compatibility. ;)

Nous utiliserons cette directive au début de chacun de nos codes sources à partir de maintenant.

Les structures

Les structures sont un peu comme les ancêtres des objets. Ils possèdent des champs, que l'on peut rapprocher aux attributs, mais ne possèdent pas de méthodes propres. La bonne nouvelle c'est qu'on peut quand même les utiliser pour programmer nos shaders. :)

Leur déclaration se fait exactement comme en C, on utilise le mot-clef struct accompagné du nom souhaité. On inclut ensuite les champs à l'intérieur.

Prenons un petit exemple en déclarant une structure Camera. Structure qui ressemble étonnement à une classe que nous avons déjà codée. :p

// Structure Camera

struct Camera
{
    vec3 position;
    vec3 orientation;

    float rapidite;
};

L'avantage des structures en GLSL c'est qu'il n'y a pas besoin d'utiliser le mot clef typedef pour s'affranchir du mot-clef struct. Nous pouvons donc utiliser notre structure directement comme s'il s'agissait d'une variable normale :

void main()
{
    // Déclaration d'une structure Camera

    Camera maCamera;

    // Utilisation

    maCamera.position = vec3(2.0, 5.0, 2.0);
    maCamera.rapidite = 0.5;
}

Fonctions prédéfinies

Nous avons fait le tour de la syntaxe de notre nouveau langage, il n'y a pas grand chose à dire de plus dessus. Je vous réserve le reste pour le chapitre suivant. ;) Mais avant de passer à ça, j'aimerais vous montrer quelques fonctions prédéfinies qui nous serons utiles par la suite.

En effet, le GLSL a l'avantage d'intégrer nativement une bibliothèque mathématique assez complète qui permet d'aider le développeur dans ses calculs 3D. On retrouve ainsi plusieurs fonctions relatives à la trigonométrie, aux vecteurs, aux matrices, etc.

Les fonctions trigonométriques

Sinus, Cosinus et Tangente

Si je vous parle de trigonométrie vous devez forcément penser aux fonctions sin(), cos() et tan(). :p Elles permettent de calculer respectivement le sinus, le cosinus et la tangente d'un angle. Leur prototype est le même qu'en C++ :

float sin(float angle);
float cos(float angle);
float tan(float angle);

Voici un petit exemple qui permet de calculer le sinus d'un angle :

#version 150 core

void main()
{
    // Calcul du sinus d'un angle de 90°

    float sinus = sin(90.0);             /* La valeur du sinus sera de 1.0 */
}

Les autres fonctions s'utilisent de la même façon.

Convertir un angle en radian et inversement

Il existe des fonctions permettant de convertir un angle exprimé en degrés vers les radians, et inversement bien sûr. Elles sont très utiles dans certains cas, d'ailleurs on aurait bien aimé les avoir en C++ quand nous avons utilisé la trigonométrie. :p

Ces fonctions s'appellent respectivement radians() et degrees() :

float radians(float degrees);
float degrees(float radians);

Un petit exemple de conversion d'un angle exprimé en degrés :

#version 150 core

void main()
{
    // Conversion de l'angle 90° en radians

    float radians = radians(90.0);                /* La valeur de l'angle sera de 1/2 Pi soit environ 1.57 */
}

Les fonctions relatives aux vecteurs

Normalisation

Ah la normalisation ... Ça devrait vous rappeler quelques souvenirs. Surtout que nous avons eu l'occasion d'en faire plein avec GLM. :p

Vous devriez donc savoir que normaliser un vecteur revient à réduire sa norme (sa longueur) à 1.0. Cela permet de faire pas mal d'opérations mathématiques et de profiter de la trigonométrie.

La fonction qui permet de normaliser un vecteur s'appelle normalize() exactement comme celle de GLM :

vec normalize(vec vector);

Les fonctions prédéfinies sont en général toutes surchargées, ce qui évite d'avoir à utiliser des noms différents pour la même opération. Le type vec n'existe évidemment pas, je l'ai mis ici pour vous montrer que la fonction normalize() était surchargée.

Je ne vous fait pas d'exemple, je pense que vous avez compris le principe. ;)

Calculer la norme

Vu que l'on parle de la norme d'un vecteur, nous pouvons parler de la fonction qui permet de la calculer. Elle s'appelle length() :

float length(vec vector);
Calculer le produit vectoriel

Nous avons déjà utilisé le produit vectoriel pour faire des calculs dans la classe Camera qui nous permettait de trouver le vecteur orthogonal à l'orientation. La fonction permettant de faire la même chose en GLSL s'appelle aussi cross() :

vec cross(vec vector1, vec vertor2);

Cette fonction prend deux paramètres représentant les deux vecteurs à multiplier. Faites attention à utiliser le même type pour cette opération. Vous aurez une erreur de compilation si vous essayez d'en utilisez deux différents.

Autres fonctions

Il existe encore une multitude de fonctions prédéfinies dont je ne vous ai pas parlées mais il y en a tellement que mes pauvres doigts souffriraient trop si je devais toutes vous les énumérer. :lol:

Sachez qu'il existe aussi des fonctions permettant d'effectuer des calculs sur les matrices comme le calcul du déterminant, d'inversion, etc. Il en existe aussi qui permettent de faire des opérations 'classiques' comme les arrondis, les valeurs absolues, les modulos, etc. Le GLSL contient une véritable bibliothèque couteau-suisse pour les calculs mathématiques dont est inspirée la librairie GLM.

Dernier point à préciser : rappelez-vous que les shaders s'exécutent autant de fois qu'il y a de vertices/pixels, donc potentiellement des millions de fois par seconde. Il est donc préférable d'utiliser les fonctions prédéfinies à la place des nôtres quand on le peut.

Nous avons fait le tour du langage de programmation qu'est l'OpenGL Shading Language.

Nous connaissons les types de variables que l'on peut utiliser, les structures de contrôle comme les conditions, les boucles, etc. Nous avons vu en somme sa syntaxe globale; syntaxe qui se rapproche beaucoup de C/C++, les développeurs ont tout fait pour faciliter son apprentissage.

Nous nous servirons de ce langage pour coder tous nos futurs codes sources. D'ailleurs, nous allons commencer tout de suite dans le chapitre suivant en programmant notre premier shader ! :D


Les shaders démystifiés (Partie 1/2)

Nous voici arrivés (enfin !) au chapitre qui va nous permettre de créer nos propres shaders. Nous avons appris la syntaxe du langage de programmation GLSL et nous savons même comment compiler des codes sources l'utilisant.

Aujourd'hui, nous allons lever le voile sur les dernières zones d'ombres qui subsistent dans nos programmes, notamment en étudiant tous les petits fichiers sources que l'on utilisés depuis le début. Nous en profiterons pour jouer un peu avec afin de créer nos premiers effets personnalisés. Ils seront un peu basiques certes, mais il faut bien commencer par quelque chose.

J'ai préféré couper ce chapitre en deux car il était trop lourd à suivre en une seule fois. A la place, vous aurez le droit à deux chapitres plus petits dans lesquels vous vous sentirez moins étouffés.

Préparation

Préparation

Dans ce chapitre, nous allons nous concentrer sur les premiers shaders que nous avons utilisés au début du tuto. Exit donc les matrices et la caméra, nous verrons ça dans le prochain chapitre. Il vaut mieux ne pas s'en occuper pour le moment car si je vous montre tout d'un coup, vous risquez de vous emmêler les pinceaux. :p Il vaut mieux y aller en douceur. Mais ne vous inquiétez pas, nous en verrons assez pour faire nos premiers essais dans la programmation GLSL.

Avant de commencer, il va nous falloir nettoyer un peu la boucle principale en enlevant la gestion des matrices et de la caméra. Cependant, nous allons devoir conserver l'utilisation du VBO et du VAO car ceux-ci sont obligatoirement demandés par la version 3.3 d'OpenGL.

Nous testerons nos premières sources sur un petit carré à 2 dimensions pour le moment vu que les matrices ne sont pas encore utilisables. Nous finirons sur des cubes et des pyramides dans le chapitre suivant.

Bref pour commencer, je vous demande de vider votre méthode bouclePrincipale() de tout son contenu précédent. Ajoutez-y ensuite les vertices de notre petit carré :

void SceneOpenGL::bouclePrincipale()

{

    // Vertices

    float vertices[] = {-0.5, -0.5,   0.5, -0.5,   0.5, 0.5,    // Triangle 1

                        -0.5, -0.5,   -0.5, 0.5,   0.5, 0.5};   // Triangle 2

}

Ce carré ne sera pas minuscule ne vous inquiétez pas. Vu que nous n'utilisons pas la caméra il faut revenir à l'ancien repère où les coordonnées sont comprises dans l'intervalle [-1; 1]. Si vous faites un carré dépassant cette taille alors il prendra toute votre fenêtre.

Enfin, maintenant qu'on déclaré nos vertices, nous pouvons passer à l'implémentation du VBO. Il n'y a qu'un seul tableau à envoyer pour le moment donc le calcul de sa taille sera simple. L'espace mémoire à allouer fait donc 3 vertices x 2 coordonnées x 2 triangles = 12 cases :

// VBO et taille des données

GLuint vbo;

int tailleVerticesBytes = 12 * sizeof(float);

Une fois la taille définie, on peut allouer VBO :

// Génération du VBO

glGenBuffers(1, &vbo);

// Verrouillage

glBindBuffer(GL_ARRAY_BUFFER, vbo);

    // Allocation

    glBufferData(GL_ARRAY_BUFFER, tailleVerticesBytes, 0, GL_STATIC_DRAW);

// Déverrouillage

glBindBuffer(GL_ARRAY_BUFFER, 0);

Puis, on peut le remplir avec les vertices :

// Allocation

glBufferData(GL_ARRAY_BUFFER, tailleVerticesBytes, 0, GL_STATIC_DRAW);

// Remplissage

glBufferSubData(GL_ARRAY_BUFFER, 0, tailleVerticesBytes, vertices);

Nous avons vu ce code dans le chapitre sur les VBO, il ne devrait pas vous poser de problème. ;)

Ce qui nous donne pour le moment :

void SceneOpenGL::bouclePrincipale()

{

    // Vertices

    float vertices[] = {-0.5, -0.5,   0.5, -0.5,   0.5, 0.5,    // Triangle 1

                        -0.5, -0.5,   -0.5, 0.5,   0.5, 0.5};   // Triangle 2

    /* ***** Gestion du VBO ***** */

    GLuint vbo;

    int tailleVerticesBytes = 12 * sizeof(float);

    // Génération du VBO

    glGenBuffers(1, &vbo);

    // Verrouillage

    glBindBuffer(GL_ARRAY_BUFFER, vbo);

        // Remplissage

        glBufferData(GL_ARRAY_BUFFER, tailleVerticesBytes, 0, GL_STATIC_DRAW);

        glBufferSubData(GL_ARRAY_BUFFER, 0, tailleVerticesBytes, vertices);

    // Déverrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, 0);

}

On passe maintenant au VAO. On commence évidemment par lui générer un ID :

// VAO

GLuint vao;

// Génération du VAO

glGenVertexArrays(1, &vao);

Nous devons mettre à l'intérieur tous nos appels aux tableaux Vertex Attrib (activation comprise). Pour le moment, nous n'avons que celui des vertices à appeler donc c'est assez simple.

D'ailleurs en parlant de ça, faites attention aux paramètres de ce tableau car nos vertices possèdent 2 coordonnées et non 3. Affectez donc la valeur 2 au paramètre size :

// Verrouillage du VAO

glBindVertexArray(vao);

    // Verrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, vbo);

        // Vertex Attrib 0 (Vertices)

        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

        glEnableVertexAttribArray(0);

    // Déverrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, 0);

// Déverrouillage du VAO

glBindVertexArray(0);

N'oubliez pas de verrouiller votre VBO quand vous utilisez le tableau Vertex Attrib.

Si on résume tout ça :

void SceneOpenGL::bouclePrincipale()

{

    // Vertices

    float vertices[] = {-0.5, -0.5,   0.5, -0.5,   0.5, 0.5,    // Triangle 1

                        -0.5, -0.5,   -0.5, 0.5,   0.5, 0.5};   // Triangle 2

    /* ***** Gestion du VBO ***** */

    GLuint vbo;

    int tailleVerticesBytes = 12 * sizeof(float);

    // Génération du VBO

    glGenBuffers(1, &vbo);

    // Verrouillage

    glBindBuffer(GL_ARRAY_BUFFER, vbo);

        // Remplissage

        glBufferData(GL_ARRAY_BUFFER, tailleVerticesBytes, 0, GL_STATIC_DRAW);

        glBufferSubData(GL_ARRAY_BUFFER, 0, tailleVerticesBytes, vertices);

    // Déverrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, 0);

    /* ***** Gestion du VAO ***** */

    GLuint vao;

    glGenVertexArrays(1, &vao);

    // Verrouillage du VAO

    glBindVertexArray(vao);

        // Verrouillage du VBO

        glBindBuffer(GL_ARRAY_BUFFER, vbo);

            // Vertex Attrib 0 (Vertices)

            glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

            glEnableVertexAttribArray(0);

        // Déverrouillage du VBO

        glBindBuffer(GL_ARRAY_BUFFER, 0);

    // Déverrouillage du VAO

    glBindVertexArray(0);

}

Pfiouuu, tout ce code pour envoyer deux simples triangles ! :lol:

Et encore ce n'est pas fini, il nous manque toujours la boucle while. On commence à bien la connaitre celle-la :

// Variables relatives au framerate

unsigned int frameRate (1000 / 50);

Uint32 debutBoucle(0), finBoucle(0), tempsEcoule(0);

// Boucle principale

while(!m_input.terminer())

{

    // Gestion des évènements, etc.

    ....

}

On inclut évidemment le code de gestion des évèndments :

// Boucle principale

while(!m_input.terminer())

{

    // On définit le temps de début de boucle

    debutBoucle = SDL_GetTicks();

    // Gestion des évènements

    m_input.updateEvenements();

    if(m_input.getTouche(SDL_SCANCODE_ESCAPE))

       break;

    // Rendu

    ....

    // Calcul du temps écoulé

    finBoucle = SDL_GetTicks();

    tempsEcoule = finBoucle - debutBoucle;

    // Si nécessaire, on met en pause le programme

    if(tempsEcoule < frameRate)

        SDL_Delay(frameRate - tempsEcoule);

}

Et enfin, on rajoute le code permettant de nettoyer et d'actualiser ce qui est affiché à l'écran :

// Boucle principale

while(!m_input.terminer())

{

    // On définit le temps de début de boucle

    debutBoucle = SDL_GetTicks();

    // Gestion des évènements

    m_input.updateEvenements();

    if(m_input.getTouche(SDL_SCANCODE_ESCAPE))

       break;

    // Nettoyage de l'écran

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    /* *** Rendu *** */

    // Actualisation de la fenêtre

    SDL_GL_SwapWindow(m_fenetre);

    // Calcul du temps écoulé

    finBoucle = SDL_GetTicks();

    tempsEcoule = finBoucle - debutBoucle;

    // Si nécessaire, on met en pause le programme

    if(tempsEcoule < frameRate)

        SDL_Delay(frameRate - tempsEcoule);

}

Il ne reste plus qu'à afficher notre rendu. Celui-ci va être très simple car il suffit de verrouiller le VAO, puis d'appeler la fonction glDrawArrays() :

// Verrouillage du VAO

glBindVertexArray(vao);

// Rendu

glDrawArrays(GL_TRIANGLES, 0, 6);

// Déverrouillage du VAO

glBindVertexArray(0);

Ah dernier point, il faut penser à détruire le VBO et le VAO une fois la boucle terminée. ;)

// Destruction du VAO et du VBO

glDeleteBuffers(1, &vbo);

glDeleteVertexArrays(1, &vao);

Récap final :

void SceneOpenGL::bouclePrincipale()

{

    // Vertices

    float vertices[] = {-0.5, -0.5,   0.5, -0.5,   0.5, 0.5,    // Triangle 1

                        -0.5, -0.5,   -0.5, 0.5,   0.5, 0.5};   // Triangle 2

    /* ***** Gestion du VBO ***** */

    GLuint vbo;

    int tailleVerticesBytes = 12 * sizeof(float);

    // Génération du VBO

    glGenBuffers(1, &vbo);

    // Verrouillage

    glBindBuffer(GL_ARRAY_BUFFER, vbo);

        // Remplissage

        glBufferData(GL_ARRAY_BUFFER, tailleVerticesBytes, 0, GL_STATIC_DRAW);

        glBufferSubData(GL_ARRAY_BUFFER, 0, tailleVerticesBytes, vertices);

    // Déverrouillage

    glBindBuffer(GL_ARRAY_BUFFER, 0);

    /* ***** Gestion du VAO ***** */

    GLuint vao;

    // Génération du VAO

    glGenVertexArrays(1, &vao);

    // Verrouillage du VAO

    glBindVertexArray(vao);

        // Verrouillage du VBO

        glBindBuffer(GL_ARRAY_BUFFER, vbo);

            // Vertex Attrib 0 (Vertices)

            glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

            glEnableVertexAttribArray(0);

        // Déverrouillage du VBO

        glBindBuffer(GL_ARRAY_BUFFER, 0);

    // Déverrouillage du VAO

    glBindVertexArray(0);

    // Variables relatives au framerate

    unsigned int frameRate (1000 / 50);

    Uint32 debutBoucle(0), finBoucle(0), tempsEcoule(0);

    // Boucle principale

    while(!m_input.terminer())

    {

        // On définit le temps de début de boucle

        debutBoucle = SDL_GetTicks();

        // Gestion des évènements

        m_input.updateEvenements();

        if(m_input.getTouche(SDL_SCANCODE_ESCAPE))

           break;

        // Nettoyage de l'écran

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // Verrouillage du VAO

        glBindVertexArray(vao);

        // Rendu

        glDrawArrays(GL_TRIANGLES, 0, 6);

        // Déverrouillage du VAO

        glBindVertexArray(0);

        // Actualisation de la fenêtre

        SDL_GL_SwapWindow(m_fenetre);

        // Calcul du temps écoulé

        finBoucle = SDL_GetTicks();

        tempsEcoule = finBoucle - debutBoucle;

        // Si nécessaire, on met en pause le programme

        if(tempsEcoule < frameRate)

            SDL_Delay(frameRate - tempsEcoule);

    }

    // Destruction du VAO et du VBO

    glDeleteBuffers(1, &vbo);

    glDeleteVertexArrays(1, &vao);

}

Nous avons maintenant un code propre sur lequel nous pouvons nous exercer. Nous augmenterons son contenu au fur et à mesure jusqu'à réintégrer les matrices, la caméra et les textures.

J'ai préféré faire une partie dédiée pour cela, en vous expliquant chaque étape, plutôt que de vous jeter le code d'un coup. Vous connaissez absolument tout ce qui se trouve à l'intérieur, il n'y a rien de nouveau (pour le moment).

Premier shader

Affichage simple

Chargement

Il est enfin temps de programmer notre premier shader, celui-ci sera très basique car il ne se contentera que d'afficher le carré à l'écran. Dans un premier temps nous lui donnerons la couleur blanche, nous gèrerons les autres couleurs petit à petit.

Pour commencer, je vais vous demander de vider complétement le dossier Shaders de votre projet afin d'enlever tous les précédents codes sources. Une fois fait, créez à la main deux nouveaux fichiers qui porteront le nom de basique2D.vert et basique2D.frag. Ensuite, déclarez un objet de type Shader dans la méthode bouclePrincipale() avec en paramètres le chemin vers ces fichiers :

void SceneOpenGL::bouclePrincipale()

{

    // Shader

    Shader shader("Shaders/basique2D.vert", "Shaders/basique2D.frag");

    shader.charger();

    // Vertices

    float vertices[] = {-0.5, -0.5,   0.5, -0.5,   0.5, 0.5,    // Triangle 1

                        -0.5, -0.5,   -0.5, 0.5,   0.5, 0.5};   // Triangle 2

    ....

}

Activez-le ensuite au moment du rendu de façon à ce qu'il soit prit en compte par OpenGL :

// Activation du shader

glUseProgram(shader.getProgramID());

    // Rendu

    glBindVertexArray(vao);

        glDrawArrays(GL_TRIANGLES, 0, 6);

    glBindVertexArray(0);

// Désactivation du shader

glUseProgram(0);

Le Vertex Shader

Nos outils sont en place et les fichiers sont crées ... Nous pouvons maintenant passer à la programmation du code source.

Nous commencerons par celui du Vertex Shader car c'est celui-ci qu'OpenGL appellera en premier dans le pipeline 3D. Son rôle consiste à prendre un vertex (avec la couleur et les coordonnées de texture qui lui sont associées) pour travailler dessus.

Son code source commencera par l'utilisation de la balise #version, pour indiquer la version du GLSL utilisée, suivie par la fonction main() :

// Version du GLSL

#version 150 core

// Fonction main

void main()

{

}

Pour la suite, nous aurons besoin d'une notion que l'on a déjà vue précédemment : les entrées shader. Nous avons évoqué ce terme il y a à peine deux chapitres quand nous parlions du linkage du shader. Nous utilisions alors la fonction glBindAttribLocation() qui permettait de créer une passerelle entre un tableau Vertex Attrib et sa variable présente dans le GLSL.

Pour les vertices par exemple, le nom de cette variable était in_Vertex :

Nous avons utilisé la fonction glBindAttribLocation() pour verrouiller cette entrée, donc la variable in_Vertex est accessible dans notre code source. D'ailleurs, les autres variables comme in_Color sont elles aussi accessibles, nous verrons cela un peu plus loin. ;)

Pour utiliser in_Vertex, il faut déclarer une variable globale portant son nom. Je dis bien globale parce qu'elle doit être déclarée en dehors de la fonction main(). Elle doit être représentée par un type vec dont le nombre de coordonnées dépend de celui renseigné dans le tableau Vertex Attrib. Dans notre cas, nos vertices possèdent 2 coordonnées, donc in_Vertex sera de type vec2 :

// Version du GLSL

#version 150 core

// Entrée Shader

vec2 in_Vertex;

// Fonction main

void main()

{

}

Cette déclaration n'est pas encore tout à fait complète. En effet, si nous la laissons ainsi, OpenGL croira qu'il s'agit d'une variable normale sans aucun rapport avec les tableaux Vertex Attrib. Pour corriger ça, il faut lui ajouter le mot-clef in juste avant son type :

// Version du GLSL

#version 150 core

// Entrée Shader

in vec2 in_Vertex;

// Fonction main

void main()

{

}

Grâce à ce mot-clef, OpenGL saura que cette variable est reliée à un tableau VertexAttrib.

Enfin, maintenant que l'on a récupéré le vertex en cours, il faut l'utiliser pour qu'il puisse servir à quelque chose. :p Pour cela, nous allons affecter son contenu à une variable prédéfinie dans le GLSL (comme les fonctions mathématiques) qui se nomme gl_Position :

vec4 gl_Position;

Cette variable est prédéfinie dans le GLSL, nous n'avons donc pas besoin de la déclarer en in. Elle permet, entre autres, de définir la position finale du vertex à l'écran, elle demande pour ça le contenu de la variable in_Vertex.

La seule difficulté que l'on peut rencontrer lors de cette affectation concerne le conflit de type entre les deux variables. En effet, in_Vertex est de type vec2 alors que gl_Position est de type vec4. Cependant, vous devriez savoir comment surmonter ce problème non ? ;)

Pour combler ce manque de coordonnées, il suffit simplement d'utiliser un constructeur ! Et plus précisément, le constructeur vec4(). Nous lui donnerons la variable in_Vertex ainsi que deux autres coordonnées égales à 0.0 pour Z, car le carré n'est qu'en 2D pour le moment, et 1.0 pour W.

La coordonnée W d'un vertex est un peu spéciale. Si vous mettez d'autres valeurs comme 2.0 par exemple, vous remarquerez que la taille de votre rendu sera divisée par deux. Pour conserver une taille normale, il faut laisser la valeur 1.0 à W. C'est ce que nous ferons pour tous nos shaders.

// Position finale du vertex

gl_Position = vec4(in_Vertex, 0.0, 1.0);

Ce qui donne :

// Version du GLSL

#version 150 core

// Entrée Shader

in vec2 in_Vertex;

// Fonction main

void main()

{

    // Position finale du vertex

    gl_Position = vec4(in_Vertex, 0.0, 1.0);

}

Je vous avais dit que les constructeurs étaient utiles. :p

Et voilà ! Notre premier Vertex Shader est terminé ! Certes, il ne fait pas grand chose mais au moins il fait exactement ce qu'on veut : il définit la position d'un vertex à l'écran. Sachez que ce bout de code sera présent dans TOUS vos futurs shaders, quelque soit leur complexité.

Les Entrées-Sorties

Avant d'aller plus loin dans le développement des codes sources, j'aimerais que l'on voit ensemble le fonctionnement des entrées-sorties au niveau des shaders. Vous connaissez déjà le mot-clé in qui permet de définir des données entrantes. Sachez qu'il existe aussi son opposé, le mot-clef out, qui permet de définir des données sortantes.

Ces termes sont assez subtiles car ils ne signifient pas la même chose en fonction du shader où ils sont utilisés.

Pour le Vertex Shader, ils signifient :

  • in : Variable représentant les données des tableaux Vertex Attrib (vertices, couleurs et tutti quanti)

  • out : Variable qui sera envoyée au shader suivant, soit le Fragment Shader dans notre cas

Pour le Fragment Shader, ces termes signifient :

  • in : Variable représentant les données reçues depuis le shader précédent, soit celles étant déclarées avec le mot-clef in dans le Vertex Shader

  • out : Variable représentant la couleur finale d'un pixel

Vous voyez qu'il y a une différence selon le shader qu'on utilise, faites bien la différence.

La variable gl_Position est un peu spéciale et ne fait pas partie de ce système d'entrées-sorties, elle est spécifique au Vertex Shader. Il y en a quelques unes comme ça mais c'est la la seule vraiment importante.

Le Fragment Shader

On passe maintenant au Fragment Shader. Celui-ci permet de définir la couleur de chaque pixel d'une surface affichée. Si vous avez utilisé une résolution de 800x600 pour votre fenêtre alors votre carte graphique devra gérer 120 000 pixels pour votre carré ! Non non vous ne rêvez pas, votre Fragment Shader devra gérer 120 000 pixels différent et cela 60 fois par seconde. :p

Mais ne vous inquiétez pas, elle est justement faite pour ça. Pour vous dire, 120 000 c'est un nombre extrêmement petit, elle en gère beaucoup plus dans des applications développées.

Enfin, le code source du Fragment Shader commence par la même chose que précédemment : la balise #version et la fonction main().

// Version du GLSL

#version 150 core

// Fonction main

void main()

{

}

Contrairement au Vertex Shader, nous n'aurons pas (pour le moment) de variable d'entrée ici, nous n'aurons donc pas besoin d'utiliser le mot-clef in. En revanche, nous devrons gérer une variable de sortie, donc nous devrons utiliser le mot-clef out.

Comme nous l'avons vu juste avant, la sortie d'un Fragment Shader correspond à la couleur finale du pixel. Il s'agit d'une variable vec4 tout comme gl_Position sauf que cette fois-ci elle n'est pas prédéfinie, nous devons la déclarer nous-même. Nous l'appellerons out_Color :

// Version du GLSL

#version 150 core

// Sortie Shader

out vec4 out_Color;

// Fonction main

void main()

{

}

Cette variable demande 4 valeurs représentant les composantes RGBA d'une couleur. Chaque valeur doit être comprise entre 0 et 1, comme lorsque nous utilisions le tableau Vertex Attrib au début du tuto.

Pour affecter ces composantes à la variable out_Color, nous aurons besoin une fois de plus du constructeur vec4(). Pour le moment, nous leur affecterons 4 fois la valeur 1.0 de façon à avoir la couleur blanche :

void main()

{

    // Couleur finale du pixel

    out_Color = vec4(1.0, 1.0, 1.0, 1.0);

}

Ce qui donne :

// Version du GLSL

#version 150 core

// Sortie Shader

out vec4 out_Color;

// Fonction main

void main()

{

    // Couleur finale du pixel

    out_Color = vec4(1.0, 1.0, 1.0, 1.0);

}

Et là, vous pouvez enfin compiler pour voir ce que ça donne :

Félicitation, vous venez de programmer votre premier shader ! Champagne ! :D

Ce qu'il faut absolument retenir

Avant de passer à la suite, j'aimerais faire un petit point sur ce que vous devez absolument retenir de cette partie.

En ce qui concerne le Vertex Shader, vous devez savoir que :

  • Les variables in représentent les tableaux Vertex Attrib

  • Les variables out sont envoyées au shader suivant, soit le Fragment Shader dans notre cas

  • La variable gl_Position doit être remplie avec la variable in_Vertex. Elle ne doit pas être déclarée en out

En ce qui concerne le Fragment Shader, vous devez savoir que :

  • Les variables in représentent les variables out du shader précédent, soit le Vertex Shader dans notre cas

  • La sortie du Fragment Shader représente la couleur finale du pixel

  • Elle est représentée par une variable out

Il faut absolument que vous connaissez ces points par cœur. Ce sont vraiment les bases de la programmation GLSL. ;)

Utilisation de couleur

Changer de couleur

Le blanc n'est évidemment pas la seule couleur que l'on peut utiliser. Pour en prendre une autre, il suffit simplement de changer les valeurs données à la variable out_Color.

Par exemple, pour afficher le carré avec la couleur bleue, nous pouvons affecter les valeurs RGB : 0.0, 0.0, 1.0. Pour la valeur Alpha, il vaut mieux la laisser à 1.0 pour le moment, quelque soit la couleur que l'on veut utiliser :

// Version du GLSL

#version 150 core

// Sortie Shader

out vec4 out_Color;

// Fonction main

void main()

{

    // Couleur finale du pixel

    out_Color = vec4(0.0, 0.0, 1.0, 1.0);

}

Si vous relancez le projet (sans le compiler ;) ), vous devriez avoir ceci :

Exercices

Allez, je vais vous donner vos premiers exercices utilisant la programmation en GLSL. Ils sont très simples.

Exercice 1 : Coloriez le carré en rouge.

Exercice 2 : Coloriez le carré en violet/rose (avec les composantes rouge et bleu).

Exercice 3 : Coloriez le carré en jaune.

Exercice 4 : Coloriez le carré en gris (n'importe quelle nuance).

Solutions

Exercice 1 :

Il n'y a que le Fragment Shader à modifier (pour les 4 exercices), l'autre ne change absolument pas. ;)

// Version du GLSL

#version 150 core

// Sortie Shader

out vec4 out_Color;

// Fonction main

void main()

{

    // Couleur finale du pixel

    out_Color = vec4(1.0, 0.0, 0.0, 1.0);

}

Exercice 2 :

// Version du GLSL

#version 150 core

// Sortie Shader

out vec4 out_Color;

// Fonction main

void main()

{

    // Couleur finale du pixel

    out_Color = vec4(1.0, 0.0, 1.0, 1.0);

}

Exercice 3 :

// Version du GLSL

#version 150 core

// Sortie Shader

out vec4 out_Color;

// Fonction main

void main()

{

    // Couleur finale du pixel

    out_Color = vec4(1.0, 1.0, 0.0, 1.0);

}

Exercice 4 :

// Version du GLSL

#version 150 core

// Sortie Shader

out vec4 out_Color;

// Fonction main

void main()

{

    // Couleur finale du pixel

    out_Color = vec4(0.5, 0.5, 0.5, 1.0);

}

Gestion de la couleur

Préparation

Changement de shader

Avant toute chose, vu que nous ajoutons une nouvelle fonctionnalité à notre rendu il nous faut donc créer un nouveau shader. Créons donc deux nouveaux fichiers appelés couleur2D.vert et couleur2D.frag dans le dossier Shaders.

Il nous faut ensuite ajouter leur chemin dans notre objet shader :

void SceneOpenGL::bouclePrincipale()

{

    // Shader

    Shader shader("Shaders/couleur2D.vert", "Shaders/couleur2D.frag");

    shader.charger();

    ....

}

Déclaration du tableau de couleurs

Il y a bien longtemps, dans une galaxy loin..., nous utilisions des tableaux de couleurs pour afficher nos modèles. Nous ne connaissions pas encore les textures, c'était alors le seul moyen d'afficher quelque chose de non-blanc à l'écran.

Pour fonctionner correctement, il fallait affecter une couleur pour chaque vertex et chacune d'elles possédait 3 composantes (Rouge Vert Bleu):

// Exemple de couleur

float rouge = {1.0, 0.0, 0.0);

Dans notre code test actuel nous avons 6 vertices, nous aurons donc besoin d'un tableau de 6 couleurs x 3 composantes soit 18 cases. Nous utiliserons la couleur bleu pour le premier triangle et le rouge pour le second :

void SceneOpenGL::bouclePrincipale()

{

    // Shader

    ....

    // Vertices

    float vertices[] = {-0.5, -0.5,   0.5, -0.5,   0.5, 0.5,    // Triangle 1

                        -0.5, -0.5,   -0.5, 0.5,   0.5, 0.5};   // Triangle 2

    // Couleurs

    float couleurs[] = {0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,    // Triangle 1

                        1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0};   // Triangle 2

    ....

}

Avec ce tableau, chacun de nos vertices aura sa propre couleur.

Gestion du VBO et du VAO

Bien évidemment, déclarer un tableau comme ça ne sert pas à grand chose, il faut l'intégrer au VBO et au VAO.

Pour ce qui est du premier, il va falloir le redimensionner car il ne peut accueillir que les vertices pour le moment. On commence donc pas créer une variable tailleCouleursBytes qui contiendra la taille du tableau de couleurs en bytes :

// VBO et taille des données

GLuint vbo;

int tailleVerticesBytes = 12 * sizeof(float);

int tailleCouleursBytes = 18 * sizeof(float);

On redimensionne ensuite le VBO en additionnant les deux tailles de données :

// Nouvelle taille du VBO

glBufferData(GL_ARRAY_BUFFER, tailleVerticesBytes + tailleCouleursBytes, 0, GL_STATIC_DRAW);

Et enfin, on le remplie avec le tableau de couleurs :

// Envoi des données

glBufferSubData(GL_ARRAY_BUFFER, 0, tailleVerticesBytes, vertices);

glBufferSubData(GL_ARRAY_BUFFER, tailleVerticesBytes, tailleCouleursBytes, couleurs);

Au niveau du VAO, nous devons juste appeler puis activer le tableau Vertex Attrib 1 qui correspond à l'envoi des couleurs :

// Verrouillage du VAO

glBindVertexArray(vao);

    // Verrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, vbo);

        // Vertex Attrib 0 (Vertices)

        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

        glEnableVertexAttribArray(0);

        // Vertex Attrib 1 (Couleurs)

        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(tailleVerticesBytes));

        glEnableVertexAttribArray(1);

    // Déverrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, 0);

// Déverrouillage du VAO

glBindVertexArray(0);

Gestions des shaders

Le Vertex Shader

Contrairement à ce qu'on pourrait penser au premier abord, les couleurs ne sont absolument pas envoyées au Fragment Shader mais bel et bien au Vertex Shader. C'est lui qui récupère toutes les données issues des tableaux Vertex Attrib. Si nous voulons travailler sur les pixels avec une couleur envoyée, alors il faudra la transférer manuellement au Fragment Shader.

Cependant, avant de faire cela il va falloir la récupérer dans notre source, tout comme nous l'avons fait avec le vertex (in_Vertex). La variable qui permet d'accéder aux couleurs s'appelle in_Color, nous l'avions appelée ainsi lors du linkage du shader. Vu qu'elle possède 3 composantes RGB alors elle sera de type vec3. Si nous avions spécifié la valeur Alpha dans le tableau de couleurs, elle aurait été de type vec4.

// Entrée Shader

in vec3 in_Color;

Ce qui donne le code source suivant :

// Version du GLSL

#version 150 core

// Entrées

in vec2 in_Vertex;

in vec3 in_Color;

// Fonction main

void main()

{

    // Position finale du vertex

    gl_Position = vec4(in_Vertex, 0.0, 1.0);

}

Maintenant que l'on a récupéré la couleur du vertex, nous allons pouvoir l'envoyer au Fragment Shader. Pour cela, nous allons utiliser ..... une variable out !

Et oui, rappelez-vous que les variables out du Vertex Shader sont automatiquement envoyées au shader suivant. La seule condition pour pouvoir les utiliser est qu'elles doivent être strictement identiques dans les deux codes sources. C'est-à-dire qu'elles doivent avoir le même type et surtout le même nom. Si vous ne respectez pas l'une de ces deux conditions, alors OpenGL ne fera pas le lien entre les deux. ;)

Pour envoyer notre couleur donc, nous devons utiliser une variable out qui sera du type vec3 de façon à pouvoir envoyer toutes les composantes. Nous l'appellerons simplement color :

// Sortie

out vec3 color;

Ce qui donne le code suivant :

// Version du GLSL

#version 150 core

// Entrées

in vec2 in_Vertex;

in vec3 in_Color;

// Sortie

out vec3 color;

// Fonction main

void main()

{

    // Position finale du vertex

    gl_Position = vec4(in_Vertex, 0.0, 1.0);

}

Pourquoi on ne l'appelle pas out_Color ? Vu qu'elle sort du shader on peut la nommer ainsi ?

Vous pouvez certes, mais il y aura un petit problème de logique. En effet, je vous ai dit qu'elle doit avoir le même nom dans les deux codes sources, ce qui voudrait dire que la variable d'entrée in du Fragment Shader aurait un nom qui commencerait pas out_ ? Ce n'est pas logique évidemment.

Enfin bref, maintenant que nous avons un moyen de communiquer avec le Fragment Shader, nous allons pouvoir lui envoyer le contenu de la variable in_Color. Nous n'avons pas besoin de constructeur vu qu'elles sont de même type :

void main()

{

    // Position finale du vertex

    gl_Position = vec4(in_Vertex, 0.0, 1.0);

    // Envoi de la couleur au Fragment Shader

    color = in_Color;

}

Ce qui donne le code source final :

// Version du GLSL

#version 150 core

// Entrées

in vec2 in_Vertex;

in vec3 in_Color;

// Sortie

out vec3 color;

// Fonction main

void main()

{

    // Position finale du vertex

    gl_Position = vec4(in_Vertex, 0.0, 1.0);

    // Envoi de la couleur au Fragment Shader

    color = in_Color;

}

Et voilà, Vertex Shader terminé. :D

Le Fragment Shader

La Fragment Shader va être très simple à programmer car il ne fera que récupérer une couleur pour la renvoyer ensuite dans une variable de sortie out.

La première chose à faire va être de re-déclarer la variable color qui sort du Vertex Shader précédent. Et comme vous le savez maintenant, elle doit être exactement identique au code source précédent. On conserve donc le même type et le même nom, la seule différence va être le mot-clef utilisé car elle ne sort pas mais elle rentre dans le Fragment Shader. On utilise donc non pas le mot-clef out mais in :

// Entrée

in vec3 color;

Ce qui donne :

// Version du GLSL

#version 150 core

// Entrée

in vec3 color;

// Sortie

out vec4 out_Color;

// Fonction main

void main()

{

    // Couleur finale du pixel

    out_Color = vec4(1.0, 1.0, 1.0, 1.0);

}

Le nom et le type sont respectés, la variable color est donc utilisable. D'ailleurs, nous allons l'utiliser tout de suite en enlevant le code de la couleur blanc pour affecter son contenu à la variable out_Color.

Si vous faites attention, vous remarquerez qu'il y a un conflit de type entre les deux variables. En effet, l'une est de type vec3 alors que l'autre est de type vec4. Pour régler ce problème, il va falloir utiliser .... un constructeur, encore une fois !

Et pour mon plus grand plaisir, je vais vous demander de convertir la variable color vous-même. Ce n'est pas compliqué bien sûr, nous avons déjà fait cet opération plusieurs fois, vous devez juste convertir un vec3 et vec4 en rajoutant la composante Alpha (1.0).

.....

Vous avez trouvé ?

Voici la solution :

void main()

{

    // Couleur finale du pixel

    out_Color = vec4(color, 1.0);

}

Il ne manque qu'une coordonnée au vec3 pour devenir un vec4. On appelle donc le bon constructeur en ajoutant la composante Alpha à la variable color. Le résultat sera une variable de type vec4.

Ce qui donne le code source final :

// Version du GLSL

#version 150 core

// Entrée

in vec3 color;

// Sortie

out vec4 out_Color;

// Fonction main

void main()

{

    // Couleur finale du pixel

    out_Color = vec4(color, 1.0);

}

Vous pouvez maintenant relancer votre projet, vous devriez avoir le résultat suivant :

Notre shader est maintenant capable de gérer les couleurs. :D Vous pouvez même changer le tableau des composantes pour vérifier que tout fonctionne correctement :

float couleurs[] = {0.5, 1.0, 0.0,   0.5, 1.0, 0.0,   0.5, 1.0, 0.0,    // Triangle 1

                    0.5, 0.0, 1.0,   0.5, 0.0, 1.0,   0.5, 0.0, 1.0};   // Triangle 2

Exercices

Énoncés

On continue notre petite vague d'exercices à l'instar de la partie précédente. Je vais monter un peu le niveau cette fois, il faudra réfléchir un peu plus. :p

Exercice 1 : Ajoutez la composante Alpha pour chaque couleur dans le tableau couleurs. Modifiez ensuite le shader pour gérer les couleurs entrantes à 4 composantes. (Pensez à modifier le VBO et le VAO pour prendre en compte la nouvelle composante.)

Exercice 2 : Inversez la couleur entrante dans le Fragment Shader avant de l'affecter à la couleur sortante. Pour vous donner un indice : inverser une couleur revient à inverser l'ordre des composantes en passant de l'ordre RGB à BGR. La composante Alpha reste cependant toujours à la fin. Vous pouvez reprendre le code de l’exercice précédent, ou utiliser celui du cours.

Exercice 3 : Oubliez complétement le tableau de couleurs et créez une variable maCouleur (à la place de Color) de type vec4 dans le Vertex Shader. Ses composantes doivent permettre d'afficher du bleu. L'objectif est d'envoyer cette variable au Fragment Shader à la place des données issues du tableau Vertex Attrib.

Exercice 4 : Reprenez le même principe que l'exercice précédent (voire correction si besoin) sauf que la couleur à envoyer doit dépendre de l’abscisse du vertex. Si la coordonnée x est supérieure à zéro alors vous devez envoyer la couleur bleu, si elle est inférieure ou égale à zéro alors vous devez envoyer la couleur rouge.

Solutions

Exercice 1 :

Pour ajouter la composante Alpha aux couleurs, il suffit de rajouter la valeur 1.0 au tableau couleurs[] :

float couleurs[] = {0.5, 1.0, 0.0, 1.0,   0.5, 1.0, 0.0, 1.0,   0.5, 1.0, 0.0, 1.0,    // Triangle 1

                    0.5, 0.0, 1.0, 1.0,   0.5, 0.0, 1.0, 1.0,   0.5, 0.0, 1.0, 1.0};   // Triangle 2

Pensez à modifier la variable tailleCouleursBytes pour prendre en compte les nouvelles données soit 4 composantes x 3 coordonnées x 2 triangles = 24 valeurs :

// Gestion du VBO

GLuint vbo;

int tailleVerticesBytes = 12 * sizeof(float);

int tailleCouleursBytes = 24 * sizeof(float);

Pensez également à modifier le paramètre size du tableau Vertex Attrib 1 pour le passer à 4 (pour 4 composantes) :

// Vertex Attrib 1 (Couleurs)

glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(tailleVerticesBytes));

glEnableVertexAttribArray(1);

Dans le Vertex Shader, la variable entrante in_Color n'est plus de type vec3 mais de type vec4 maintenant. La variable sortante color devient donc elle-aussi une vec4 :

// Version du GLSL

#version 150 core

// Entrées

in vec2 in_Vertex;

in vec4 in_Color;

// Sortie

out vec4 color;

// Fonction main

void main()

{

    // Position finale du vertex

    gl_Position = vec4(in_Vertex, 0.0, 1.0);

    // Envoi de la couleur au Fragment Shader

    color = in_Color;

}

Dans le Fragment Shader, on commence évidemment par changer le type de la variable color en vec4. Puis on enlève le constructeur vu qu'elle possède maintenant le même type que la variable sortante out_Color. On peut donc l'affecter normalement :

// Version du GLSL

#version 150 core

// Entrée

in vec4 color;

// Sortie

out vec4 out_Color;

// Fonction main

void main()

{

    // Couleur finale du pixel

    out_Color = color;

}

Exercice 2 :

Pour inverser une couleur, il suffit juste d'inverser les 'sous-variables' (x, y, z) avant d'affecter la variable color à out_Color. On peut appeler le constructeur vec4() ou affecter les valeurs à la main, comme vous voulez. Mais il est préférable d'utiliser la première solution :

// Version du GLSL

#version 150 core

// Entrée

in vec4 color;

// Sortie

out vec4 out_Color;

// Fonction main

void main()

{

    // Couleur finale du pixel

    out_Color = vec4(color.z, color.y, color.x, color.w);

}

Il y a un moyen de raccourcir encore plus ce code et même d'utiliser d'autres noms pour les composantes, mais je ne vous en parle pas maintenant pour éviter de nous embrouiller l'esprit.

Le Vertex Shader n'a pas besoin d'être modifié car il ne fait qu'envoyer la couleur, il ne fait pas de traitement dessus.

Exercice 3 :

On commence par supprimer la variable Color dans le Vertex Shader pour la remplacer par une nouvelle. On la nomme maCouleur et on lui donne le type vec4. On utilise également le mot-clef out puisqu'elle sort du shader :

// Sortie

out vec4 maCouleur;

Ensuite, on lui assigne les 4 composantes permettant d'afficher du bleu :

void main()

{

    // Position finale du Vertex

    gl_Position = vec4(in_Vertex, 0.0, 1.0);

    // Envoi de la couleur au Fragment Shader

    maCouleur = vec4(0.0, 0.0, 1.0, 1.0);

}

Ce qui donne le code source :

// Version du GLSL

#version 150 core

// Entrées

in vec2 in_Vertex;

in vec4 in_Color;

// Sortie

out vec4 maCouleur;

// Fonction main

void main()

{

    // Position du vertex

    gl_Position = vec4(in_Vertex, 0.0, 1.0);

    // Envoi de la couleur au Fragment Shader

    maCouleur = vec4(0.0, 0.0, 1.0, 1.0);

}

Quant au Fragment Shader, on supprime simplement la variable in Color pour la remplacer par maCouleur afin de respecter les conditions des entrées-sorties :

// Entrée

in vec4 maCouleur;

Il ne reste plus qu'à assigner le contenu de la variable maCouleur à out_Color. On n'utilise pas de constructeur vu qu'elles sont de même type :

// Version du GLSL

#version 150 core

// Entrée

in vec4 maCouleur;

// Sortie

out vec4 out_Color;

// Fonction main

void main()

{

    // Couleur finale du pixel

    out_Color = maCouleur;

}

Exercice 4 :

On reprend le même code que précédemment auquel on va rajouter une condition pour choisir une couleur dans le Vertex Shader. Si la coordonnée x de la variable in_Vertex est supérieure à zéro alors on assigne la couleur bleu à maCouleur. Dans le cas contraire, on assigne la couleur rouge :

// Version du GLSL

#version 150 core

// Entrées

in vec2 in_Vertex;

in vec4 in_Color;

// Sortie

out vec4 maCouleur;

// Fonction main

void main()

{

    // Position du vertex

    gl_Position = vec4(in_Vertex, 0.0, 1.0);

    // Si la coordonnée x est supérieure à 0

    if(in_Vertex.x > 0)

        maCouleur = vec4(0.0, 0.0, 1.0, 1.0);

    // Dans le cas inverse

    else

        maCouleur = vec4(1.0, 0.0, 0.0, 1.0);

}

Il n'y a rien à changer au niveau du Fragment Shader.

Télécharger (Windows, UNIX/Linux, Mac OS X) : code source C++ du chapitre sur les shaders (Partie 1/2)

Nous avons vu pas mal de choses dans ce chapitre, nous avons même appris à programmer nos premiers effets ! :D

Vous savez maintenant comment gérer les vertices du coté des shaders pour afficher quelque chose à l'écran. Vous savez même gérer les tableaux de couleur pour colorier toutes les surfaces à votre guise. Ces effets sont assez simples et ne gèrent pas la 3D mais au moins, vous avez fait vos premiers pas dans le développement de shaders.

Si vous êtes prêts, je vous invite à lire le prochain chapitre qui va nous permettre d'apprendre à intégrer la troisième dimension et afficher des textures.


Les shaders démystifiés (Partie 2/2)

Précédemment, nous avons commencé à démystifier un peu les shaders en étudiant leur comportement dans un environnement en deux dimensions. Nous avons même vu comment gérer la couleur grâce aux tableaux Vertex Attrib et envoyer des variables d'un shader à un autre.

Ces échanges de données seront quasiment toujours présents quelque soit les effets que l'on voudra programmer. Si vous n'êtes pas à l'aise avec ça, je vous conseille vivement de relire le chapitre précédent. ;)

D'ailleurs, ce que nous allons voir aujourd'hui concerne également les échanges de données sauf qu'ici, nous parlerons d'échanges entre l'application principale (en C++) et les shaders (en GLSL). Grâce à eux, nous pourrons enfin envoyer nos matrices à nos codes sources et nous pourrons enfin gérer la 3D. :D

Nous parlerons aussi des derniers petits points à connaitre sur la programmation GLSL.

Les variables uniform

Qu'est-ce qu'une variable uniform ?

On commence ce chapitre par une notion que l'on retrouvera dans tous les shaders un tant soit peu développés. ;)

Les variables uniform sont des variables envoyées depuis une application classique (codée en C++) jusqu'à un shader. Elles peuvent être utilisées pour envoyer tous les types de données possibles au niveau du GLSL soit les variables classiques (float, int, etc.), les vecteurs et les matrices. Nous connaissons particulièrement ce dernier cas car nous avons déjà eu l'occasion d'envoyer les fameuses matrices projection et modelview, ce qui permettait d'intégrer la troisième dimension à nos applications.

D'ailleurs lorsque nous les avons vues, je vous avais fourni deux fonctions totalement incompréhensibles qui permettaient d'envoyer des matrices aux shaders. Elles ressemblaient à ceci :

                                    // Envoi des matrices au shader

                                    glUniformMatrix4fv(glGetUniformLocation(shader.getProgramID(),
                                    "projection"),
                                    1,
                                    GL_FALSE,
                                    value_ptr(projection));

                                    glUniformMatrix4fv(glGetUniformLocation(shader.getProgramID(),
                                    "modelview"),
                                    1,
                                    GL_FALSE,
                                    value_ptr(modelview));

Complément incompréhensible bien sûr. :p

Vous remarquerez cependant l'utilisation du terme uniform dans le nom de la fonction glUniformMatrix4fv(). Ce qui signifie que les matrices sont bien considérées comme des variables uniform. Elles arrivent donc directement dans le shader. C'est un exemple d'utilisation que l'on rencontrera tout le temps, mais les matrices ne sont pas les seules valeurs à pouvoir être envoyées comme nous allons le voir.

Envoyer une variable simple

Le mot-clef uniform

Avant d'étudier en détails les fonctions que l'on vient de voir, nous allons faire un petit détour au niveau des codes sources GLSL. Les variables uniform se comportent un peu comme les in et les out dans le sens où ils possèdent un mot-clef qui leur est propres. Ce mot-clef est simplement uniform.

Prenons un petit exemple en déclarant une variable float en tant qu'uniform :

// Version du GLSL
#version 150 core
// Variable uniform
uniform float maVariable;
// Fonction main
void main()
{
}

Ce nouveau mot-clef indique au shader que l'application enverra une variable au code source GLSL. Bien évidemment, celle-ci se comportera comme une variable classique sauf qu'on ne pourra pas la modifier. Nous pourrons toujours l'utiliser dans des opérations arithmétiques, dans des conditions, ... mais nous ne pourrons jamais modifier sa valeur directement.

Il faut savoir également qu'une variable uniform est accessible dans les deux types de shaders (Vertex et Fragment), nous n'avons donc pas besoin de les manipuler avec les entrées-sorties ni de spécifier dans quel code source les utiliser. Ça les rend plus simple à utiliser. :)

La localisation et son utilisation

On revient maintenant aux fonctions de gestion des uniform au niveau de l'application. Nous en avons rapidement survolé une que nous connaissions déjà depuis un moment et qui s’appelait glUniformMatrix4fv(). Celle-ci permet d'envoyer une matrice directement au shader.

Cependant, ce n'est pas la seule fonction qui permet d'envoyer des données. Elle possède plusieurs 'sœurs' qui prennent en compte d'autres types comme les variables simples (float, int, etc.) et les vecteurs. Tous ces cas se gèrent un peu de la même façon, il n'y a que les paramètres à utiliser qui vont changer.

D'ailleurs nous allons étudier en premier le cas des variables simples car c'est celui qui en requiert le moins. :p Nous aurons besoin pour cela d'utiliser une fonction dont le nom se rapproche fortement de celui que nous connaissons déjà. Elle s'appelle glUniform1f() :

void glUniform1f(GLint location, GLfloat value);

A noter qu'il existe aussi les fonctions glUniform1i() et glUniform1ui() qui permettent d'envoyer respectivement des entiers et des entiers non-signés.

  • location : paramètre permettant de retrouver la variable utilisant le mot-clef uniform dans le code source GLSL

  • value : La valeur (de type float) que vous souhaitez envoyer

Le premier paramètre peut vous sembler un peu flou et c'est tout à fait normal. Pour le comprendre, nous allons faire une analogie avec la fonction glBindAttribLocation(). Celle-ci demandait, parmi ses paramètres, le nom correspondant à la variable de destination dans le code GLSL. Par exemple, nous l'utilisions pour faire le lien entre la variable in_Vertex et le tableau de vertices. Même chose avec la variable in_Color et le tableau de couleurs.

Cette fonction nous permettait donc de spécifier directement un nom pour retrouver son destinataire dans le code GLSL.

Le problème avec les uniform, c'est que ce système de nom n'existe pas directement. Il faut passer par une fonction intermédiaire pour faire le lien entre le shader et l'application. Cette fonction intermédiaire s'appelle glGetUniformLocation() :

GLint glGetUniformLocation(GLuint program, const GLchar *name)

  • program : l'ID du programme shader. Nous utiliserons la méthode glGetProgramID() pour lui affecter une valeur

  • name : nom de la variable dans le code source GLSL

Grâce à elle, nous pouvons trouver facilement le paramètre location de glUniform1f().

On reprend notre petit exemple précédent qui contenait la variable uniformmaVariable. Pour récupérer sa localisation au sein du shader, on appelle donc la fonction glGetUniformLocation() en donnant en paramètre l'ID du programme ainsi qu'une chaine de caractère contenant le nom "maVariable" :

// Localisation de maVariable
int localisation = glGetUniformLocation(shader.getProgramID(), "maVariable");

Maintenant que l'on connait la localisation de maVariable, nous pouvons lui envoyer une valeur. On utilise pour cela la fonction glUniform1f() en donnant en paramètre cette fameuse localisation ainsi qu'une valeur (par exemple 8.5) :

// Localisation de maVariable
int localisation = glGetUniformLocation(shader.getProgramID(), "maVariable");
// Envoi d'une valeur à maVariable
glUniform1f(localisation, 8.5);

Vous ne devez envoyer vos variable uniform qu'après avoir activé votre shader, sinon il aura un peu de mal à les recevoir.

La variable uniform dans le code GLSL possèdera maintenant la valeur 8.5. ;)

Petit détail, il est tout à fait possible de combiner les deux fonctions utilisées comme nous l'avons vu pour les matrices :

// Envoi d'une valeur à maVariable
glUniform1f(glGetUniformLocation(shader.getProgramID(), "maVariable"), 8.5);
Point récapitulatif

Nous allons faire un petit point récapitulatif car ce que nous venons de voir constitue la base de l'envoi de variable au shader. Il n'y a que trois points à retenir alors ouvrez grand vos oreilles (ou plutôt vos yeux :p ) :

  • Premièrement, il faut déclarer une variable avec le mot-clef uniform dans le code source GLSL.

  • Ensuite, on récupère sa localisation grâce à la fonction glGetUniformLocation() du coté de l'application C++

  • Puis, on envoie la valeur souhaitée grâce à la fonction glUniform1f() ou ses variantes

Je le répète, ces 3 points constituent la base du fonctionnement des uniform. Retenez-les bien. Nous ferons quelques exercices à la fin de cette partie pour vous familiariser avec eux.

Envoyer un vecteur

Coté GLSL

Comme nous l'avons vu précédemment, il existe une multitude de variantes de la fonction glUniform1f() qui permettent d'envoyer d'autres types de variable que les float ou les int. Nous allons justement voir une de ces variantes qui nous servira à envoyer des vecteurs (à 2, 3, ou 4 coordonnées).

Pour illustrer cela, nous allons déclarer une variable position de type vec3 avec le mot-clef uniform dans le Vertex Shader :

// Version du GLSL
#version 150 core
// Variable uniform
uniform vec3 position;
// Fonction main
void main()
{
}
Coté application

Le détail qui vous a peut-être frappé dans le nom de la fonction glUniform1f() est l'utilisation du chiffre 1 qui, apparemment, n'avait rien à faire ici. Ce chiffre est en fait très important car il permet de déterminer le nombre de données à envoyer à la variable de destination.

Lorsqu'il s'agit d'un variable simple comme un float, nous n'avons besoin d'envoyer qu'une seule valeur. Cependant lorsque l'on parle de vecteur, le nombre de données à envoyer devient un peu plus important car il faut affecter une valeur à chacune de ses coordonnées.

Vu qu'il existe 3 types de vecteur (vec2, vec3 et vec4), il existe donc 3 formes possibles pour la fonction glUniform*(). La seule chose qui va être modifiée avec celle que l'on connait déjà est le fameux chiffre utilisé à la fin du nom. Pour envoyer un vecteur à 2 coordonnées par exemple, il faudra mettre la valeur 2, ce qui donnera la fonction suivante :

// Vecteur à 2 coordonnées
void glUniform2f(GLint location, GLfloat valu0, GLfloat value1);

  • location : localisation du vecteur au niveau GLSL

  • value0 : valeur pour la coordonnée x

  • valeur1 : valeur pour la coordonnée y

Pour les vecteurs à 3 ou 4 coordonnées, il faudra mettre le chiffre correspondant au nom de la fonction glUniform() :

// Vecteur à 3 coordonnées
void glUniform3f(GLint location, GLfloat valu0, GLfloat value1, GLfloat valu2);
// Vecteur à 4 coordonnées
void glUniform4f(GLint location, GLfloat valu0, GLfloat value1, GLfloat valu2, GLfloat value3);

  • location : localisation du vecteur au niveau GLSL

  • value0 : valeur pour la coordonnée x

  • valeur1 : valeur pour la coordonnée y

  • valeur2 : valeur pour la coordonnée z

  • valeur3 : valeur pour la coordonnée w

Si on reprend l'exemple du vecteur vec3 position déclaré dans le shader, la fonction à utiliser sera glUniform3f(). Il faudra lui donner en paramètre la localisation de la variable ainsi qu'une valeur pour chacune de ses 3 coordonnées :

// Localisation du vecteur position
int localisation = glGetUniformLocation(shader.getProgramID(), "position");
// Envoi d'une valeur au vecteur position
glUniform3f(localisation, 1.0, 2.0, 3.0);

Le vecteur position est maintenant utilisable dans notre shader :

// Version du GLSL
#version 150 core
// Variable uniform
uniform vec3 position;
// Fonction main
void main()
{
// Exemple d'utilisation
if(position.x >= 0)
.... ;
else
.... ;
}

Le fonctionnement reste évidemment le même pour les autres types de vecteur.

Envoyer une matrice

Coté GLSL

Avant de s'occuper de la partie application, nous allons déclarer une matrice dans le Vertex Shader. Nous l’appellerons modelview en référence avec une certaine matrice que l'on connait déjà. ;)

Nous déclarons donc une matrice carrée d'ordre 4 (mat4) avec le mot-clef uniform :

// Version du GLSL
#version 150 core
// Variable uniform
uniform mat4 modelview;
// Fonction main
void main()
{
}
"Transposer" une matrice

Les envois de matrice se passent quasiment de la même façon que ceux que nous venons d'étudier. A vrai dire, il n'y a que deux paramètres supplémentaires à gérer. Pour envoyer une matrice carrée d'ordre 4 par exemple, la fonction à utiliser s'appelle glUniformMatrix4fv() :

void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);

  • location : localisation de la matrice au niveau GLSL

  • count : nombre de sous-tableaux utilisés par la matrice. Nous lui affecterons la valeur 1.0 car nous n'en enverrons qu'un seul.

  • transpose : booléen pouvant prendre la valeur GL_TRUE ou GL_FALSE et qui permet de transposer une matrice. Nous verrons ce que cela signifie dans un instant

  • value : pointeur sur les valeurs de la matrice. Nous lui donnerons le résultat de la méthode getValeurs()

A noter aussi qu'il existe les fonctions glUniformMatrix2fv() et glUniformMatrix3fv() qui permettent d'envoyer respectivement des matrices carrées d'ordre 2 et 3. Elles prennent les mêmes paramètres que celle que nous étudions actuellement.

Le paramètre transpose peut vous sembler un peu flou pour le moment. C'est parce que nous n'avons pas encore vu ce que voulait dire le terme "transposer" une matrice. C'est pourtant une notion assez simple qui signifie "inverser" les valeurs d'une matrice pour que les lignes se retrouvent à la place des colonnes et les colonnes à la place des lignes :

Si vous utilisez GLM, ce qui est normalement le cas, vous n'avez pas à vous soucier de ce paramètre car la librairie est en parfaite adéquation avec le GLSL. C'est-à-dire que les objets mat4 en C++ se lisent aussi en colonne et c'est exactement ce que veut le shader :

Si vous utilisez une autre librairie mathématique pour gérer vos matrices, je vous conseille de vérifier leur ordre de lecture car elles doivent peut-être être transposées. C'est-à-dire qu'elles se lisent peut-être en ligne :

Si c'est le cas, vous devez les transposer à l'aide du paramètre transpose.

Coté application

Ceci étant dit, nous pouvons maintenant repasser à la fonction . Nous allons pouvoir l'utiliser pour envoyer notre matrice modelview à la variable du même nom dans le code source GLSL.

Nous savons qu'elle prendra 4 paramètres :

  • location : qu'il faudra déterminer comme nous savons déjà le faire

  • count : auquel nous affecterons la valeur 1.0

  • transpose : auquel il faudra affecter la valeur GL_FALSE car nous n'avons pas besoin de transposer nos matrices

  • value : qui demande un pointeur sur les valeurs à envoyer

Ainsi, pour envoyer la matrice modelview au sahder, nous devrons d'abord la localiser à l'aide de la fonction glGetUniformLocation() :

// Localisation de la matrice modelview
int localisation = glGetUniformLocation(shader.getProgramID(), "modelview");

Puis, nous envoyons ses valeurs grâce à la fonction glUniformMatrix4fv() avec les paramètres cités précédemment :

// Localisation de la matrice modelview
int localisation = glGetUniformLocation(shader.getProgramID(), "modelview");
// Envoi de la matrice
glUniformMatrix4fv(localisation, 1, GL_FALSE, value_ptr(modelview));

La variable uniform au niveau du shader contient maintenant les valeurs de la matrice modelview.

Vous êtes à présent capables de comprendre les fameux envois que nous utilisons depuis le chapitre sur la 3D. :p

// Envoi des matrices au shader
glUniformMatrix4fv(glGetUniformLocation(shader.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));
glUniformMatrix4fv(glGetUniformLocation(shader.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));

Envoyer un tableau

Coté GLSL

L'envoi de tableaux est parfois utile lorsque vous souhaitez envoyer plusieurs variables en une seule fois. Nous savons déjà comment faire en plus car ce type d'envoi s'effectue exactement de la même façon que les matrices. Les paramètres sont eux-aussi identiques, il n'y a que le nom de la fonction à utiliser qui va changer.

Au niveau du GLSL, il suffit simplement de déclarer un tableau comme nous le ferions en temps normal mais précédé par le mot-clef uniform. Pour déclarer un tableau de 10 cases par exemple, nous ferions ainsi :

// Version du GLSL
#version 150 core
// Variable uniform
uniform float monTableau[10];
// Fonction main
void main()
{
}
Coté application

La fonction à utiliser est très similaire à celle des matrices au niveau des paramètres. Elle s’appelle : glUniform1fv() :

void glUniform1fv(GLint location, GLsizei count, const GLfloat *value);

  • location : localisation du tableau au niveau GLSL

  • count : taille du tableau

  • value : pointeur sur les données

Attention, la fonction possède la lettre v à la fin de son nom, ce qui la différencie de glUniform1f().

Pour envoyer un tableau de 10 float à l'uniform précédent, il suffit donc d'appeler la fonction glUniform1fv() avec les paramètres suivants :

// Tableau de 10 float
float tableau[10] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 1.0};
// Localisation du tableau monTableau
int localisation = glGetUniformLocation(shader.getProgramID(), "monTableau");
// Envoi des valeurs
glUniform1fv(localisation, 10, tableau);

Ce qu'il faut retenir

Les variables uniform sont un peu complexes au premier abord mais vous avez remarqué qu'ils se gèrent tous de la même façon. La seule difficulté vient du nombre de variantes de la fonction glUniform*() à utiliser. Nous allons faire un petit résumé de ce qu'il faut retenir pour éviter de vous emmêler les pinceaux dans le futur.

Tout d'abord, il faut savoir que les uniform sont des variables envoyées de l'application jusqu'au shader. Elles doivent être déclarées avec le mot-clef uniform dans le code GLSL.

Pour envoyer des données, il faut commencer par localiser les variables contenues dans le shader grâce à la fonction glGetUniformLocation(). Puis, il faut appeler l'une des variantes suivantes :

  • glUniform1f() : pour envoyer des variables simples

  • glUniform*f() : pour envoyer des vecteurs. L'étoile dans le nom permet de définir le type utilisé dans le shader (vec2, vec3 ou vec4).

  • glUniformMatrix*fv() : pour envoyer des matrices. Même remarque pour l'étoile mais pour les types de matrice cette fois

  • glUniform1fv() : pour envoyer des tableaux

Enfin, la lettre f dans le nom des fonctions peut être remplacée par i ou ui pour envoyer respectivement des entiers ou des entiers non-signés. Si vous laissez la lettre f, alors vous enverrez des flottants.

C'est tout ce qu'il faut retenir. :)

Exercices

Énoncés

Comme d'habitude, nous allons faire quelques exercices pour assimiler ce que nous avons vu dans cette partie. Vous avez évidemment le droit de revenir sur le cours pour vous aider. ;)

Exercice 1 : Créez une variable de type float dans votre application que vous appellerez maCouleur. Envoyez-la ensuite au Fragment Shader pour qu'elle puisse remplacer la composante rouge de la variable d'entrée in Color au moment de l'affecter à la variable de sortie out_Color.

Exercice 2 : Créez un objet de type Vecteur dans votre application, ses attributs représenteront une couleur quelconque. Envoyez justement ces attributs au Fragment Shader pour définir la couleur du pixel.

Exercice 3 : Re-déclarez les matrices projection et modelview dans votre application (vous n'avez pas besoin des les initialiser). Envoyez-les ensuite dans le Vertex Shader pour les multiplier dans la fonction main(). Le résultat doit être contenu dans une variable de type mat4.

Solutions

Exercice 1 :

On commence par créer la variable maCouleur dans l'application, puis on l'envoie au shader depuis la boucle principale grâce à la fonction glUniform1f() :

// Variable à envoyer
float maCouleur(1.0);
....
// Boucle principale
while(!m_input.terminer())
{
....
// Localisation de la variable maCouleur
int localisationMaCouleur = glGetUniformLocation(shader.getProgramID(), "maCouleur");
// Envoi de la variable
glUniform1f(localisationMaCouleur, maCouleur);
....
}

Au niveau du Fragment Shader, on déclare la variable maCouleur avec le mot-clef uniform :

// Uniform
uniform float maCouleur;

Puis on modifie l'affectation de la variable de sortie out_Color en prenant en compte l'uniform et les composantes (y, z) de la variable d'entrée Color :

void main()
{
// Couleur du pixel
out_Color = vec4(maCouleur, color.y, color.z, 1.0);
}

Ce qui donne le code source suivant :

// Version du GLSL
#version 150 core
// Entrée
in vec3 color;
// Uniform
uniform float maCouleur;
// Sortie
out vec4 out_Color;
// Fonction main
void main()
{
// Couleur du pixel
out_Color = vec4(maCouleur, color.y, color.z, 1.0);
}

Exercice 2 :

On commence par créer un objet Vecteur dans l'application, puis on envoie ses coordonnées grâce à la fonction glUniform3f() :

// Vecteur à envoyer
Vecteur monVecteur(1.0, 0.0, 1.0);
....
// Boucle principale
while(!m_input.terminer())
{
....
// Localisation de la variable monVecteur
int localisationMonVecteur = glGetUniformLocation(shader.getProgramID(), "monVecteur");
// Envoi de la variable
glUniform3f(localisationMonVecteur, monVecteur.getX(), monVecteur.getY(), monVecteur.getZ());
....
}

Au niveau du Fragment Shader, on déclare la variable monVecteur de type vec3 avec le mot-clef uniform :

// Uniform
uniform vec3 monVecteur;

Puis on utilise le constructeur vec4() en donnant en paramètre monVecteur ainsi qu'une valeur représentant la composante Alpha (1.0) :

void main()
{
// Couleur du pixel
out_Color = vec4(monVecteur, 1.0);
}

Ce qui donne le code source suivant :

// Version du GLSL
#version 150 core
// Entrée
in vec3 color;
// Uniform
uniform vec3 monVecteur;
// Sortie
out vec4 out_Color;
// Fonction main
void main()
{
// Couleur du pixel
out_Color = vec4(monVecteur, 1.0);
}

Exercice 3 :

On commence par déclarer les matrices projection et modelview comme nous savons le faire depuis un moment déjà. Puis on les localise toutes les deux grâce à la fonction glGetUniformLocation() :

// Matrices
mat4 projection;
mat4 modelview;
....
// Boucle principale
while(!m_input.terminer())
{
....
// Localisation des matrices
int localisationProjection = glGetUniformLocation(shader.getProgramID(), "projection");
int localisationModelview = glGetUniformLocation(shader.getProgramID(), "modelview");
}

Ensuite, on les envoie au shader grâce à la fonction glUniformMatrix4fv() en n'oubliant pas des les transposer :

// Localisation des matrices
int localisationProjection = glGetUniformLocation(shader.getProgramID(), "projection");
int localisationModelview = glGetUniformLocation(shader.getProgramID(), "modelview");
// Envoi des matrices
glUniformMatrix4fv(localisationProjection, 1, GL_FALSE, value_ptr(projection));
glUniformMatrix4fv(localisationModelview, 1, GL_FALSE, value_ptr(modelview));

Au niveau du Vertex Shader, on déclare deux variables uniform de type mat4 :

// Uniform
uniform mat4 projection;
uniform mat4 modelview;

Enfin, il ne reste plus qu'à les multiplier en prenant soin de créer une variable pour contenir le résultat de l'opération :

void main()
{
// Multiplication des deux matrices
mat4 resultat = projection * modelview;
....
}

Ce qui donne le code source suivant :

// Version du GLSL
#version 150 core
// Entrées
in vec2 in_Vertex;
in vec3 in_Color;
// Uniform
uniform mat4 projection;
uniform mat4 modelview;
// Sortie
out vec3 color;
// Fonction main
void main()
{
// Multiplication des deux matrices
mat4 resultat = projection * modelview;
// Position finale du vertex
gl_Position = vec4(in_Vertex, 0.0, 1.0);
// Envoi de la couleur au Fragment Shader
color = in_Color;
}

Gestion de la 3D

Préparation

Dans la partie précédente, nous avons vu ce qu'étaient les variables uniform et comment elles fonctionnaient. Nous allons maintenant pouvoir en tirer profit en envoyant nos fameuses matrices à notre shader. Pour rappel, la matrice modelview permet de "placer" un modèle dans un monde en 3D. La matrice de projection quant à elle permet de transformer, ou plutôt projeter, ce monde vers notre écran; ce dernier n'étant qu'en 2 dimensions.

Ce qu'il faut comprendre c'est qu'elles ont chacune une utilité bien particulière. Elles sont complémentaires pour afficher quelque chose mais elles sont totalement différentes dans le fond.

Pour afficher des modèles en 3D, nous aurons donc besoin de les envoyer toutes les deux au shader que l'on utilisera grâce aux uniform.

Le gros avantage avec la préparation du code c'est qu'on va pouvoir supprimer le gros code tout moche de la boucle principale. En effet, nous n'avons plus besoin d'afficher un carré en 2D pour faire nos tests, nous avons besoin de modèles 3D. Et nous avons justement codé deux classes qui permettent d'en afficher (Cube et Caisse), nous n'allons donc pas nous priver de leur utilisation. :p

On peut donc supprimer tout le code relatif au carré pour ne garder que ceci (sans la boucle while) :

void SceneOpenGL::bouclePrincipale()
{
// Variables
unsigned int frameRate (1000 / 50);
Uint32 debutBoucle(0), finBoucle(0), tempsEcoule(0);
// Matrices
mat4 projection;
mat4 modelview;
projection = perspective(70.0, (double) m_largeurFenetre / m_hauteurFenetre, 1.0, 100.0);
modelview = mat4(1.0);
// Boucle principale
while(!m_input.terminer())
{
}
}

Profitons-en dès maintenant pour réutiliser notre bonne vielle caméra en déclarant un objet Camera et en la positionnant en 3D au point de coordonnées (3, 3, 3) :

// Caméra mobile
Camera camera(vec3(3, 3, 3), vec3(0, 0, 0), vec3(0, 1, 0), 0.5, 0.5);

Pour tester l'implémentation de la 3D, nous allons utiliser un objet Cube. Nous verrons par la suite la façon de gérer les textures avec un objet Caisse. Nous lui donnerons une taille de 2.0 ainsi que le chemin vers deux nouveaux fichiers que nous appellerons couleur3D.vert et couleur3D.frag (n'oubliez pas de les placer dans le dossier Shaders de votre projet) :

// Objet Cube
Cube cube(2.0, "Shaders/couleur3D.vert", "Shaders/couleur3D.frag");
cube.charger();

Au niveau de la boucle principale, on supprime tout le code relatif à l'ancien carré pour ne garder que ceci :

// Boucle principale
while(!m_input.terminer())
{
// On définit le temps de début de boucle
debutBoucle = SDL_GetTicks();
// Gestion des évènements
m_input.updateEvenements();
if(m_input.getTouche(SDL_SCANCODE_ESCAPE))
break;
// Gestion du déplacement de la caméra
camera.deplacer();
// Nettoyage de l'écran
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Placement de la caméra
camera.lookAt(modelview);
// Rendu
// Actualisation de la fenêtre
SDL_GL_SwapWindow(m_fenetre);
// Calcul du temps écoulé
finBoucle = SDL_GetTicks();
tempsEcoule = finBoucle - debutBoucle;
// Si nécessaire, on met en pause le programme
if(tempsEcoule < frameRate)
SDL_Delay(frameRate - tempsEcoule);
}

Remarquez les appels aux méthodes deplacer() et lookAt() de la caméra. N'utilisez pas directement celle de la matrice modelview.

On y ajoute ensuite l'appel à la méthode afficher() de l'objet cube. On lui donnera au passage les matrices projection et modelview :

// Placement de la caméra
....
// Affichage du cube
cube.afficher(projection, modelview);
// Actualisation de la fenêtre
....

La préparation du code est terminée. ;)

Intégrer la troisième dimension

Coté application

Pour programmer nos premiers shaders "3D", nous avons vu à l'instant qu'il fallait envoyer les matrices projection et modelview en tant que variable uniform. Nous aurons besoin pour cela de la fonction glUniformMatrix4fv() dont nous avons enfin appris son fonctionnement dans la partie précédente.

Vérifiez donc bien (même s'il ne devrait pas y avoir de problème) qu'elles sont bien présentes dans la méthode afficher() du cube :

// Verrouillage du VAO
glBindVertexArray(m_vaoID);
// Envoi des matrices
glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "projection"), 1, GL_FALSE, value_ptr(projection));
glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "modelview"), 1, GL_FALSE, value_ptr(modelview));
// Rendu
glDrawArrays(GL_TRIANGLES, 0, 36);
// Déverrouillage du VAO
glBindVertexArray(0);
Le Vertex Shader

La première chose à faire au niveau du GLSL va être la déclaration des matrices avec le mot-clef uniform. D'ailleurs, vu qu'elles sont relatives aux vertices, et non aux pixels, il faut les déclarer dans le Vertex Shader.

On reprend donc le code que nous avons laissé au chapitre précédent auquel on va rajouter les nouveaux uniform :

// Uniform
uniform mat4 projection;
uniform mat4 modelview;

Ce qui donne :

// Version du GLSL
#version 150 core
// Entrées
in vec2 in_Vertex;
in vec3 in_Color;
// Uniform
uniform mat4 projection;
uniform mat4 modelview;
// Sortie
out vec3 color;
// Fonction main
void main()
{
// Position finale du vertex
gl_Position = vec4(in_Vertex, 0.0, 1.0);
// Envoi de la couleur au Fragment Shader
color = in_Color;
}

Avant d'aller plus loin, pensez à modifier le type de la variable d'entrée in_Vertex. Vu qu'elle possède maintenant 3 coordonnées, son type devient donc vec3 :

// Entrées
in vec3 in_Vertex;

La variable in_Color ne change pas, elles a toujours ses 3 composantes.

Contrairement à ce qu'on pourrait penser, la gestion de la 3D est une chose d'assez simple à réaliser. Ce qu'il y a de plus dur à comprendre, c'est l'envoi des variables uniform. :p
En fait, il suffit juste de multiplier la variable in_Vertex par la matrice modelview et projection. De cette façon, on peut positionner le vertex dans le monde 3D et le projeter sur l'écran.

Le seul point sensible auquel il faut faire attention concerne l'ordre des membres dans la multiplication qui doivent se présenter de la manière suivante : projection x modelview x vertex. Si vous inversez un seul de ces membres alors votre calcul sera totalement faux. Dans le meilleur des cas, vous afficherez un bout de votre modèle et dans le pire, vous aurez un bel écran noir.

Euh au fait, c'est normal qu'on puisse multiplier une matrice par un vecteur ?

Oui tout à fait, nous avons même vu comment faire dans le chapitre sur les matrices :

La seule condition à respecter était le fait que le nombre de colonnes de la matrice soit égal au nombre de coordonnées du vecteur :

Pour le moment, notre vertex ne possède que 3 coordonnées, la multiplication est donc impossible. Cependant grâce aux constructeurs, nous allons pouvoir régler ce problème, il suffira juste d'ajouter la valeur 1.0 à la coordonnée W comme nous le faisions avant.

Pour résumer, nous devons multiplier les matrices que nous avons envoyées en uniform par la variable d'entrée correspondant au vertex. Vu qu'elles sont toutes considérées comme des variables en GLSL, nous allons pouvoir faire cette opération très simplement :

void main()
{
// Position finale du vertex en 3D
gl_Position = projection * modelview * vec4(in_Vertex, 1.0);
// Envoi de la couleur au Fragment Shader
color = in_Color;
}

Ce qui donne le code source final :

// Version du GLSL
#version 150 core
// Entrées
in vec3 in_Vertex;
in vec3 in_Color;
// Uniform
uniform mat4 projection;
uniform mat4 modelview;
// Sortie
out vec3 color;
// Fonction main
void main()
{
// Position finale du vertex en 3D
gl_Position = projection * modelview * vec4(in_Vertex, 1.0);
// Envoi de la couleur au Fragment Shader
color = in_Color;
}
Le Fragment Shader

Le code du Fragment Shader va être super simple à faire car il n'y a aucun modificatione à faire dessus. :p

En effet, nous n'avons pas modifié le contenu de la variable de sortie Color dans le Vertex Shader précédent. Il n'y a donc aucune modification à apporter au Fragment Shader puisqu'il se contente simplement de recopier cette variable dans sa sortie. Nous pouvons reprendre sans problème le code source du chapitre précédent (même s'il était prévu pour une utilisation 2D à la base) :

// Version du GLSL
#version 150 core
// Entrée
in vec3 color;
// Sortie
out vec4 out_Color;
// Fonction main
void main()
{
// Couleur finale du pixel
out_Color = vec4(color, 1.0);
}

Vous pouvez compiler votre projet pour admirer le retour de la 3D dans vos programmes. :D

Gestion des textures

Afficher une texture

Coté application

Nous allons maintenant passer au dernier shader qui reste encore un peu mystérieux pour le moment : celui qui permet d'afficher des textures.

Depuis que nous les utilisons, nous n'avons plus besoin de couleur pour afficher quelque chose à l'écran. Ce qui rajoute un semblant de réalisme à nos scènes. Pour les intégrer dans l'application, nous devons utiliser un tableau (différent de celui des couleurs) qui contient des coordonnées de texture. Ces coordonnées permettent de savoir où "plaquer" l'image que l'on veut afficher :

Avant de commencer, veuillez créer deux nouveaux fichiers sources que vous appellerez texture.vert et texture.frag.

Pour la suite, nous allons supprimer le code relatif au cube pour le remplacer par celui de la classe Caisse. Nous déclarons donc un objet de ce type avec en paramètres le chemin vers le deux nouveaux fichiers créés ainsi qu'un chemin supplémentaire pour sa texture :

// Objet Caisse
Caisse caisse(2.0, "Shaders/texture.vert", "Shaders/texture.frag", "Textures/Caisse2.jpg");
caisse.charger();

Au niveau de la boucle principale, nous n'avons qu'à supprimer l'appel à la méthode afficher() du cube pour le remplacer par celui de notre nouvelle caisse :

// Placement de la caméra
....
// Affichage de la caisse
caisse.afficher(projection, modelview);
// Actualisation de la fenêtre
....

Préparation terminée. :)

Le Vertex Shader

Pour intégrer les textures, nous n'avons pas de grande modification à faire dans le Vertex Shader car ce ne sera pas à lui de gérer leur affichage. En effet, ce n'est pas lui qui va prendre les pixels de l'image pour les mettre dans sa sortie, ça c'est le rôle du Fragment Shader. ;) La seule chose que l'on va modifier, c'est l'envoi de données au Fragment Shader. On n'enverra plus de couleurs mais des coordonnées de texture.

Dans notre code, on commence par reprendre celui que l'on utilisait précédemment auquel on va supprimer la variable d'entrée in_Color, qui est devenue inutile ici, ainsi que la variable de sortie color :

// Version du GLSL
#version 150 core
// Entrée
in vec3 in_Vertex;
// Uniform
uniform mat4 projection;
uniform mat4 modelview;
// Fonction main
void main()
{
// Position finale du vertex en 3D
gl_Position = projection * modelview * vec4(in_Vertex, 1.0);
}

Ensuite, on rajoute une nouvelle variable d'entrée qui va nous permettre de récupérer les coordonnées de texture. Nous l'avions appelée in_TexCoord0 lorsque nous avons utilisé la fonction glBindAttribLocation() pour faire le lien avec le tableau Vertex Attrib 2. Nous n'avons donc qu'à déclarer cette variable accompagnée du type vec2 (car il n'y a que 2 coordonnées) ainsi que du mot-clef in :

// Entrées
in vec3 in_Vertex;
in vec2 in_TexCoord0;

On en profite également pour déclarer une variable de sortie qui copiera le contenu de in_TexCoord0 pour l'envoyer au Fragment Shader. Nous l'appellerons coordTexture et sera elle-aussi du type vec2. Elle utilisera cependant le mot-clef out vu qu'elle sort du shader :

// Sortie
out vec2 coordTexture;

Enfin, pour terminer notre code source, nous devons simplement copier le contenu de la variable d'entrée dans celle de sortie. Le Fragment Shader se chargera du reste.

void main()
{
// Position finale du vertex en 3D
gl_Position = projection * modelview * vec4(in_Vertex, 1.0);
// Envoi des coordonnées de texture au Fragment Shader
coordTexture = in_TexCoord0;
}

Ce qui donne le code source final :

// Version du GLSL
#version 150 core
// Entrées
in vec3 in_Vertex;
in vec2 in_TexCoord0;
// Uniform
uniform mat4 projection;
uniform mat4 modelview;
// Sortie
out vec2 coordTexture;
// Fonction main
void main()
{
// Position finale du vertex en 3D
gl_Position = projection * modelview * vec4(in_Vertex, 1.0);
// Envoi des coordonnées de texture au Fragment Shader
coordTexture = in_TexCoord0;
}
Le Fragment Shader

Le Fragment Shader va subir un peu plus de modifications que son prédécesseur. Le premier sera évidemment la disparition de la variable d'entrée color qui n'est maintenant plus utilisée je vous le rappelle. ;)

A la place, nous mettrons la variable coordTexture que le Vertex Shader nous a gentiment envoyée à l'instant :

// Version du GLSL
#version 150 core
// Entrée
in vec2 coordTexture;
// Sortie
out vec4 out_Color;
// Fonction main
void main()
{
// Couleur finale du pixel
out_Color = vec4(color, 1.0);
}

Pour la suite, vous vous demandez peut-être comment intégrer une texture au sein d'un code source GLSL ? Vu que nous ne savons pas encore le faire, c'est une question qui peut paraître légitime.

Au risque de vous étonner, les textures sont en fait des variables uniform !

Euh des uniform ? Mais on a jamais utilisé la fonction glUniform*() pour en envoyer ?

Alors oui certes, nous ne l'avons jamais fait. Mais nous aurions pu !

L'appel à la fonction glUniform*() est facultatif si on n'envoie qu'une seule texture par shader. En revanche, si on en envoie plusieurs, il faut le faire pour pouvoir les différencier dans le code source GLSL. Nous aurons l'occasion de voir cela dans la partie sur les effets avancés. ;)

En attendant, cet appel reste facultatif. Cependant, nous devons quand même déclarer les textures en tant que variables uniform dans les shaders. D'ailleurs, elles possèdent un type particulier qui s'appelle sampler2D :

// Uniform
uniform sampler2D texture;

Vous pouvez donner n'importe quel nom à votre texture du moment que vous n'en envoyez qu'une. Le shader saura automatiquement laquelle utiliser.

L'objectif de cette variable en tout cas va être de récupérer la couleur du pixel recherché pour l'affecter à la variable de sortie out_Color. Pour le récupérer, nous allons utiliser notre première fonction prédéfinie dans le GLSL. Celle-ci s'appelle texture() :

vec4 texture(sampler2D texture, vec2 textCoord);

  • texture : la texture contenant les pixels. Nous lui donnerons la variable uniform texture

  • textCoord : le couple de coordonnées permettant de retrouver un pixel. Nous lui donnerons notre variable d'entrée coordTexture

Cette fonction renvoie un vec4 contentant la couleur du pixel que l'on recherche (composante Alpha comprise).

Nous devons donc faire appel à cette fonction pour trouver notre couleur finale. Le résultat sera affecté à la variable out_Color :

void main()
{
// Couleur du pixel
out_Color = texture(texture, coordTexture);
}

Ce qui donne le code source final :

// Version du GLSL
#version 150 core
// Entrée
in vec2 coordTexture;
// Uniform
uniform sampler2D texture;
// Sortie
out vec4 out_Color;
// Fonction main
void main()
{
// Couleur du pixel
out_Color = texture(texture, coordTexture);
}

Si vous compilez votre code, vous devriez avoir une belle caisse (sans jeu de mot :p ) affichée sur votre écran.

Ce qu'il faut retenir

Point récapitulatif

Nous allons faire un dernier point récapitulatif sur ce que nous avons vu dans ces deux dernières parties. Il n'y aura pas eu beaucoup de code nouveau mais il vaut mieux synthétiser toute ça pour éviter d’éventuelles zones d'ombre.

Ainsi, pour intégrer la troisième dimension, nous devons :

  • Envoyer les matrices projection et modelview au Vertex Shader grâce aux variables uniform

  • Les multiplier toutes les deux par le vertex en cours dans l'ordre : projection * modelview * vertex. Le résultat final doit être contenu dans la variable prédéfinie gl_Position

Pour gérer l'affichage de texture, nous devons :

  • Passer les coordonnées du tableau Vertex Attrib 2 depuis le Vertex Shader jusqu'au Fragment Shader (avec les mots-clés out et in)

  • Déclarer la texture en tant que variable uniformsampler2D dans le Fragment Shader

  • Appeler la fonction texture() pour trouver la couleur du pixel cherché. On affecte le résultat à la variable de sortie out_Color

Quand faut-il "changer" de shader ?

Avant de terminer, nous allons faire un dernier point sur le changement de shader. Vous vous êtes déjà peut-être demandé quand est-ce que vous devez changer de shader pour afficher un modèle ? Faut-il que vous en reprogrammiez un ou pouvez-vous utiliser celui que vous avez déjà fait ?

Pour faire simple, vous devez changer de shader à chaque fois que :

  • Vous touchez aux tableaux Vertex Attribs

  • Vous voulez envoyer d'autres variables uniform

Nous avons vu dans ces deux derniers chapitres qu'à chaque fois que nous modifions nos tableaux Vertex Attrib (pour les vertices, les couleurs, etc.), nous devions programmer un autre shader. Nous avions créé de nouveaux fichiers pour chaque changement.

Évidemment, si vous envoyez une variable uniform vous devrez créer un autre shader pour le prendre en compte.

Si vous ne souhaitez pas utiliser d'effets avancés dans vos applications alors les exemples que nous avons eus l'occasion de voir seront ceux que vous utiliserez 90% du temps. Ce sont les shaders de base de la programmation GLSL. :)

Bonnes pratiques

Optimisation du Vertex Shader

Actuellement, nos shaders soufrent d'un énorme manque d'optimisation. En effet si vous avez remarqué, nous multiplions les matrices projection et modelview dans le Vertex Shader. Le problème de cette multiplication c'est qu'elle s'effectue autant de fois qu'il y a de vertex à traiter. Donc si avons par exemple 5000 vertices à gérer alors elle s'effectuera également 5000 fois !

En temps normal, je vous dirais que ça ne pose pas de problème, que la carte graphique est faite pour ça, etc. Mais là, nous pouvons quand même alléger les calculs en effectuant simplement la multiplication avant d'envoyer les matrices au shader. C'est une opération toute simple à faire car nous avons déjà surchargé l'opérateur *.

D'ailleurs, c'est de cette façon que ça se passait dans les anciennes versions d'OpenGL. ;)

Au niveau du code C++, il nous suffit juste de déclarer une nouvelle matrice que nous appellerons modelviewProjection (en référence à gl_ModelviewProjectionMatrix d'OpenGL 2.1) au moment de l'envoyer au shader qui contiendra le résultat de la multiplication de projection et modelview. Ensuite, nous n'aurons plus qu'à l'envoyer à la place d'envoyer les deux matrices séparément

// Verrouillage du VAO
glBindVertexArrays(m_vao);
// Multiplication des matrices
mat4 modelviewProjection = projection * modelview;
// Envoi du résultat
glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "modelviewProjection"), 1, GL_FALSE, value_ptr(modelviewProjection));
// Rendu
....
// Déverrouillage du VAO
glBindVertexArray(0);

Au niveau du Vertex Shader, nous devons supprimer les deux anciens uniform pour les remplacer par un nouveau du nom de modelviewProjection :

// Uniform
uniform mat4 modelviewProjection;

Il ne manque plus qu'à prendre en compte la nouvelle matrice dans le calcul de la position du vertex :

void main()
{
// Position finale du vertex en 3D
gl_Position = modelviewProjection * vec4(in_Vertex, 1.0);
// Envoi des coordonnées de texture
coordTexture = in_TexCoord0;
}

Notre Vertex Shader ne perd maintenant plus de temps à effectuer une multiplication à chaque fois qu'il doit traiter un vertex. :)

Il n'y avait que deux lignes à changer au final. Voici à quoi ressemblerait le code permettant d'afficher une texture avec ces petits changements :

// Version du GLSL
#version 150 core
// Entrées
in vec3 in_Vertex;
in vec2 in_TexCoord0;
// Sortie
out vec2 coordTexture;
// Uniform
uniform mat4 modelviewProjection;
// Fonction main
void main()
{
// Position finale du vertex en 3D
gl_Position = modelviewProjection * vec4(in_Vertex, 1.0);
// Envoi des coordonnées de texture
coordTexture = in_TexCoord0;
}

Bien entendu, il peut arriver qu'on ait besoin de traiter les deux matrices séparément dans le shader. Si cela arrive, alors on les enverra toutes les deux séparément au lieu, ou en plus, d'envoyer la multiplication seule.

La méthode envoyerMat4()

Nous terminons ce chapitre par une méthode simple qui va nous permettre d'économiser l'écriture d'une longue ligne de code répétitive. Je ne sais pas si ça vous fait le même effet mais personnellement, l'appel à la fonction glUniformMatrix4fv() m'est extrêmement pénible du fait de sa longueur :

// Envoi de la matrice modelviewProjection
glUniformMatrix4fv(glGetUniformLocation(m_shader.getProgramID(), "modelviewProjection"), 1, GL_FALSE, value_ptr(modelviewProjection));

Étant donné que nous sommes obligés de faire ceci à chaque fois que l'on veut afficher un modèle, il serait judicieux de coder une méthode qui nous permettrait d'enfermer cette fonction sans avoir à la réutiliser tout le temps. Je pense que vous serez d'accord sur le principe. :p

Nous allons donc créer une méthode, dans la classe Shader, qui nous permettra de faire cette économie. Nous l'appellerons envoyerMat4() car elle n'enverra que des matrices carrées d'ordre 4. Elle prendra en paramètre le nom de la matrice dans le code source GLSL ainsi qu'une référence sur un objet de type mat4 :

void envoyerMat4(std::string nom, glm::mat4 matrice);

Dans un premier temps, nous devons localiser la variable de destination grâce à la fonction glGetUniformLocation() :

void Shader::envoyerMat4(std::string nom, glm::mat4 matrice)
{
// Localisation de la matrice
int localisation = glGetUniformLocation(m_programID, nom.c_str());
}

Ensuite, nous appelons la fameuse fonction glUniformMatrix4fv() pour envoyer les valeurs de la matrice :

void Shader::envoyerMat4(std::string nom, glm::mat4 matrice)
{
// Localisation de la matrice
int localisation = glGetUniformLocation(m_programID, nom.c_str());
// Envoi des valeurs
glUniformMatrix4fv(localisation, 1, GL_FALSE, value_ptr(matrice));
}

Méthode terminée. :)

Elle va nous faire gagner un peu temps à chaque fois que nous devrons afficher un modèle. D'ailleurs, le code source deviendra un peu plus compréhensible également :

// Mutltiplication des matrices
mat4 modelviewProjection = projection * modelview;
// Envoi du résultat
m_shader.envoyerMat4("modelviewProjection", modelviewProjection);

Nous pouvons même inclure la multiplication directement dans l'appel à la méthode envoyerMat4(). Ce qui fait que l'envoi d'une matrice se résumera maintenant à une seule et unique ligne :

// Envoi d'une matrice au shader
m_shader.envoyerMat4("modelviewProjection", projection * modelview);

A partir de maintenant, on ne reverra plus les lignes d'envoi de matrice. :D

L'avantage de cette méthode en plus, c'est qu'elle fonctionne aussi pour les envois séparés de projection et de modelview. En effet, elle ne fait qu'appeler la fonction glUniformMatrix4fv(), ce qui la rend parfaitement compatible avec les autres matrices carrées d'ordre 4.

Télécharger (Windows, UNIX/Linux, Mac OS X) : Code Source C++ du chapitre sur les shaders (Partie 2/2)

Nous sommes enfin arrivés à la fin de cette série de chapitres consacrés aux shaders. Nous avons vu pas mal de notions relatives à leur programmation de la compilation à l'aide d'une classe dédiée jusqu'à l'envoi de variable depuis l'application principale.

J'ai préféré vous occulter toute cette partie au début du tuto car la plupart d'entre vous aurait arrêté sa lecture dès que je vous aurais parlé de ça. C'était beaucoup trop indigeste à mon gout pour être balancé ni vu ni connu en même temps que la découverte d'OpenGL. Un chapitre introductif sur les shaders me semblait plus approprié.

Néanmoins, toutes les zones d'ombre sont maintenant levées et vous êtes seuls maitres de vos applications. :D

En ce qui concerne le GLSL, nous avons vu les principales notions à connaitre pour faire une application, à savoir la gestion des couleurs, de la 3D et des textures. Si vous souhaitez réaliser une solution pour entreprise, vous n'avez besoin que de ça. En revanche, si vous souhaitez aller plus loin dans le développement, je serai heureux de vous retrouver dans la quatrième partie de ce tuto pour étudier les effets réalistes.

Mais avant cela, nous devons terminer la partie en cours avec encore un chapitre consacré aux Frame Buffer Objects. Nous en aurons besoin dans la quatrième partie alors ne faites pas l'impasse dessus. :p


Les Frame Buffer Objects

Nous voici arrivés au dernier chapitre de cette deuxième partie. Celui-ci concernera une fonctionnalité très utile pour les effets réalises et constitue donc un incontournable de la programmation avancée OpenGL. Il y aura un peu de technique mais rien de vraiment insurmontable, nous verrons tout ce qu'il y a à voir pas à pas pour ne pas être perdu. :)

Commençons d'ailleurs sans plus tarder ! :pirate:

Ce chapitre a eu une naissance assez folklorique, ne vous en faites pas s'il y a des petites erreurs, des images bizarres ou des fautes d’orthographe. Je préfère sortir ce long chapitre maintenant plutôt que d'attendre et lui faire prendre la poussière jusqu'à une validation totale.

Qu'est-ce qu'un FBO ?

Définitions

Les différents types de buffer

Avant de nous intéresser au sujet principal de ce chapitre, nous allons faire un petit retour en arrière sur une fonction que nous utilisons depuis pas mal de temps déjà : la fonction glClear() :

// Nettoyage de l'écran

glClear(GL_COLOR_BUFFER | GL_DEPTH_BUFFER);

Celle-ci permet de nettoyer les buffers (zones mémoires ou tampons si vous préférez) qu'ils lui sont donnés en paramètres. Par exemple, nous lui demandons ici de nettoyer le Color ainsi que le Depth Buffer, le premier correspondant simplement aux pixels affichés sur notre fenêtre et le second à la position des objets 3D les uns par rapport aux autres pour pouvoir les cacher si besoin.

Si je vous parle de ça, c'est parce qu'il existe encore un autre buffer dont je ne vous ai pas encore parlé, ce qui est un peu normal vu que c'est celui qui est le moins utilisé. :p Il s'agit du Stencil Buffer ou en français tampon du pochoir. Si vous vous posez la question de savoir ce qu'est un pochoir, sachez que vous en avez déjà probablement utilisé un en maternelle pour dessiner une forme complexe comme un animal par exemple. Je vous laisse regarder sur internet des exemples de pochoirs, vous devriez vite comprendre à quoi ils correspondent en les voyant.

Le Stencil Buffer permet de faire exactement la même chose. Par exemple, au lieu d'afficher une texture carrée représentant un buisson, vous pouvez lui appliquer un pochoir (donc utiliser le Stencil Buffer) pour n'afficher que les pixels qui vous intéressent de façon à ne pas vous retrouver avec un buisson carré mais avec un buisson "réaliste". Je schématise un peu mais vous aurez compris le principe.

Le problème avec ce buffer, c'est qu'il est assez lourd à mettre en place, il faut activer un tableau Vertex Attrib dédié et envoyer ses donnés au shader. Nous ne l'utiliserons pas pour le moment. Si je vous en parle c'est parce que nous devrons gérer le cas où nous en aurions besoin plus tard, vous verrez pourquoi.

Ce qu'il faut retenir c'est qu'il existe 3 types de buffers :

  • Color : qui contient la couleur de chaque pixel de votre fenêtre

  • Depth : qui permet à OpenGL de gérer la profondeur et de cacher les objets

  • Stencil : qui permet de filtrer un rendu avec des pochoirs

Les Frame Buffers

Cette parenthèse étant faite, nous allons pouvoir passer au sujet principal qui concerne les Frame Buffer Objets ou FBO. Vous l'aurez deviné, nous allons encore voir des objets OpenGL. :p

Pour donner une définition simple d'un FBO, on pourrait dire qu'il s'agit d'un écran caché qui utilise les 3 types de buffers que nous avons vus à l'instant :

Si j'utilise le terme d'écran caché c'est par les FBO permettent de faire ce que l'on appelle un rendu off-screen (en dehors de l'écran). C'est-à-dire qu'au lieu d'afficher votre scène dans votre fenêtre, vous le ferrez dans un endroit à part dans la carte graphique comme s'il y en avait une deuxième à l'intérieure. Le rendu sera exactement le même sauf que vous ne le verrez pas directement.

À quoi ça sert d'afficher quelque chose si on ne le voit pas ?

C'est quelque chose d'un peu tordu au premier abord mais je pense que vous allez vite aimer les FBO quand vous connaitrez les possibilités qu'ils offrent. En étant associés aux shaders, ils permettent de faire plein d'effets réalistes tels que :

  • Le flou : pour la vitesse ou l'éblouissement

  • Les reflets : pour les miroirs et l'eau

  • Les ombres dynamiques : qui sont modifiées à chaque fois que leur "modèle" bouge

  • La capture vidéo : pour filmer un endroit tout en étant dans un autre (caméra de surveillance, télévision, etc.)

  • ...

Une bonne palette d'effets sympathiques en somme.

Petit détail pour terminer : votre écran lui-même est considéré comme un Frame Buffer. Il reprend donc toutes les caractéristiques dont nous venons de parler, sauf pour les effets réalistes parce qu'il est impossible de modifier les pixels une fois qu'ils sont affichés sur l'écran.

Fonctionnement

Nous allons aller un peu plus loin dans la structure des FBO car ils ont une façon bien particulière de fonctionner, en particulier au niveau de ses 3 buffers qui ne se gèrent pas tous de la même manière. On distinguera d'un coté le Color Buffer et de l'autre le Depth et le Stencil Buffer.

Le Color Buffer

Comme nous l'avons vu un peu plus haut, un FBO peut être considéré comme un écran caché au sein de la carte graphique, nous pouvons donc sans aucun problème faire n'importe quel rendu à l'intérieur. L'intérêt dans tout ça vient du fait que l'on peut modifier les pixels une fois qu'ils sont affichés, ce qui est impossible avec le véritable écran.

Ces fameux pixels sont contenus dans le Color Buffer, celui que nous nettoyons à chaque tour de boucle avec la constante GL_COLOR_BUFFER. Et si je peux vous apprendre quelque chose d'étonnant avec lui, c'est qu'il s'agit en fait d'une simple texture !

Oui, oui vous avez bien lu, le Color Buffer est une texture :

C'est un peu logique puisque le but d'OpenGL est d'afficher des pixels sur un écran, et une texture est justement faite pour en contenir plein. L'avantage avec elle, c'est que nous pourrons l'utiliser dans un shader pour la modifier très facilement. Rajouter un effet sera donc un jeu d'enfant. ;)

Ah j'ai une question : le Color Buffer de l'écran est lui-aussi une texture ?

Oui on peut dire ça comme ça. C'es comme si on avait une grosse texture, contenant l'affichage de notre scène, plaquée sur la fenêtre SDL.

Petite précision importante, les FBO peuvent contenir jusqu'à 16 Color Buffers. C'est-à-dire que vous pouvez afficher ou modifier votre rendu 16 fois dans le même FBO :

Ceci est particulièrement utile si l'on souhaite stocker des données autres que celles relatives à l'affichage. Les ombres en sont un exemple car elles utilisent plusieurs Color Buffers pour stocker leurs données.

Mais bon, pour le moment nous n'en utiliserons qu'un seul ne vous en faites pas, c'est déjà bien assez.

Le Depth et le Stencil Buffer

Les deux autres buffers (Depth et Stentil) se gèrent différemment du premier. Leurs données sont un peu plus complexes et ne peuvent pas être représentées par des textures. À la place, nous utiliserons ce que l'on appel des Render Buffers, ce sont des espaces mémoires conçues pour accueillir ces types de données :

En résumé

Pour résumer un peu tout ce flot d'informations : un Frame Buffer est composé de 3 sous-buffers (Color, Depth et Stencil) :

  • Le premier est représenté pas une texture et contient l'affichage d'une scène. Il peut y en avoir jusqu'à 16.

  • Les deux autres sont représentés par des Render Buffer.

Ce sont les points à connaitre par coeur en ce qui concerne les FBO.

Programme du chapitre

Après ce petit bout de théorie, nous allons pouvoir nous lancer dans la pratique pure et dure.

Je vous retiens encore un peu ici pour vous montrer le programme pour la suite. Nous allons éparpiller le code relatif aux FBO dans plusieurs classes, je préfère donc vous exposer clairement les différentes tâches qui nous attendent.

Celles-ci sont :

  • La modification la classe Texture pour lui permettre de créer des Color Buffers

  • La modification des méthodes de copie pour gérer ce nouveau type de texture

  • L'implémentation des Render Buffers

  • La création une classe Frame Buffer qui réunira tous les buffers

Nous avons donc pas mal de trucs à faire avec tout ceci.

Je vous invite naturellement à commencer par la première tâche qui concerne la modification de la classe Texture.

Le Color Buffer

Les tâches

Pour reprendre très rapidement le programme précédent, notre objectif dans cette partie va être la réalisation des deux premières tâches :

  • La modification la classe Texture pour lui permettre de créer des Color Buffers

  • La modification des méthodes de copie pour gérer ce nouveau type de texture

  • L'implémentation des Render Buffers

  • La création une classe Frame Buffer qui réunira tous les buffers

Je parle bien des deux premières car nous modifierons la classe Texture pour toutes les deux, autant les faire dans la même partie. :)

Commençons par la première.

Créer un Color Buffer

Le header

La fameuse modification de la classe Texture concerne l'ajout d'une nouvelle méthode qui permettra de créer des textures vides. Je dis bien vide parce qu'elle n'est pas chargée depuis une image sur le disque dur, elle ne contient donc aucune donnée au moment de sa création. Ce n'est qu'à l'affichage qu'elle aura ses pixels.

Avant de créer ladite méthode, nous allons tout d'abord ajouter de nouveaux attributs à la classe Texture qui lui permettront de gérer les textures vides.

Dans les chapitres précédents, nous utilisions une structure de type SDL_image pour garder en mémoire pas mal d'informations sur une texture comme ses dimensions, le format interne des couleurs, etc. Vu que nous n'en utilisons pas ici, il faudra recréer toutes ces informations à la main à travers des attributs de classe.

Ces attributs sont les suivants :

  • int largeur : La largeur de la texture

  • int hauteur : Sa hauteur

  • GLenum format : Le format des couleurs (3 ou 4 couleurs en comptant le canal Alpha)

  • GLenum formatInerne : Le format interne (l'ordre des couleurs)

  • bool textureVide : Ce booléen sera utilisé dans les méthodes de copie

En ajoutant le prefix m_ à ces attributs, on a le header suivant :

#ifndef DEF_TEXTURE

#define DEF_TEXTURE

// Includes

....

// Classe Texture

class Texture

{

    public:

    // Méthodes

    ....

    private:

    GLuint m_id;

    std::string m_fichierImage;

    int m_largeur;

    int m_hauteur;

    GLenum m_format;

    GLenum m_formatInterne;

    bool m_textureVide;

};

#endif
Les anciens constructeurs

Les nouveaux attributs doivent évidemment être initialisés dans les différents constructeurs, nous leur donnerons tous la valeur 0 :

// Constructeur par défaut

Texture::Texture() : m_id(0), m_fichierImage(""), m_largeur(0), m_hauteur(0), m_format(0), m_formatInterne(0), m_textureVide(false)

{

}

// Autre constructeur

Texture::Texture(std::string fichierImage) : m_id(0), m_fichierImage(fichierImage), m_largeur(0), m_hauteur(0), m_format(0), m_formatInterne(0), m_textureVide(false)

{

}

Notez que j'ai volontairement occulté le constructeur de copie, nous le verrons à la fin.

Un nouveau constructeur

Le problème avec les constructeurs précédents c'est qu'aucun d'entre eux n'est capable de donner de "vraies" valeurs aux attributs, ce qui est normal car ils n'ont aucuns paramètres qui leur permettent de faire cela.

Pour combler ce manque, nous allons devoir rajouter un nouveau constructeur qui, lui, prendra en paramètres tout ce dont on a besoin pour initialiser nos attributs correctement :

Texture(int largeur, int hauteur, GLenum format, GLenum formatInterne, bool textureVide);

L'implémentation de ce constructeur est assez simple puisqu'il suffit juste de donner le bon paramètre au bon attribut :

Texture::Texture(int largeur, int hauteur, GLenum format, GLenum formatInterne, bool textureVide) : m_id(0), m_fichierImage(""), m_largeur(largeur),

                 m_hauteur(hauteur), m_format(format), m_formatInterne(formatInterne), m_textureVide(textureVide)

{

}

Grâce à lui, nous pourrons donner de véritables valeurs à nos nouveaux attributs.

La méthode chargerTextureVide()

L'étape suivante consiste à coder la méthode qui nous permettra de créer des textures vides. Elle utilisera pour cela les attributs précédemment initialisés, elle s'appellera tout simplement chargerTextureVide() :

void chargerTextureVide();

Elle ne prendra aucun paramètre.

Son code sera beaucoup plus simple que celui de la méthode charger() car elle n'aura pas besoin de charger une image, d'inverser ses pixels et de déterminer le format des couleurs. Nous avons déjà toutes ces informations grâce à notre nouveau constructeur. :)

On commence l'implémentation de la méthode en générant un nouvel identifiant d'objet OpenGL grâce à la fonction glGenTextures(). On en profitera au passage pour ajouter le code de vérification d'ID à l'aide la fonction glIsTexture() :

void Texture::chargerTextureVide()

{

    // Destruction d'une éventuelle ancienne texture

    if(glIsTexture(m_id) == GL_TRUE)

        glDeleteTextures(1, &m_id);

    // Génération de l'ID

    glGenTextures(1, &m_id);

}

On ajoute ensuite le verrouillage a l'aide de la fonction glBindTexture() :

void Texture::chargerTextureVide()

{

    // Destruction d'une éventuelle ancienne texture

    if(glIsTexture(m_id) == GL_TRUE)

        glDeleteTextures(1, &m_id);

    // Génération de l'ID

    glGenTextures(1, &m_id);

    // Verrouillage

    glBindTexture(GL_TEXTURE_2D, m_id);

    // Déverrouillage

    glBindTexture(GL_TEXTURE_2D, 0);

}

Une fois le verrouillage enclenché, nous allons appeler la fonction glTexImage2D() qui permet de définir les caractéristiques d'une texture (dimensions, formats, etc.). Je vous redonne son prototype ainsi que sa floppé de paramètres :

void glTexImage2D(GLenum target,  GLint level,  GLint internalFormat,  GLsizei width,  GLsizei height,  GLint border,  GLenum format,  GLenum type,  const GLvoid * data);

  • target : Le type de la texture auquel nous donnerons, comme toujours, la constante GL_TEXTURE_2D

  • level : Paramètre que nous n'utiliserons pas, nous lui donnerons la valeur 0

  • internalFormat : Le format interne de la texture

  • width : Sa largeur

  • height : Sa hauteur

  • border : Une bordure, nous ne l'utiliserons pas et donnerons la valeur 0

  • format : Le format de la texture

  • type : Type de donnée des pixels (float, int, ...). Nous lui donnerons la constante GL_UNSIGNED_BYTE

  • data : Pixels de la texture, nous n'en avons pas encore et nous donnerons encore la valeur 0

Refaite une petite lecture lentement pour différencier correctement les paramètres. Remarquez aussi que nous utiliserons tous nos nouveaux attributs.

En appelant la fonction glTexImage2D() avec ces paramètres, on a :

// Définition des caractéristiques de la texture

glTexImage2D(GL_TEXTURE_2D, 0, m_formatInterne, m_largeur, m_hauteur, 0, m_format, GL_UNSIGNED_BYTE, 0);

Pour terminer la méthode, il ne nous manque plus qu'à appliquer les filtres avec la fonction glTexParameteriv(). Vous vous souvenez de ce que sont les filtres ? Ils permettent à OpenGL de savoir s'il doit améliorer ou baisser la qualité de la texture en fonction de notre distance par rapport à elle.

On utilisera les mêmes filtres que ceux de la méthode charger() :

// Application des filtres

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

Ces filtres permettent à OpenGL d'affiner l'affichage des textures proches et de pixeliser celui des textures éloignées, le premier étant plus gourmand en calcul que le second.

Si nous réunissons ces bouts de code, on trouve la méthode chargerTextureVide() suivante :

void Texture::chargerTextureVide()

{

    // Destruction d'une éventuelle ancienne texture

    if(glIsTexture(m_id) == GL_TRUE)

        glDeleteTextures(1, &m_id);

    // Génération de l'ID

    glGenTextures(1, &m_id);

    // Verrouillage

    glBindTexture(GL_TEXTURE_2D, m_id);

        // Définition des caractéristiques de la texture

        glTexImage2D(GL_TEXTURE_2D, 0, m_formatInterne, m_largeur, m_hauteur, 0, m_format, GL_UNSIGNED_BYTE, 0);

        // Application des filtres

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    // Déverrouillage

    glBindTexture(GL_TEXTURE_2D, 0);

}

Grâce à elle, nous pourrons créer tous les Colors Buffers que nous voudrons. :D Il suffira juste de faire attention aux paramètres que nous donnerons.

Le problème de la copie de texture

Étant donné que nous avons rajouté des attributs dans la classe, nous devons penser à modifier les méthodes de copie (constructeur et opérateur =) pour éviter les problèmes plus tard. C'est la deuxième tâche sur notre liste.

Les attributs

On commence par le constructeur.

En reprenant son code actuel, on remarque que celui-ci ne gère pas les nouveaux attributs :

Texture::Texture(Texture const &textureACopier)

{

    // Copie de la texture

    m_fichierImage = textureACopier.m_fichierImage;

    charger();

}

Nous devons donc corriger cela en utilisant l'opérateur = pour chacun d'entre eux :

Texture::Texture(Texture const &textureACopier)

{

    // Copie des attributs

    m_fichierImage = textureACopier.m_fichierImage;

    m_largeur = textureACopier.m_largeur;

    m_hauteur = textureACopier.m_hauteur;

    m_format = textureACopier.m_format;

    m_formatInterne = textureACopier.m_formatInterne;

    m_textureVide = textureACopier.m_textureVide;

    // Chargement de la copie

    charger();

}
Le chargement

Le chargement d'une texture copiée est ce qu'il y a de plus délicat à gérer, surtout dans notre situation car nous avons deux méthodes de chargement : l'une pour les images présentes sur le disque dur et l'autre pour les textures vides.

Le problème avec la copie ici c'est que la classe Texture est incapable de savoir laquelle des deux elle doit appeler. Pour le moment, elle appelle toujours la méthode charger() quelque soit le type de texture (vide ou image), ce qui va nous poser des problèmes quand on utilisera les Color Buffer car eux ont besoin de l'autre méthode.

Pour éviter cela, nous devons définir manuellement les cas de chargement pour appeler la bonne méthode. Pour nous aider, nous allons nous servir du seul attribut que nous n'avons pas encore utilisé : le booléen m_textureVide. Nous lui avions donné une valeur dans tous les constructeurs un peu plus haut.

Si sa valeur est égale à true (c'est-à-dire si la texture est vide) alors nous devons appeler la méthode chargerTextureVide(). Si elle est égale à false (image SDL), alors nous devons appeler la méthode charger(). Un petit bloc if sera nécessaire pour faire cette vérification :

// Si la texture est vide, alors on appelle la méthode chargerTextureVide()

if(m_textureVide)

    chargerTextureVide();

// Sinon, on appelle la méthode charger() par défaut

else

    charger();

Le constructeur complet :

Texture::Texture(Texture const &textureACopier)

{

    // Copie des attributs

    m_fichierImage = textureACopier.m_fichierImage;

    m_largeur = textureACopier.m_largeur;

    m_hauteur = textureACopier.m_hauteur;

    m_format = textureACopier.m_format;

    m_formatInterne = textureACopier.m_formatInterne;

    m_textureVide = textureACopier.m_textureVide;

    // Si la texture est vide, alors on appelle la méthode chargerTextureVide()

    if(m_textureVide)

        chargerTextureVide();

    // Sinon, on appelle la méthode charger() par défaut

    else

        charger();

}

Cette fois, la classe appellera la bonne méthode pour charger la texture. :)

Problème de copie des textures non chargées

Courage, il ne manque plus qu'une condition à gérer et toute cette partie embêtante sera terminée.

Celle-ci concerne les textures qui n'ont pas encore été chargées au moment de la copie, c'est-à-dire celles dont nous n'avons appelé ni la méthode charger(), ni la méthode chargerTextureVide(). Que se passerait-il à votre avis si on essayait d'en copier une dans cette situation ? Et bien la copie serait automatiquement chargée même si l'originale ne l'était pas. Cet automatisme peut causer des problèmes et engendrer des bugs dans certains cas.

Pour éviter cela encore une fois, nous devons rajouter une condition qui vérifiera si la copie doit être chargée ou pas. Nous utiliserons la fonction glIsTexture() qui nous permet justement de savoir ceci :

Texture::Texture(Texture const &textureACopier)

{

    // Copie des attributs

    m_fichierImage = textureACopier.m_fichierImage;

    m_largeur = textureACopier.m_largeur;

    m_hauteur = textureACopier.m_hauteur;

    m_format = textureACopier.m_format;

    m_formatInterne = textureACopier.m_formatInterne;

    m_textureVide = textureACopier.m_textureVide;

    // Si la texture est vide, alors on appelle la méthode chargerTextureVide()

    if(m_textureVide && glIsTexture(textureACopier.m_id) == GL_TRUE)

        chargerTextureVide();

    // Sinon, on appelle la méthode charger() par défaut

    else if(glIsTexture(textureACopier.m_id) == GL_TRUE)

        charger();

}

Désormais, pour déclencher un des deux chargements, il faudra que la texture originale ait un identifiant valide.

L'opérateur =

Il ne reste plus qu'une seule chose a faire : recopier le code précédent dans la méthode operator=(), en n'oubliant pas de rajouter le return *this bien sûr :

Texture& Texture::operator=(Texture const &textureACopier)

{

    // Copie des attributs

    m_fichierImage = textureACopier.m_fichierImage;

    m_largeur = textureACopier.m_largeur;

    m_hauteur = textureACopier.m_hauteur;

    m_format = textureACopier.m_format;

    m_formatInterne = textureACopier.m_formatInterne;

    m_textureVide = textureACopier.m_textureVide;

    // Si la texture est vide, alors on appelle la méthode chargerTextureVide()

    if(m_textureVide && glIsTexture(textureACopier.m_id) == GL_TRUE)

        chargerTextureVide();

    // Sinon, on appelle la méthode charger() par défaut

    else if(glIsTexture(textureACopier.m_id) == GL_TRUE)

        charger();

    // Retour du pointeur *this

    return *this;

}

Les modifications dans la classe Texture sont terminées. :)

Les Render Buffers

Les tâches

Reprenons la petite liste de tâches que nous avons à faire pour créer un FBO. Dans cette partie, nous allons nous occuper des deux dernières :

  • La modification la classe Texture pour lui permettre de créer des Color Buffers

  • La modification des méthodes de copie pour gérer ce nouveau type de texture

  • L'implémentation des Render Buffers

  • La création une classe Frame Buffer qui réunira tous les buffers

Ces deux tâches sont liées entre elles car les Render Buffers sont créés à l'intérieur de la classe FrameBuffer. Nous commencerons par parler d'elle et de ses attributs avant d'attaquer les Render Buffers.

La classe FrameBuffer

Le header

La classe FrameBuffer constitue évidemment le coeur des FBO, elle devra gérer la création et l'utilisation de tous les buffers. Elle appellera par la méthode chargerTextureVide() que nous avons codée dans la partie précédente. ;)

Son header de base est celui-ci :

#ifndef DEF_FRAMEBUFFER

#define DEF_FRAMEBUFFER

// Include Windows

#ifdef WIN32

#include <GL/glew.h>

// Include Mac

#elif __APPLE__

#define GL3_PROTOTYPES 1

#include <OpenGL/gl3.h>

// Include UNIX/Linux

#else

#define GL3_PROTOTYPES 1

#include <GL3/gl3.h>

#endif

// Classe FrameBuffer

class FrameBuffer

{

    public:

    private:

};

#endif

Cette classe possédera pas mal d'attributs pour fonctionner correctement :

  • GLuint m_id : identifiant OpenGL représentant le FBO

  • float m_largeur : largeur du FBO. On peut comparer cet attribut à la largeur de la fenêtre SDL

  • float m_hauteur : même chose pour la hauteur

  • vector m_couleursBuffers : tableau dynamique qui contiendra tous les Colors Buffers désirés

  • GLuint m_depthBufferID : identifiant du Depth Buffer

On ne crée par d'attribut pour le Stencil Buffer ?

Non nous n'en avons pas besoin, vous verrez pourquoi dans la partie suivante. :)

Cette petite flopée d’attributs permettra de gérer tous les aspects de nos futurs FBO. Nous en rajouterons quelqu'uns dans la dernière partie consacrée aux améliorations.

// Attributs

GLuint m_id;

int m_largeur;

int m_hauteur;

std::vector<Texture> m_colorBuffers;

GLuint m_depthBufferID;

Le header comportera aussi son constructeur par défaut :

FrameBuffer();

Ainsi qu'un second constructeur qui prendra en paramètres la largeur et la hauteur du FBO. Nous passerons leur valeur aux attributs du même nom :

FrameBuffer(int largeur, int hauteur);

Ce sera ce constructeur que l'on utilisera principalement. ;)

Enfin, nous ajoutons aussi le destructeur qui ne change décidément pas de forme :

~FrameBuffer();

Si on résume tout ça :

#ifndef DEF_FRAMEBUFFER

#define DEF_FRAMEBUFFER

// Include Windows

#ifdef WIN32

#include <GL/glew.h>

// Include Mac

#elif __APPLE__

#define GL3_PROTOTYPES 1

#include <OpenGL/gl3.h>

// Include UNIX/Linux

#else

#define GL3_PROTOTYPES 1

#include <GL3/gl3.h>

#endif

// Includes communs

#include <vector>

#include "Texture.h"

// Classe

class FrameBuffer

{

    public:

    FrameBuffer();

    FrameBuffer(int largeur, int hauteur);

    ~FrameBuffer();

    private:

    GLuint m_id;

    int m_largeur;

    int m_hauteur;

    std::vector<Texture> m_colorBuffers;

    GLuint m_depthBufferID;

};

#endif
Les constructeurs et le destructeur

Passons tout de suite à l'implémentation des quelques pseudo-méthodes que nous avons déclarés, nous en seront débarrassés. :)

Le premier constructeur est assez simple (comme d'habitude) puisqu'il ne fait que mettre des valeurs nulles aux attributs. Nous leur donnerons tous la valeur 0 :

FrameBuffer::FrameBuffer() : m_id(0), m_largeur(0), m_hauteur(0), m_colorBuffers(0), m_depthBufferID(0)

{

}

Le second quant à lui sera un poil différent puisqu'il prendra 2 paramètres qui correspondent aux dimensions du FBO. Les autres attributs seront initialisés avec la valeur 0 :

FrameBuffer::FrameBuffer(int largeur, int hauteur) : m_id(0), m_largeur(largeur), m_hauteur(hauteur),

                                                     m_colorBuffers(0), m_depthBufferID(0)

{

}

Pour ce qui est du destructeur, celui-ci ne va pas détruire grand chose pour le moment. Nous le remplirons une fois que nous aurons terminé l'implémentation de toutes les méthodes.

FrameBuffer::~FrameBuffer()

{

}

La méthode creerRenderBuffer()

Explications

Maintenant que nous avons un squelette de classe défini, nous allons pouvoir passer à la troisième tâche que nous attend sur la liste : la création des Render Buffers.

Comme je vous l'ai dit tout à l'heure, les Render Buffers ne seront pas codés dans une classe dédiée, ils ne sont pas aussi importants que les textures donc ils n'ont pas besoin d'être séparés du reste. Nous les utiliserons pour gérer le Depth et le Stencil Buffer qui, contrairement au Color, ne sont pas représentables par des textures (souvenez-vous du petit schéma dans l'introduction).

Pour les créer, nous allons implémenter une méthode qui se chargera de générer et de configurer un Render Buffer à partir d'un identifiant OpenGL. De cette façon, nous initialiserons un buffer très simplement en utilisant seulement une lignes de code. :)

La méthode en question s'appellera creerRenderBuffer() et prendra 2 paramètres :

void creerRenderBuffer(GLuint &id, GLenum formatInterne);

  • id : L'identifiant qui représentera le Render Buffer

  • formatInterne : Format interne du buffer. C'est un paramètre que l'on voit plus souvent avec les textures, nous verrons pourquoi nous l'utilisons ici aussi

Génération de l'identifiant

L'avantage des Render Buffers c'est que ce sont des objets OpenGL, ils se gèrent donc de la même façon que les VBO, les VAO et les textures. C'est-à-dire qu'ils ont besoin d'un identifiant et d'un verrouillage pour leur configuration (et à fortiori pour leur utilisation). Nous retrouverons ainsi, une fois de plus, toutes les fonctions du type glGenXXX(), glBindXXX() etc.

Je vais passer rapidement sur ces deux notions que nous avons l'habitude de voir maintenant. La première est la génération d'identifiant, elle se fait ici avec la fonction glGenRenderbuffers() :

void glGenRenderbuffers(GLsizei number, GLuint *renderbuffers);

  • number : Le nombre d'ID à initialiser. Nous lui donnerons toujours la valeur 1

  • renderbuffers : Un tableau de type GLuint ou l'adresse d'une variable de même type représentant le ou les Render Buffer(s)

Nous appellerons cette fonction en donnant en paramètre la valeur 1 ainsi que l'adresse de la variable id (qui est elle-même un paramètre de la méthode creerRenderBuffer()) :

void FrameBuffer::creerRenderBuffer(GLuint &id, GLenum formatInterne)

{

    // Génération de l'identifiant

    glGenRenderbuffers(1, &id);

}

On profite de cette génération pour inclure la vérification d'un "précédent chargement" qui s'applique à tous les objets OpenGL, et donc aux Render Buffers, afin d'éviter d'éventuelles fuites de mémoire. Nous utiliserons pour cela la fonction glIsRenderbuffer() :

GLboolean glIsRenderbuffer(GLuint renderbuffer);

La fonction renvoie la valeur GL_FALSE si aucun chargement n'a été effectué sur l'objet donné ou GL_TRUE si c'est le cas.

Si nous tombons sur la première valeur, nous n'aurons rien à faire. En revanche, si la fonction nous renvoie GL_TRUE il faudra faire attention à détruire le Render Buffer avant de le charger à nouveau. La fonction permettant cette destruction s'appelle glDeleteRenderbuffers() :

void glDeleteRenderbuffers(GLsizei number, GLuint *renderbuffers);

Elle prend exactement les mêmes paramètres que glGenRenderbuffers(), à savoir :

  • number : Le nombre d'ID à détruire

  • renderbuffers : Un tableau de type GLuint ou l'adresse d'une variable de même type contenant le ou les objet(s) à détruire

Comme à l'accoutumé, nous appellerons les deux fonctions que nous venons de voir juste avant la génération d'ID :

void FrameBuffer::creerRenderBuffer(GLuint &id, GLenum formatInterne)

{

    // Destruction d'un éventuel ancien Render Buffer

    if(glIsRenderbuffer(id) == GL_TRUE)

        glDeleteRenderbuffers(1, &id);

    // Génération de l'identifiant

    glGenRenderbuffers(1, &id);

}
Configuration

Maintenant que nous avons un Render Buffer généré, il ne nous reste plus qu'à le configurer. Cette étape commence évidemment par le "verrouillage" de façon à ce qu'OpenGL sache sur quel objet il doit travailler. Nous utiliserons pour cela la fonction glBindRenderbuffer() :

void glBindRenderbuffer(GLenum target, GLuint renderbuffer);

  • target : Le fameux paramètre target :p qui correspond toujours au type de l'objet que l'on veut verrouiller. Dans notre cas, nous lui donnerons la constante GL_RENDERBUFFER

  • renderbuffer : Identifiant représentant le Render Buffer

Nous appellerons cette fonction deux fois : une fois pour le verrouillage et une autre pour le déverrouillage. Le premier appel prendra en paramètre l'identifiant à verrouiller tandis que le second prendra la valeur 0 :

void FrameBuffer::creerRenderBuffer(GLuint &id, GLenum formatInterne)

{

    // Destruction d'un éventuel ancien Render Buffer

    if(glIsRenderbuffer(id) == GL_TRUE)

        glDeleteRenderbuffers(1, &id);

    // Génération de l'identifiant

    glGenRenderbuffers(1, &id);

    // Verrouillage

    glBindRenderbuffer(GL_RENDERBUFFER, id);

    // Déverrouillage

    glBindRenderbuffer(GL_RENDERBUFFER, 0);

}

Le Render Buffer est à présent verrouillé et prêt à être configuré. Cette opération va d'ailleurs être beaucoup plus simple que celle des autres objets OpenGL car nous n'avons besoin ici que d'une seule et unique fonction.

La fonction en question peut d'ailleurs nous faire penser à glTexImage2D() car elle permet de définir le format et les dimensions du Render Buffer. Elle possède cependant moins de paramètres qu'elle, ce qui la rend tout de même plus agréable à utiliser. :p

Cette fameuse fonction s'appelle glRenderbufferStorage() :

void glRenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height);

  • target : Type de l'objet que l'on veut verrouiller, donc GL_RENDERBUFFER ici

  • internalformat : Format interne du buffer, nous allons faire un petit aparté dessus dans un instant

  • width : largeur du buffer

  • height : hauteur du buffer

Vous vous souvenez du paramètre formatInterne dont je vous parlé tout à l'heure ? Celui de la méthode creerRenderBuffer() ? Et bien c'est ici que nous allons l'utiliser.

Ce dernier permet de définir le "type" de donnée à stocker dans le buffer. Par exemple, les données relatives à la profondeur (Depth) ne sont pas du tout les mêmes que celles relatives aux pochoirs (Stencil), et pourtant ce sont tous les deux des Render Buffers. Pour marquer cette différence, il faut indiquer dès maintenant le type de donnée qu’accueillera le buffer.

Pour cela, nous allons donner le paramètre formatInterne à la fonction glRenderbufferStorage(). Nous lui donnerons également les dimensions du FBO à l'aide des attributs m_largeur et m_hauteur :

// Configuration du Render Buffer

glRenderbufferStorage(GL_RENDERBUFFER, formatInterne, m_largeur, m_hauteur);

Avec cet appel, nous demandons à créer un Render Buffer du type donné par le paramètre formatInterne (nous verrons ses valeurs possibles dans la partie qui suit) le tout avec les dimensions de notre FBO initial.

Si on ajoute ceci à notre méthode, on obtient :

void FrameBuffer::creerRenderBuffer(GLuint &id, GLenum formatInterne)

{

    // Destruction d'un éventuel ancien Render Buffer

    if(glIsRenderbuffer(id) == GL_TRUE)

        glDeleteRenderbuffers(1, &id);

    // Génération de l'identifiant

    glGenRenderbuffers(1, &id);

    // Verrouillage

    glBindRenderbuffer(GL_RENDERBUFFER, id);

        // Configuration du Render Buffer

        glRenderbufferStorage(GL_RENDERBUFFER, formatInterne, m_largeur, m_hauteur);

    // Déverrouillage

    glBindRenderbuffer(GL_RENDERBUFFER, 0);

}

La méthode creerRenderBuffer() est maintenant terminée, nous pouvons l'utiliser quand bon nous semble. C'est d'ailleurs ce que nous allons faire tout de suite en utilisant tout ce que l'on vient de faire (Color et Render Buffers) dans le but de créer enfin notre premier FBO. :D

Le Frame Buffer

La méthode charger()

Une impression de déjà-vu

Après avoir créé les deux types de buffers dans les parties précédentes, nous allons enfin pouvoir les associer au FBO. Si vous vous sentez un peu perdus entre tous ces buffers, vous pouvez faire une petite analogie avec les shaders : d'un coté vous avez les Color et Render Buffers (les Vertex et Fragment Shaders) et de l'autre le Frame Buffer qui va les utiliser (Le Programme). Je vous redonne le schéma du début pour vous resituer :

La comparaison avec les shaders ne s'arrête pas là d'ailleurs car nous retrouvons des notions similaires telles que l'association des buffers avec le FBO, le "attachment", ainsi que la vérification d'intégrité qui sera ici beaucoup moins longue à coder je vous rassure :p

Nous ferons tout ça dans une nouvelle méthode qui s'appellera tout simplement charger() :

bool charger();

Elle retournera un booléen pour confirmer ou non la réussite du chargement. Pensez à inclure ce prototype dans le header de la classe FrameBuffer.

La génération d'identifiant

Allez, on se met dans le bain directement et on commence à coder notre nouvelle méthode dès maintenant. Le début sera très similaire à ce que nous avons fait jusqu'à présent car les Frame Buffers sont eux-aussi des objets OpenGL (et oui encore :p ). On retrouvera donc une fois de plus la génération d'identifiant, la vérification de double chargement et le verrouillage.

La première de ces opérations sera assurée par la fonction glGenFrambuffers() :

void glGenFramebuffers(GLsizei number,  GLuint *ids);

  • number : Le nombre d'ID à initialiser. Nous lui donnerons la valeur 1

  • ids : Un tableau de type GLuint ou l'adresse d'une variable de même type représentant le ou les FBO

Le verrouillage quant à lui, sera effectué par la fonction glBindFramebuffer() :

void glBindFramebuffer(GLenum target, GLuint framebuffer);

  • target : Type d'objet à verrouiller, ici nous lui donnerons toujours la valeur GL_FRAME_BUFFER

  • framebuffer : Identifiant représentant le FBO

Nous utiliserons ces deux fonctions au début de la méthode charger(). L'identifiant à donner sera évidemment l'attribut m_id de notre classe FrameBuffer :

bool FrameBuffer::charger()

{

    // Génération d'un id

    glGenFramebuffers(1, &m_id);

    // Verrouillage du Frame Buffer

    glBindFramebuffer(GL_FRAMEBUFFER, m_id);

    // Déverrouillage du Frame Buffer

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

}

Remarquez le dernier appel qui permet de déverrouiller le FBO.

Comme d'habitude, on en profite au passage pour inclure le code qui permet d'éviter le double chargement. Nous utiliserons pour cela les fonctions glIsFramebuffer() et glDeleteFramebuffers() :

GLboolean glIsFramebuffer(GLuint framebuffer);

void glDeleteFramebuffers(GLsizei number, GLuint *framebuffers);

  • La première renverra la valeur GL_FALSE si aucun chargement n'a été effectué ou GL_TRUE si c'est le cas.

  • La seconde prendra les mêmes paramètres que la fonction de génération d'ID.

Nous appellerons ces fonctions avec un bloc if de la même façon que d'habitude. Je vous conseille cependant de mettre des accolades à votre bloc car nous rajouterons une instruction dans très peu de temps. ;)

// Vérification d'un éventuel ancien FBO

if(glIsFramebuffer(m_id) == GL_TRUE)

{

    glDeleteFramebuffers(1, &m_id);

}

Si on ajoute ceci au code que nous avions déjà :

bool FrameBuffer::charger()

{

    // Vérification d'un éventuel ancien FBO

    if(glIsFramebuffer(m_id) == GL_TRUE)

    {

        glDeleteFramebuffers(1, &m_id);

    }

    // Génération d'un id

    glGenFramebuffers(1, &m_id);

    // Verrouillage du Frame Buffer

    glBindFramebuffer(GL_FRAMEBUFFER, m_id);

    // Déverrouillage du Frame Buffer

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

}
Le Color Buffer

Comme dit un peu plus, la configuration du FBO va être d'une simplicité enfantine. Nous avons déjà tout codé dans les parties précédentes, nous n'avons juste qu'à appeler nos nouvelles méthodes pour créer nos différents buffers.

Le premier dont nous allons nous occuper est le Color Buffer. En théorie, nous devrions gérer le cas où nous en utiliserions 16 comme le permet OpenGL, cependant je ne veux pas vous alourdir encore plus les explications surtout avec tout ce que l'on a vu jusqu'à maintenant. Nous verrons donc cela dans la dernière partie de ce chapitre qui sera consacrée aux améliorations.

En attendant, nous allons gérer le cas où nous utilisons un seul et unique Color Buffer qui correspond, je vous le rappelle, à une texture. La première étape consistera à créer un nouvel objet de type Texture grâce au nouveau constructeur de cette classe.

Texture(int largeur, int hauteur, GLenum format, GLenum formatInterne, bool textureVide);

Celui-ci prendra en paramètres :

  • Les dimensions du FBO : parce qu'il faut bien que les buffers fassent la même taille que lui. Ces dimensions sont représentées par les attributs m_largeur et m_hauteur de notre classe

  • Le format : qui correspond au format des couleurs (3 ou 4 couleurs avec le canal Alpha). Nous lui affecterons la constante GL_RGBA

  • Le format interne des couleurs : qui correspond au interne de la texture (l'ordre des couleurs). Bizarrement, nous lui affecterons la constante GL_RGBA, celle-ci fonctionne pour les deux paramètres

  • Le booléen textureVide : qui sera utile en cas de copie de texture pour savoir quelle méthode appeler (charger() ou chargerTextureVide()). Il faut lui affecter la valeur true évidemment

Nous créons donc un objet Texture en utilisant ce constructeur :

// Création du Color Buffer

Texture colorBuffer(m_largeur, m_hauteur, GL_RGBA, GL_RGBA, true);

Ensuite, on appelle la méthode chargerTextureVide() de notre nouvel objet de façon à l'initialiser avec les bonnes dimensions et les bons formats :

// Création du Color Buffer

Texture colorBuffer(m_largeur, m_hauteur, GL_RGBA, GL_RGBA, true);

colorBuffer.chargerTextureVide();

Enfin, on l'ajoute au tableau dynamique m_colorBuffers. Ce dernier ne contiendra qu'une seule texture pour le moment mais n'oubliez pas que nous allons gérer les autre cas un peu plus tard :

// Création du Color Buffer

Texture colorBuffer(m_largeur, m_hauteur, GL_RGBA, GL_RGBA, true);

colorBuffer.chargerTextureVide();

// Ajout au tableau

m_colorBuffers.push_back(colorBuffer);

Petit récap :

bool FrameBuffer::charger()

{

    // Vérification d'un éventuel ancien FBO

    if(glIsFramebuffer(m_id) == GL_TRUE)

    {

        glDeleteFramebuffers(1, &m_id);

    }

    // Génération d'un id

    glGenFramebuffers(1, &m_id);

    // Verrouillage du Frame Buffer

    glBindFramebuffer(GL_FRAMEBUFFER, m_id);

        // Création du Color Buffer

        Texture colorBuffer(m_largeur, m_hauteur, GL_RGBA, GL_RGBA, true);

        colorBuffer.chargerTextureVide();

        // Ajout au tableau

        m_colorBuffers.push_back(colorBuffer);

    // Déverrouillage du Frame Buffer

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

}
Le Depth et le Stencil Buffer

Bien, on passe maintenant aux Render Buffers.

Dans la partie précédente, nous avons codé une méthode permettant d'en créer un très facilement. Son prototype est le suivant :

void creerRenderBuffer(GLuint &id, GLenum formatInterne);

  • id : référence sur l'identifiant représentant le buffer créé

  • formatInterne : Format interne du buffer

Fut un temps, nous aurions appelé cette méthode deux fois : une pour le Depth Buffer et l'autre pour le Stencil. Cependant, les choses ont changés et ce système ne semble plus fonctionner avec OpenGL 3. Au lieu d'avoir deux buffers séparés, nous n'en avons maintenant plus qu'un seul qui remplit le rôle du Depth et du Stencil.

Notre petit schéma n'est donc plus exact, il doit être modifié pour fusionner les deux Render Buffers :

Ce schéma est plus proche de la réalité. :)

En définitif, nous n'appellerons pas la méthode creerRenderBuffer() deux fois mais une seule fois. Nous lui donnerons en paramètre l'attribut m_depthBufferID qui accueillera le "double-buffer" ainsi que la constante GL_DEPTH24_STENCIL8 pour le format interne. Cette constante permet de :

  • Définir une profondeur de 24bits pour le Depth Buffer. Nous avons utilisé le même nombre au moment de configurer le contexte OpenGL avec la fonction SDL_GL_SetAttribute()

  • Même chose pour le Stencil Buffer mais avec seulement 8 bits cette fois-ci. Ce buffer prend moins de place en mémoire

L'appel final ressemble donc à ceci :

// Création du Depth et du Stencil Buffer

creerRenderBuffer(m_depthBufferID, GL_DEPTH24_STENCIL8);

Attention, nous utilisons bien une référence pour le premier paramètre, ce n'est donc pas une copie qui est envoyée à la méthode.

Quelle simplicité, nous venons de créer un double-buffer en seulement une lignes de code. :p

Petite récap :

bool FrameBuffer::charger()

{

    // Vérification d'un éventuel ancien FBO

    if(glIsFramebuffer(m_id) == GL_TRUE)

    {

        glDeleteFramebuffers(1, &m_id);

    }

    // Génération d'un id

    glGenFramebuffers(1, &m_id);

    // Verrouillage du Frame Buffer

    glBindFramebuffer(GL_FRAMEBUFFER, m_id);

        // Création du Color Buffer

        Texture colorBuffer(m_largeur, m_hauteur, GL_RGBA, GL_RGBA, true);

        colorBuffer.chargerTextureVide();

        // Ajout au tableau

        m_colorBuffers.push_back(colorBuffer);

        // Création du Depth et du Stencil Buffer

        creerRenderBuffer(m_depthBufferID, GL_DEPTH24_STENCIL8);

    // Déverrouillage du Frame Buffer

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

}
Association (Attachment)

Nos différentes buffers sont maintenant créés et prêts à être utilisés. Cependant ils ne sont pas d'une grande utilité pour le moment, ils sont tous éparpillés dans plusieurs attributs et OpenGL ne connait même pas leur existence. Pour remédier à ce problème, nous allons les associer au FBO, un peu comme nous avons associé les shaders au programme.

Pour faire cela, nous aurons besoin de deux fonctions : l'une est dédiée aux Color Buffers et l'autre aux Render Buffers. Je vais vous demander un peu d'attention pour leur explication car ce sont certainement les fonctions OpenGL les plus importantes de ce chapitre.

La première s'appelle glFramebufferTexture2D() (un peu compliqué comme nom [image] ) :

void glFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);

  • target : Le paramètre target du FBO, soit GL_FRAMEBUFFER ici

  • attachment : Paramètre SUPER important que nous allons développer dans un instant

  • textarget : Le paramètre target de la texture cette fois, soit GL_TEXTURE_2D

  • texture : L'identifiant de la texture à associer. Nous utiliserons le getter getID() de notre Color Buffer

  • level : Paramètre que l'on a déjà rencontré dans le chapitre sur les textures. Nous l'avions laissé à 0 et c'est ce que nous allons faire ici aussi

Le paramètre auquel il faut faire attention ici est évidemment le paramètre attachment. Ce dernier correspond au point d'attache, ou plus grossièrement à l'index du Color Buffer. N'oubliez pas que nous pouvons en créer jusqu'à 16, il faut qu'OpenGL donne un index à chacun d'entre eux pour pouvoir les reconnaitre au moment de l'affichage.

C'est précisément ce que fait le paramètre attachment, il permet de différencier les buffers au moment de les associer. Les valeurs qu'il peut prendre correspondent aux constantes allant de GL_COLOR_ATTACHMENT0 à GL_COLOR_ATTACHMENT15. Dans notre cas, nous n'utiliserons que la première car nous n'avons qu'un seul buffer à gérer.

Au final, l'appel à la fonction glFramebufferTexture2D() ressemblera à ceci :

// Association du Color Buffer

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorBuffers[0].getID(), 0);

La seconde fonction d'association ressemble fortement à la première, ses paramètres sont assez semblables sauf qu'ils permettent de gérer les Render Buffers. La fonction s'appelle glFramebufferRenderbuffer() :

void glFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);

  • target : Le paramètre target du FBO, soit GL_FRAMEBUFFER

  • attachment : Paramètre que l'on va développer dans un instant ;)

  • renderbuffertarget : Le paramètre target du Reder Buffer cette fois, celui que nous avons utilisé pour le configurer. Nous lui donnerons donc la constante GL_RENDERBUFFER

  • renderbuffer : L'identifiant du Render Buffer à associer. Nous lui donnerons l'attribut m_depthBufferID qui contient le Depth et le Stencil Buffer

Le principe du paramètre attachment ne change pas vraiment par rapport au précédent on parle toujours du point d'attache, sauf qu'ici OpenGL n'attend plus un index mais le type de Render Buffer que l'on souhaite associer.

Là-aussi, nous devrions en théorie appeler cette fonction deux fois pour le Depth et le Stencil Buffer, cependant avec la fusion de ces derniers, nous n'aurons besoin que d'un seul appel.

Le paramètre attachment prendra une constante assez proche de celle que l'on a utilisée pour la méthode creerRenderBuffer() et qui s'appelle : GL_DEPTH_STENCIL_ATTACHMENT. L'appel à la fonction glFramebufferRenderbuffer() ressemblera donc à ceci :

// Association du Depth et du Stencil Buffer

glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_depthBufferID);

Avec cet appel, on associe le double-buffer que l'on a créé précédemment avec le point d'attache GL_DEPTH_STENCIL_ATTACHMENT. Le FBO possède maintenant tous les buffers dont il a besoin pour fonctionner.

Si on récapitule tout ça :

bool FrameBuffer::charger()

{

    // Vérification d'un éventuel ancien FBO

    if(glIsFramebuffer(m_id) == GL_TRUE)

    {

        glDeleteFramebuffers(1, &m_id);

    }

    // Génération d'un id

    glGenFramebuffers(1, &m_id);

    // Verrouillage du Frame Buffer

    glBindFramebuffer(GL_FRAMEBUFFER, m_id);

        // Création du Color Buffer

        Texture colorBuffer(m_largeur, m_hauteur, GL_RGBA, GL_RGBA, true);

        colorBuffer.chargerTextureVide();

        // Ajout au tableau

        m_colorBuffers.push_back(colorBuffer);

        // Création du Depth et du Stencil Buffer

        creerRenderBuffer(m_depthBufferID, GL_DEPTH24_STENCIL8);

        // Association du Color Buffer

        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorBuffers[0].getID(), 0);

        // Association du Depth et du Stencil Buffer

        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_depthBufferID);

    // Déverrouillage du Frame Buffer

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

}
Vérification de la construction

En théorie, nous sommes maintenant capables d'utiliser notre classe FrameBuffer sans ajouter de code quelconque. Cependant, faire cela serait assez imprudent car nous ne savons absolument pas si notre FBO est valide ou non.

Pour éviter d'être surpris lors de notre prochain affichage, nous allons coder encore un petit bloc dans la méthode charger() qui nous permettra de vérifier ce que l'on peut appeler l'intégrité du FBO. Si une erreur s'est produite au moment de sa construction (buffer, association, etc.) OpenGL nous le fera savoir, et nous devrons réagir en conséquence.

Mais euh comment on fait pour savoir si le FBO est mal construit ?

La réponse est très simple : il existe une fonction pour nous le dire, tout comme avec les shaders.

Heureusement pour nous, la fonction en question est moins compliquée à utiliser que celle des shaders une fois de plus. Vous vous souvenez qu'avec eux, il fallait vérifier s'il y avait une erreur, récupérer la taille du message et l'afficher. Avec les FBO, nous n'avons pas à faire tout ça. :) Mais en contrepartie, nous n'aurons pas de détails précis sur l'erreur remontée. Ce n'est pas trop grave ici car nous n'avons pas de code source à vérifier, l'erreur est donc moins susceptible de venir de nous.

Cette fonction de vérification s'appelle glCheckFramebufferStatus() :

GLenum glCheckFramebufferStatus(GLenum target);

  • target : Le paramètre target des FBO ! La constante GL_FRAMEBUFFER. Oui c'est un peu spécial mais je vous rassure, la fonction fonctionne correctement.

Elle renvoie plusieurs constantes en cas d'erreur ou GL_FRAMEBUFFER_COMPLETE si tout s'est bien passé. Pour plus de simplicité, nous n'utiliserons que cette dernière. ;)

Nous commençons donc notre code en vérifiant la valeur renvoyée par la fonction glCheckFramebufferStatus(). Si elle est différente de GL_FRAMEBUFFER_COMPLETE alors on entre dans un bloc if :

// Vérification de l'intégrité du FBO

if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)

{

}

Si le programme entre dans ce bloc, c'est qu'il s'est passé quelque chose de mauvais dans la méthode (un Render Buffer mal initialisé par exemple). Si cela arrive, il faudra libérer toute la mémoire prise par les différents objets.

Pour cela, nous allons appeler la fonction glDeleteFramebuffers() pour détruire le FBO et la fonction glDeleteRenderbuffers() pour détruire le double-buffer gérant le Depth et le Stencil :

// Vérification de l'intégrité du FBO

if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)

{

    // Libération des buffers

    glDeleteFramebuffers(1, &m_id);

    glDeleteRenderbuffers(1, &m_depthBufferID);

}

Hum au fait, on ne libère pas tout là ... Il manque encore le Color Buffer non ?

Oui c'est exact ! Je l'ai gardé pour la fin celui-la. :p

En fait, ce buffer-la est un peu spécial vu qu'il s'agit d'une texture. Sa libération dépend donc entièrement de son destructeur. Pour l'appeler, il nous suffit simplement de supprimer la texture dans le tableau m_colorBuffers, le programme appellera automatiquement le destructeur concerné. C'est une des bases du C++.

Pour supprimer la texture, nous n'allons pas utiliser la méthode pop_back() contrairement à ce qu'on pourrait penser. A la place, nous allons prendre un peu d'avance et imaginer que nous ayons 7 Color Buffers à détruire. Si nous étions dans ce cas, nous n'appellerions pas la même méthode 7 fois d'affilé. Ce serait une perte de temps, surtout que la classe vector nous fourni une jolie méthode qui permet de vider entièrement son contenu. Cette méthode s'appelle clear().

Si nous l'utilisons, le tableau se videra entièrement et le programme appellera automatiquement les destructeurs de tous les objets qu'il contient.

Donc au final, pour libérer la mémoire prise par toutes les textures, nous appellerons la méthode clear() :

// Vérification de l'intégrité du FBO

if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)

{

    // Libération des buffers

    glDeleteFramebuffers(1, &m_id);

    glDeleteRenderbuffers(1, &m_depthBufferID);

    m_colorBuffers.clear();

}

Maintenant, tous nos objets sont détruits proprement et la mémoire est libérée.

Il ne reste plus qu'à afficher un message d'erreur pour conclure le tout et renvoyer la valeur false :

// Vérification de l'intégrité du FBO

if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)

{

    // Libération des buffers

    glDeleteFramebuffers(1, &m_id);

    glDeleteRenderbuffers(1, &m_depthBufferID);

    m_colorBuffers.clear();

    // Affichage d'un message d'erreur et retour de la valeur false

    std::cout << "Erreur : le FBO est mal construit" << std::endl;

    return false;

}

Si on récapitule toute notre méthode charger() :

bool FrameBuffer::charger()

{

    // Vérification d'un éventuel ancien FBO

    if(glIsFramebuffer(m_id) == GL_TRUE)

    {

        glDeleteFramebuffers(1, &m_id);

    }

    // Génération d'un id

    glGenFramebuffers(1, &m_id);

    // Verrouillage du Frame Buffer

    glBindFramebuffer(GL_FRAMEBUFFER, m_id);

        // Création du Color Buffer

        Texture colorBuffer(m_largeur, m_hauteur, GL_RGBA, GL_RGBA, true);

        colorBuffer.chargerTextureVide();

        // Ajout au tableau

        m_colorBuffers.push_back(colorBuffer);

        // Création du Depth et du Stencil Buffer

        creerRenderBuffer(m_depthBufferID, GL_DEPTH24_STENCIL8);

        // Association du Color Buffer

        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorBuffers[0].getID(), 0);

        // Association du Depth et du Stencil Buffer

        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_depthBufferID);

        // Vérification de l'intégrité du FBO

        if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)

        {

            // Libération des buffers

            glDeleteFramebuffers(1, &m_id);

            glDeleteRenderbuffers(1, &m_depthBufferID);

            m_colorBuffers.clear();

            // Affichage d'un message d'erreur et retour de la valeur false

            std::cout << "Erreur : le FBO est mal construit" << std::endl;

            return false;

        }

    // Déverrouillage du Frame Buffer

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

}

Il ne manque plus que la touche finale : le renvoi de la valeur true pour indiquer que tout s'est bien passé (tant que la condition précédente n'a pas été déclenchée) :

bool FrameBuffer::charger()

{

    // Création du FBO + Vérification

    ....

    // Déverrouillage du Frame Buffer

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    // Si tout s'est bien passé, on renvoie la valeur true

    return true;

}

Cette fois, nous avons enfin terminé tout le codage des Frame Buffer. :D Nous avons créé tout ce dont ils avaient besoin pour fonctionner. Nous avons même inclus un code de vérification en cas d'erreur.

On passe maintenant aux derniers détails à régler avant d'utiliser notre premier FBO dans une scène 3D. :D

Petit rajout

On commence tout de suite les petits détails par la première vérification de la méthode charger(), celle qui concerne le cas des double chargements. Je vous avais demandé de rajouter des accolades à votre bloc if car nous allions rajouter quelques lignes de code :

bool FrameBuffer::charger()

{

    // Vérification d'un éventuel ancien FBO

    if(glIsFramebuffer(m_id) == GL_TRUE)

    {

        glDeleteFramebuffers(1, &m_id);

    }

    // Génération d'un id

    ....

}

Les lignes de code concernées sont en fait celles qui permettent de détruire tous les objets OpenGL. Le but de cette vérification est de nettoyer un éventuel ancien chargement, il faut donc penser à détruire tous les objets qui étaient présents avant.

Cependant, nous n'ajouterons que la destruction des Colors Buffers car les Render Buffers sont détruits automatiquement au début de la méthode creerRenderBuffer(). Il est donc inutile de les détruire une seconde fois. ;)

Donc au final, nous n'ajoutons que l'appel à la méthode clear() pour l'attribut m_colorBuffers :

bool FrameBuffer::charger()

{

    // Vérification d'un éventuel ancien FBO

    if(glIsFramebuffer(m_id) == GL_TRUE)

    {

        // Destruction du Frame Buffer

        glDeleteFramebuffers(1, &m_id);

        // Libération des Color Buffers

        m_colorBuffers.clear();

    }

    // Génération d'un id

    ....

}

Le destructeur

Contrairement au début du chapitre, le destructeur va enfin pouvoir se remplir un peu pour lui permettre de détruire tous les objets OpenGL. Pour nous faciliter la vie en plus, nous allons faire les faignants et reprendre le code de destruction que nous déjà avons fait au moment de vérifier l'intégrité du FBO. :p En effet, ce code-la détruit proprement les différents objets OpenGL utilisés, à savoir tous les Color Buffers, le double Render Buffer (Depth et Stencil) et le FBO en lui-même pour finir :

FrameBuffer::~FrameBuffer()

{

    // Destruction des buffers

    glDeleteFramebuffers(1, &m_id);

    glDeleteRenderbuffers(1, &m_depthBufferID);

    m_colorBuffers.clear();

}

Quelques getters

On termine cette partie avec quelques getters qui nous serviront à manipuler nos FBO sans problème. Nous en aurons exactement besoin de quatre : le premier permettra de récupérer l'identifiant du FBO, le deuxième permettra de récupérer ceux des différents Color Buffer et les deux derniers s'occuperont de renvoyer la largeur et la hauteur. Voici leur prototype :

GLuint getID() const;

GLuint getColorBufferID(unsigned int index) const;

int getLargeur() const;

int getHauteur() const;

Remarquez que le second getter prend en paramètre une variable de type unsigned int qui permettra de récupérer un Color Buffer dans le tableau-attribut m_colorBuffers.

L'implémentation de la première méthode se passe de commentaire, il suffit de renvoyer la valeur de l'attribut m_id :

GLuint FrameBuffer::getID() const

{

    return m_id;

}

Au niveau de la seconde méthode, on ne peut pas se contenter de renvoyer l'attribut m_colorBuffers car cela casserait la règle de l'encapsulation (il ne faut pas pouvoir accéder à un attribut en dehors de sa classe d'origine). A la place, nous allons utiliser un index pour récupérer une texture dans ce tableau et appeler ensuite sa propre méthode getID(). De cette façon, on peut renvoyer l'identifiant du Color Buffer souhaité sans avoir à accéder directement à l'attribut m_colorBuffers en dehors de la classe. :)

GLuint FrameBuffer::getColorBufferID(unsigned int index) const

{

    return m_colorBuffers[index].getID();

}

Les deux derniers getters renverront simplement les attributs m_largeur et m_hauteur :

int FrameBuffer::getLargeur() const

{

    return m_largeur;

}

int FrameBuffer::getHauteur() const

{

    return m_hauteur;

}

Grâce à ces getters, nous pourrons utiliser nos FBO facilement. Et c'est justement ce que nous allons faire maintenant.

Utilisation

Quelques explications

Après toutes les péripéties des précédentes parties, nous allons pouvoir nous reposer un peu. L'utilisation des FBO est une chose assez simple si on la compare à la configuration car il n'y a pas grand chose à faire.

L'objectif de cette partie va être de faire un rendu d'une caisse à l'intérieur d'un carré, que l'on peut comparer un à second écran :

Avec ceci, nous ne verrons plus la caisse comme un modèle 3D mais comme une simple image sur un écran de télévision. Nous ajouterons même une petite rotation pour prouver que le rendu se fait en temps réel.

Mais avant cela, nous allons voir ensemble quelques notions relatives à l'utilisation des FBO. Elles ne sont pas compliquées à comprendre mais il faut mieux les voir pour éviter d'être surpris par certains points plus tard.

Les passes

La première chose à savoir à propos de l'utilisation des FBO c'est qu'elle est axée autour de deux étapes, ou de deux passes.

La première passe consiste à effectuer le rendu de ce qui se trouvera dans le FBO. Dans notre cas, ce serait uniquement une caisse. Si nous voulions faire une caméra de sécurité, il y aurait une salle, des personnages, des bureaux, etc. En gros tout ce qui se trouve dans le champ de vision de la caméra.

Tous ces rendus ne s'affichent pas sur l'écran évidemment mais dans le Color Buffer du FBO qui est une simple texture en deux dimensions.

La seconde passe quant à elle consiste à faire le rendu "normal" de la scène 3D, exactement comme nous l'avons toujours fait jusque là. La seule différence sera l'ajout d'un carré, ou tout autre surface, sur laquelle nous afficherons la texture du FBO. Pour reprendre l'exemple de la caméra, nous ajouterions une carré représentant une télévision quelque part dans notre scène sur laquelle viendrait s'afficher le rendu du FBO. Nous pourrions ainsi observer ce qui se trouve dans une salle tout en étant à un autre endroit.

Bien entendu, nous pouvons faire beaucoup plus de choses, notamment des ajouts d'effets sur la texture, mais vous avez au moins un aperçu de ce que sont les passes.

La résolution d'un FBO

La résolution des FBO est notion importante à prendre en compte car elle impacte directement les performances de votre application.

Il faut savoir qu'un FBO fera très rarement la taille de votre véritable écran. Il est tout à fait possible de procéder ainsi mais les ressources consommées seront trop importantes du fait des deux passes. C'est comme si vous travailliez avec des textures de 2048x2048 ...

Le choix de la résolution dépend de ce que vous voulez faire avec votre FBO. Un effet de miroir demandera plus de précision qu'un effet de flou par exemple. Il faudra jauger en fonction des situations.

Sachez que les options graphiques dans les jeux-vidéo permettent de déterminer cette précision (Qualité des ombres, des reflets, etc.)

Dans ce cas, nous utiliserons une résolution de 512x512. Cela nous rapprochera du pack de textures que nous utilisons.

Les matrices

Étant donné que nous avons deux affichages différents, nous allons devoir utiliser deux couples de matrices différents. Nous aurons ainsi une matrice projection et modelview dédiées à la première passe, et un autre couple pour le rendu normal.

Même si l'on fait deux fois le même affichage il vaut mieux séparer les matrices, en particulier la matrice projection car c'est elle qui gère la résolution finale. Souvenez-vous de ce que fait sa méthode perspective().

Première utilisation

Après ces petites explications, nous pouvons passer à la pratique. Le but est d'afficher une caisse dans une sorte de télé, nous allons le faire en faisant attention aux quelques points que nous avons vus à l'instant.

Création du FBO

Premièrement, nous allons reprendre le code du dernier chapitre et ajouter quelques lignes de code pour créer notre FBO. Celui-ci sera contenu dans un objet FrameBuffer et sa résolution de 512x512 pixels :

void SceneOpenGL::bouclePrincipale()

{

    // Variables

    unsigned int frameRate (1000 / 50);

    Uint32 debutBoucle(0), finBoucle(0), tempsEcoule(0);

    // Frame Buffer

    FrameBuffer frameBuffer(512, 512);

    frameBuffer.charger();

    // Matrices

    ....

}

N'oubliez pas d'inclure le header FrameBuffer.h pour ne pas avoir d'erreur au moment de la compilation. N'oubliez pas non plus d'appeler la méthode charger().

Les matrices

Maintenant que le FBO est créé, nous pouvons passer à la création du second couple de matrices, celles qui seront dédiées à la première passe. Ces matrices porteront quasiment le même nom que celles que nous avons l'habitude de manipuler :

void SceneOpenGL::bouclePrincipale()

{

    // Variables

    unsigned int frameRate (1000 / 50);

    Uint32 debutBoucle(0), finBoucle(0), tempsEcoule(0);

    // Frame Buffer

    FrameBuffer frameBuffer(512, 512);

    frameBuffer.charger();

    // Matrices (première passe)

    mat4 projectionFBO, modelviewFBO;

    ....

}

L'initialisation de ces matrices sera elle-aussi quasiment identique. La seule chose qui va différer dans cette initialisation c'est le rapport des pixels pour la matrice de projection (le ratio).

De base, celui-ci est relatif à la taille de votre fenêtre, tout dépend de ce que vous avez mis avant. Cependant le rendu de la première passe s'effectue dans le FBO, le rapport doit donc est relatif à la taille de ce dernier. Vu que nous avons spécifié une taille de 512x512 pixels, le rapport sera donc de 512 / 512. Nous utiliserons les getters sur la largeur et la hauteur du FBO pour avoir accès à ces valeurs, ce qui sera utile en cas de changement de ces dimensions :

// Matrices (première passe)

mat4 projectionFBO;

mat4 modelviewFBO;

// Initialisation

projectionFBO = perspective(70.0, (double)frameBuffer.getLargeur() / frameBuffer.getHauteur(), 1.0, 100.0);

modelviewFBO = mat4(1.0);

Si vous vous posez la question, sachez que l'autre couple de matrices n'a pas besoin d'être modifié, elles sont très bien comme ça. :)

void SceneOpenGL::bouclePrincipale()

{

    // Variables

    unsigned int frameRate (1000 / 50);

    Uint32 debutBoucle(0), finBoucle(0), tempsEcoule(0);

    // Frame Buffer

    FrameBuffer frameBuffer(512, 512);

    frameBuffer.charger();

    // Matrices (première passe)

    mat4 projectionFBO;

    mat4 modelviewFBO;

    projectionFBO = perspective(70.0, (double)frameBuffer.getLargeur() / frameBuffer.getHauteur(), 1.0, 100.0);

    modelviewFBO = mat4(1.0);

    // Matrices (seconde passe)

    mat4 projection;

    mat4 modelview;

    projection = perspective(70.0, (double) m_largeurFenetre / m_hauteurFenetre, 1.0, 100.0);

    modelview = mat4(1.0);

    // Caméra

    ....

}
La première passe

Tous nos objets sont maintenant en place, il ne manque plus qu'à passer aux deux passes. Dans la première, nous allons juste afficher une caisse dans le FBO. Créez donc, si ce n'est déjà fait, un objet Caisse. Ajoutez également une variable de type float, elle nous permettra de le faire pivoter sur lui-même :

void SceneOpenGL::bouclePrincipale()

{

    // Frame Buffer, Matrices et Caméra

    ....

    // Objet Caisse

    Caisse caisse(2.0, "Shaders/textureMVP.vert", "Shaders/texture.frag", "Textures/Caisse2.jpg");

    caisse.charger();

    float angle = 0.0;

    // Boucle principale

    ....

}

La classe Caisse a été modifiée dans le chapitre précédent pour gérer l'envoi de la matrice modelviewProjection et non les matrices projection et modelview séparément. Si vous n'avez pas modifié les classes Caisse et Cube et bien faites-le ( :p ) ou donnez le shader "texture.vert" à votre objet et non "textureMVP.vert".

Étant donné que les FBO se comportent comme des écrans, nous n'avons pas à modifier notre manière d'effectuer nos rendus. Il nous faut toujours nettoyer les buffers avec la fonction glClear(), repositionner la caméra, etc. La seule chose qui ne sera pas présente ce sera la limitation Frame Rate qui elle concerne l'application entière et pas juste un FBO.

Pour être plus précis, nous devons :

  • Nettoyer les buffers

  • Ré-initialiser la matrice modelview

  • Replacer la caméra

  • Afficher la caisse, rotation comprise

Nous avons déjà vu ce code pas mal de fois, je passerai donc les explications pour celui-ci. Faites juste attention à utiliser les matrices réservées au FBO.

// Boucle principale

while(!m_input.terminer())

{

    // Gestion des évènements

    ....

    /* ***** Première passe ***** */

    // Nettoyage de l'écran

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Placement de la caméra

    modelviewFBO = lookAt(vec3(3, 0, 3), vec3(0, 0, 0), vec3(0, 1, 0));

    // Gestion de la rotation de la caisse

    angle += 2;

    if(angle > 360)

        angle -= 360;

    mat4 sauvegardeModelviewFBO = modelviewFBO;

            modelviewFBO = rotate(modelviewFBO, angle, vec3(0, 1, 0));

            caisse.afficher(projectionFBO, modelviewFBO);

    modelviewFBO = sauvegardeModelviewFBO;

    // Actualisation de la fenêtre

    ....

}

Remarquez ici que l'on n'utilise pas la caméra mobile, on se place au niveau d'un point fixe. Si nous faisons cela, c'est pour éviter que le contenu du FBO ne change en fonction des mouvements de la caméra. Si vous faites un test avec elle, vous remarquez que votre cube n'apparait qu'à certains endroits car votre point de vue est modifié en permanence.

Mais si on ne change rien ce code, le rendu va se faire sur l'écran non ?

Oui évidemment si on ne dit rien à OpenGL il va comprendre qu'il doit tout afficher sur l'écran.

Pour lui dire d'utiliser le FBO, nous allons devoir ajouter deux choses :

  • Premièrement, nous allons devoir encadrer toutes les étapes précédentes par le verrouillage du FBO que l'on veut remplir. Il faudra donc appeler la fonction glBindFramebuffer() juste avant l'appel à glClear() et une autre fois après avoir affiché tous nos objets.

  • Ensuite, nous allons redimensionner virtuellement la fenêtre, ou plutôt le contexte OpenGL. Nous devons faire cela car celui-ci considère toujours qu'il est dans une fenêtre dont les dimensions sont celles que nous avons spécifiées au début du programme. Une petite fonction lui permettra de redimensionner son espace d'affichage pour correspondre aux dimensions du FBO.

Le verrouillage va être très simple à faire car il nous suffit d'appeler la fonction glBindFramebuffer() au début et à la fin de l'affichage de la première passe :

// Boucle principale

while(!m_input.terminer())

{

    // Gestion des évènements

    ....

    /* ***** Première passe ***** */

    // Verrouillage du Frame Buffer

    glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer.getID());

        // Nettoyage de l'écran

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // Placement de la caméra

        modelviewFBO = lookAt(vec3(3, 0, 3), vec3(0, 0, 0), vec3(0, 1, 0));

        // Gestion de la rotation de la caisse

        angle += 2;

        if(angle > 360)

            angle -= 360;

        // Affichage de la caisse

        mat4 sauvegardeModelviewFBO = modelviewFBO;

            modelviewFBO = rotate(modelviewFBO, angle, vec3(0, 1, 0));

            caisse.afficher(projectionFBO, modelviewFBO);

        modelviewFBO = sauvegardeModelviewFBO;

    // Déverrouillage du Frame Buffer

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    // Actualisation de la fenêtre

    ....

}

Le redimensionnement de la fenêtre est une notion qui peut vous paraitre un peu bizarre, mais pour vous donner un exemple voici ce qui se passerait si nous ne faisions pas cette étape :

Dans ce cas, OpenGL et le FBO ne travaillent pas avec les mêmes dimensions, la caisse peut se retrouver alors déformée, tronquée, ou je ne sais quoi d'autre.

Pour éviter cela, nous allons dire à OpenGL de travailler temporairement avec les dimensions du FBO (512x512 ici). Ceci se fait grâce à la fonction glViewport() :

void glViewport(GLint x, GLint y, GLsizei width, GLsizei height);

  • x : abscisse où commence le redimensionnement. Le point de coordonnées (0; 0) correspond au coin inférieur gauche de votre fenêtre

  • y : ordonnée où commence le redimensionnement

  • width : nouvelle largeur de la zone d'affichage

  • height : nouvelle hauteur

Cette fonction est à appeler juste après la fonction de nettoyage glClear() :

// Boucle principale

while(!m_input.terminer())

{

    // Gestion des évènements

    ....

    /* ***** Première passe ***** */

    // Verrouillage du Frame Buffer

    glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer.getID());

        // Nettoyage de l'écran

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // Redimensionnement de la zone d'affichage

        glViewport(0, 0, frameBuffer.getLargeur(), frameBuffer.getHauteur());

        // Placement de la caméra

        modelviewFBO = lookAt(vec3(3, 0, 3), vec3(0, 0, 0), vec3(0, 1, 0));

        // Gestion de la rotation de la caisse

        angle += 2;

        if(angle > 360)

            angle -= 360;

        // Affichage de la caisse

        mat4 sauvegardeModelviewFBO = modelviewFBO;

            modelviewFBO = rotate(modelviewFBO, angle, vec3(0, 1, 0));

            caisse.afficher(projectionFBO, modelviewFBO);

        modelviewFBO = sauvegardeModelviewFBO;

    // Déverrouillage du Frame Buffer

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    // Actualisation de la fenêtre

    ....

}

Vu que nous avons redimensionné l'espace de travail d'OpenGL ici, nous allons devoir refaire la même chose pour le rendu normal.

Si tout s'est bien passé jusque là, vous devriez avoir le rendu de votre caisse à l'intérieur de votre FBO, et plus précisément à l'intérieur de la texture qu'est le Color Buffer.

Si vous pensez qu'il y a trop de choses à apprendre, ne vous inquiétez pas nous allons faire un point récapitulatif à la fin de cette partie.

La seconde passe

La seconde passe est la dernière étape d'implémentation d'un FBO, elle consiste simplement à afficher le contenu de son Color Buffer sur une surface. Si vous avez une scène 3D, c'est le moment de l'afficher également.

Il n'y a pas de nouvelles notions à apprendre ici, il faut effectuer le rendu comme d'habitude. Le seul petit ajout sera le redimensionnement de la zone d'affichage qui est réduite à 512x512 pour le moment. Il faudra remettre les dimensions de la fenêtre SDL.

Pour commencer, nous allons afficher un carré qui accueillera la texture du FBO. Ajoutez donc le code suivant après déclaré la caisse. Je passe un peu son explication, le chapitre est assez dense comme ça, il permet juste d'afficher un carré avec une gestion des VBO/VAO. Les coordonnées de textures sont également présentes, elles permettront de faire le lien avec la texture du FBO.

void SceneOpenGL::bouclePrincipale()

{

    // Frame Buffer, Matrices et Caméra

    ....

    // Objet Caisse

    ...

    // Vertices

    float vertices[] = {0, 0, 0,   4, 0, 0,   4, 4, 0,     // Triangle 1

                0, 0, 0,   0, 4, 0,   4, 4, 0};    // Triangle 2

    float coordTexture[] = {0, 0,   1, 0,   1, 1,          // Triangle 1

                0, 0,   0, 1,   1, 1};         // Triangle 2

    /* ***** Gestion du VBO ***** */

    GLuint vbo;

    int tailleVerticesBytes = 18 * sizeof(float);

    int tailleCoordTextureBytes = 12 * sizeof(float);

    // Génération du VBO

    glGenBuffers(1, &vbo);

    // Verrouillage

    glBindBuffer(GL_ARRAY_BUFFER, vbo);

    // Remplissage

    glBufferData(GL_ARRAY_BUFFER, tailleVerticesBytes + tailleCoordTextureBytes, 0, GL_STATIC_DRAW);

    glBufferSubData(GL_ARRAY_BUFFER, 0, tailleVerticesBytes, vertices);

    glBufferSubData(GL_ARRAY_BUFFER, tailleVerticesBytes, tailleCoordTextureBytes, coordTexture);

    // Déverrouillage

    glBindBuffer(GL_ARRAY_BUFFER, 0);

    /* ***** Gestion du VAO ***** */

    GLuint vao;

    // Génération du VAO

    glGenVertexArrays(1, &vao);

    // Verrouillage du VAO

    glBindVertexArray(vao);

    // Verrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, vbo);

        // Vertex Attrib 0 (Vertices)

        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

        glEnableVertexAttribArray(0);

         // Vertex Attrib 0 (Vertices)

        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(tailleVerticesBytes));

        glEnableVertexAttribArray(2);

    // Déverrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, 0);

    // Déverrouillage du VAO

    glBindVertexArray(0);

    // Shader

    Shader shaderTexture("Shaders/textureMVP.vert", "Shaders/texture.frag");

    shaderTexture.charger();

    // Boucle principale

    while(!m_input.terminer())

    {

        ....

    }

}

Dans le prochain chapitre, nous créerons une classe dédiée pour les carrés. Cela évitera d'avoir à inclure des gros bouts de code comme celui-là. :)

Une fois le carré déclaré, il me manque plus qu'à l'afficher. Pour cela nous allons répéter les mêmes opérations d'affichage que l'on a l'habitude de faire, à savoir:

  • Activer le shader

  • Verrouiller le VAO

  • Envoyer les matrices

  • Verrouiller la texture

  • Afficher le tout

// Boucle principale

while(!m_input.terminer())

{

    // Gestion des évènements

    ....

    /* ***** Première passe ***** */

    ....

    /* ***** Seconde passe ***** */

    // Nettoyage de l'écran

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Gestion de la caméra

    camera.lookAt(modelview);

    // Activation du shader

    glUseProgram(shaderTexture.getProgramID());

    // Verrouillage du VAO

    glBindVertexArray(vao);

        // Envoi des matrices

            shaderTexture.envoyerMat4("modelviewProjection", projection * modelview);

        // Verrouillage de la texture

        glBindTexture(GL_TEXTURE_2D, frameBuffer.getColorBufferID(0));

        // Rendu

        glDrawArrays(GL_TRIANGLES, 0, 6);

        // Déverrouillage de la texture

            glBindTexture(GL_TEXTURE_2D, 0);

        // Verrouillage du VAO

        glBindVertexArray(0);

    // Désactivation du shader

    glUseProgram(0);

    // Gestion du Frame Rate

    ....

}

N'oublions pas la touche finale qui consiste à redimensionner de la zone d'affichage. Nous utiliserons les attributs m_largeurFenetre et m_hauteurFenetre avec la fonction glViewport() pour régler ce petit détail :

/* ***** Seconde passe ***** */

// Nettoyage de l'écran

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// Redimensionnement de la zone d'affichage

glViewport(0, 0, m_largeurFenetre, m_hauteurFenetre);

// Gestion de la caméra

camera.lookAt(modelview);
Récapitulatif de la méthode bouclePrincipale()

La méthode bouclePrincipale() accueille pas mal de code en ce moment, nous allons tout récapituler pour être sûr qu'il ne vous manque pas un bout quelque part.

Voici les objets à déclarer au début de votre méthode (attention au shader de la caisse je le répète ;) ) :

void SceneOpenGL::bouclePrincipale()

{

    // Variables

    unsigned int frameRate (1000 / 50);

    Uint32 debutBoucle(0), finBoucle(0), tempsEcoule(0);

    // Frame Buffer

    FrameBuffer frameBuffer(512, 512);

    frameBuffer.charger();

    // Matrices (première passe)

    mat4 projectionFBO;

    mat4 modelviewFBO;

    projectionFBO = perspective(70.0, (double)frameBuffer.getLargeur() / frameBuffer.getHauteur(), 1.0, 100.0);

    modelviewFBO = mat4(1.0);

    // Matrices (seconde passe)

    mat4 projection;

    mat4 modelview;

    projection = perspective(70.0, (double) m_largeurFenetre / m_hauteurFenetre, 1.0, 100.0);

    modelview = mat4(1.0);

    // Caméra mobile

    Camera camera(vec3(3, 3, 3), vec3(0, 0, 0), vec3(0, 1, 0), 0.5, 0.5);

    m_input.afficherPointeur(false);

    m_input.capturerPointeur(true);

    // Objet Caisse

    Caisse caisse(2.0, "Shaders/textureMVP.vert", "Shaders/texture.frag", "Textures/Caisse2.jpg");

    caisse.charger();

    float angle = 0.0;

    // Vertices

    float vertices[] = {0, 0, 0,   4, 0, 0,   4, 4, 0,     // Triangle 1

                0, 0, 0,   0, 4, 0,   4, 4, 0};    // Triangle 2

    float coordTexture[] = {0, 0,   1, 0,   1, 1,          // Triangle 1

                0, 0,   0, 1,   1, 1};         // Triangle 2

    /* ***** Gestion du VBO ***** */

    GLuint vbo;

    int tailleVerticesBytes = 18 * sizeof(float);

    int tailleCoordTextureBytes = 12 * sizeof(float);

    // Génération du VBO

    glGenBuffers(1, &vbo);

    // Verrouillage

    glBindBuffer(GL_ARRAY_BUFFER, vbo);

    // Remplissage

    glBufferData(GL_ARRAY_BUFFER, tailleVerticesBytes + tailleCoordTextureBytes, 0, GL_STATIC_DRAW);

    glBufferSubData(GL_ARRAY_BUFFER, 0, tailleVerticesBytes, vertices);

    glBufferSubData(GL_ARRAY_BUFFER, tailleVerticesBytes, tailleCoordTextureBytes, coordTexture);

    // Déverrouillage

    glBindBuffer(GL_ARRAY_BUFFER, 0);

    /* ***** Gestion du VAO ***** */

    GLuint vao;

    // Génération du VAO

    glGenVertexArrays(1, &vao);

    // Verrouillage du VAO

    glBindVertexArray(vao);

    // Verrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, vbo);

        // Vertex Attrib 0 (Vertices)

        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

        glEnableVertexAttribArray(0);

         // Vertex Attrib 0 (Vertices)

        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(tailleVerticesBytes));

        glEnableVertexAttribArray(2);

    // Déverrouillage du VBO

    glBindBuffer(GL_ARRAY_BUFFER, 0);

    // Déverrouillage du VAO

    glBindVertexArray(0);

    // Shader

    Shader shaderTexture("Shaders/textureMVP.vert", "Shaders/texture.frag");

    shaderTexture.charger();

    // Boucle principale

    while(!m_input.terminer())

    {

        ....

    }

}

Et voici ce que contient la boucle principale :

// Boucle principale

while(!m_input.terminer())

{

    // On définit le temps de début de boucle

    debutBoucle = SDL_GetTicks();

    // Gestion des évènements

    m_input.updateEvenements();

    if(m_input.getTouche(SDL_SCANCODE_ESCAPE))

        break;

    camera.deplacer(m_input);

    /* ***** Première Passe ***** */

    // Verrouillage du Frame Buffer

    glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer.getID());

        // Nettoyage de l'écran

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // Redimensionnement de la zone d'affichage

        glViewport(0, 0, frameBuffer.getLargeur(), frameBuffer.getHauteur());

        // Placement de la caméra

        modelviewFBO = lookAt(vec3(3, 0, 3), vec3(0, 0, 0), vec3(0, 1, 0));

        // Gestion de la rotation de la caisse

        angle += 2;

        if(angle > 360)

            angle -= 360;

        // Affichage de la caisse

        mat4 sauvegardeModelviewFBO = modelviewFBO;

            modelviewFBO = rotate(modelviewFBO, angle, vec3(0, 1, 0));

            caisse.afficher(projectionFBO, modelviewFBO);

        modelviewFBO = sauvegardeModelviewFBO;

    // Déverrouillage du Frame Buffer

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    /* ***** Seconde Passe ***** */

    // Nettoyage de l'écran

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Redimensionnement de la zone d'affichage

    glViewport(0, 0, m_largeurFenetre, m_hauteurFenetre);

    // Gestion de la caméra

    camera.lookAt(modelview);

    // Activation du shader

    glUseProgram(shaderTexture.getProgramID());

    // Verrouillage du VAO

    glBindVertexArray(vao);

        // Envoi des matrices

            shaderTexture.envoyerMat4("modelviewProjection", projection * modelview);

        // Verrouillage de la texture

        glBindTexture(GL_TEXTURE_2D, frameBuffer.getColorBufferID(0));

        // Rendu

        glDrawArrays(GL_TRIANGLES, 0, 6);

        // Déverrouillage de la texture

            glBindTexture(GL_TEXTURE_2D, 0);

        // Verrouillage du VAO

        glBindVertexArray(0);

    // Désactivation du shader

    glUseProgram(0);

    // Actualisation de la fenêtre

    SDL_GL_SwapWindow(m_fenetre);

    // Calcul du temps écoulé

    finBoucle = SDL_GetTicks();

    tempsEcoule = finBoucle - debutBoucle;

    // Si nécessaire, on met en pause le programme

    if(tempsEcoule < frameRate)

        SDL_Delay(frameRate - tempsEcoule);

}

Si vous êtes prêts, vous pouvez compiler tout ça pour voir ce que cela donne. :D

C'est un peu bizarre ton truc on ne voit pas grand chose. :(

Bon j'avoue, on ne voit pas grand chose.

On va ajouter un appel à la fonction glClearColor() juste avant d'appeler glClear() au niveau de la première passe. Cette fonction permet de donner une couleur par défaut à votre affichage au moment où les buffers sont nettoyés :

void glClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)

  • red : composante rouge de la couleur par défaut

  • green : composante verte

  • blue : composante bleue

  • alpha : composante alpha

Appelons cette fonction dans la première passe juste avant glClear(), nous donnerons une couleur grise par défaut :

/* ***** Première passe ***** */

// Nettoyage de l'écran

glClearColor(0.5, 0.5, 0.5, 1.0);

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

Le problème avec cette fonction est qu'elle applique la couleur par défaut à tous les FBO, écran compris. Pour remettre la couleur qu'il y avait avant il faut donc faire la même chose qu'avec glViewport() et rappeler la fonction une seconde fois au moment de la seconde passe.

C'est donc ce que nous allons faire en remettant cette fois la couleur noir :

/* ***** Seconde passe ***** */

// Nettoyage de l'écran

glClearColor(0.0, 0.0, 0.0, 1.0);

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

Recompilons maintenant tout cela :

C'est déjà un peu mieux.

Faites le tour de votre carré pour voir que votre caisse n'existe pas en 3D, elle est contenue dans une simple texture 2D. Grâce aux FBO, nous sommes en théorie capables d'appliquer n'importe quel effet sur cet affichage à l'aide des shaders (déformation, miroir, flou, ombre, etc.).

Cet exemple n'est pas bien parlant, mais le TP qui va arriver juste après ce chapitre, vous devrez ên faire une utilisation plus utile.

Ce qu'il faut retenir

Piouf, nous aurons vu pas mal de choses dans ce chapitre. Entre la création d'un FBO et son utilisation finale, il y a plein de petites notions auxquelles il faut faire attention. Nous allons résumé tout cela en quelques points pour synthétiser toute cette partie.

Les préparatifs

Premièrement, l'utilisation d'un FBO est divisée en deux étapes que l'on appelle passe. La première contient les éléments affichés uniquement dans le FBO. La seconde quant à elle se concentre sur l'affichage normal de la scène (plus le contenu du FBO).

Avant même de commencer la première passe, il faut déterminer les dimensions du FBO. Plus elles seront grandes, meilleur sera votre rendu. Cependant cela impactera sur les performances de votre application final. Il est préférable de choisir des dimensions moins importantes pour économiser au maximum vos ressources.

Enfin, le dernier point à retenir pour les préparatifs est la création d'un second couple de matrices spécialement dédié à la première passe. On évite ainsi d'éventuelles erreurs en rapport avec la seconde. La matrice projection doit prendre en compte le rapport des dimensions du FBO dans la méthode perspective().

La première passe

La première passe commence par le verrouillage du FBO à l'aide de la fonction glBindFramebuffer(). Incluez le déverrouillage immédiatement après pour ne pas oublier de le faire.

Le rendu se comporte ensuite de la même façon qu'un rendu classique. Cela va de la fonction glClear(), qui permet de nettoyer les buffers du FBO jusqu'au dernier objet que vous souhaitez afficher. Petite précision importante : Il est préférable de se positionner d'un point de vu fixe, et non mobile. Préférez donc la méthode lookAt() de la matrice modelview.

Pensez à appeler la fonction glViewport() pour redimensionner la zone d'affichage en fonction des dimensions du FBO.

La seconde passe

Le rendu de la second passe se concentre sur le rendu normal de la scène 3D. On retrouve ainsi tous les objets qui la composent, même ceux faisant partie du FBO.

Pensez la encore à appeler la fonction glViewport() pour redimensionner la zone d'affichage en fonction des dimensions de votre fenêtre.

Affichez enfin le contenu du Color Buffer du FBO sur une surface comme vous le feriez pour une texture classique.

Améliorations simples

Dans cette partie, nous allons faire plusieurs modifications dans notre classe FrameBuffer. Nous allons tout d'abord voir comment économiser un peu de ressources en évitant d'utiliser le Stencil Buffer lorsque l'on n'en a pas besoin. Nous implémenterons ensuite le constructeur de copie, vous savez déjà à quoi il peut bien service. Enfin, nous verrons ensemble, dans une ultime partie, comment gérer plusieurs Color Buffers. Ce sera un peu technique, je ferai donc un point récapitulatif dessus à la fin.

Éviter la création du Stencil Buffer

Un nouvel attribut

Le premier point que nous allons gérer concerne l'économie de ressources relatives au Stencil Buffer. En effet celui-ci n'est pas souvent utilisé, d'ailleurs nous ne l'avons jamais utilisé, nous pouvons donc éviter sa création dans la méthode charger() lorsque nous n'en avons pas besoin.

Pour cela, nous allons ajouter un nouvel attribut nommé m_utiliserStencilBuffer de type bool :

// Classe

class FrameBuffer

{

    public:

    ....

    private:

    GLuint m_id;

    int m_largeur;

    int m_hauteur;

    std::vector<Texture> m_colorBuffers;

    GLuint m_depthBufferID;

    bool m_utiliserStencilBuffer;

};

Celui-ci permettra de dire si oui ou non, nous devons créer le Stencil Buffer.

Modification du constructeur

Pour lui donner une valeur au moment au moment de créer un objet FrameBuffer, nous allons légèrement modifier le constructeur. Pour le moment, le prototype est le suivant :

FrameBuffer(int largeur, int hauteur);

Nous allons ajouter un autre paramètre pour lui permettre de donner une valeur à notre nouveau booléen. De préférence, nous l'ajouterons avec une valeur par défaut égale à false car le Stencil Buffer n'est pas souvent utilisé :

FrameBuffer(int largeur, int hauteur, bool utiliserStencilBuffer = false);

Bien entendu, il faut modifier le constructeur dans le fichier .cpp. Il faut ajouter ce nouveau paramètre ainsi que l'initialisation du booléen :

FrameBuffer::FrameBuffer(int largeur, int hauteur, bool utiliserStencilBuffer) : m_id(0), m_largeur(largeur), m_hauteur(hauteur),

                                                                                m_colorBuffers(0), m_depthBufferID(0),

                                                                                m_utiliserStencilBuffer(utiliserStencilBuffer)

{

}

Pour compléter les modifications, pensons à initialiser le booléen dans le constructeur par défaut avec une valeur égale à false :

FrameBuffer::FrameBuffer() : m_id(0), m_largeur(0), m_hauteur(0), m_colorBuffers(0), m_depthBufferID(0), m_utiliserStencilBuffer(false)

{

}
Création des Render Buffers

Maintenant que le booléen possède une valeur (soit false par défaut, soit true par le programmeur) nous pouvons l'utiliser dans la méthode charger() pour nous permettre de bloquer ou non le chargement du Stencil Buffer.

Mais avant cela, nous allons revenir à la création du double-Render Buffer et se demander : comment peut-on séparer le Depth et le Stencil Buffer ? Car je vous rappelle que les deux se trouve dans le même buffer.

La réponse est en fait très simple et se situe au niveau du format interne, celui que nous donnons à la méthode creerRenderBuffer() :

// Création du Depth et du Stencil Buffer

creerRenderBuffer(m_depthBufferID, GL_DEPTH24_STENCIL8);

Pour le moment, nous lui donnons la constante GL_DEPTH24_STENCIL8 pour dire à OpenGL de créer un double-RenderBuffer contenant le Depth et le Stencil.

Pour ne garder que le Depth, il suffit juste de changer la constante en GL_DEPTH_COMPONENT24. Avec elle, OpenGL comprendrait qu'il ne doit créer que le Depth Buffer. L'appel à la méthode creerRenderBuffer() ressemblerait donc à ceci :

// Création du Depth Buffer

creerRenderBuffer(m_depthBufferID, GL_DEPTH_COMPONENT24);

En définitif, si le booléen m_utiliserStencilBuffer est égal à la valeur true alors on conserve le double-Render Buffer, sinon on utilise la nouvelle constante pour ne garder que le Depth Buffer :

bool FrameBuffer::charger()

{

    // Début de la méthode

    ....

    // Création du Depth Buffer et du Stencil Buffer (si besoin)

    if(m_utiliserStencilBuffer == true)

        creerRenderBuffer(m_depthBufferID, GL_DEPTH24_STENCIL8);

    else

        creerRenderBuffer(m_depthBufferID, GL_DEPTH_COMPONENT24);

    // Association du Color Buffer

    ....

}
Association des Renders Buffers

L'appel à la fonction glFramebufferRenderbuffer() doit également être modifié. En effet, son paramètre attachment permet de spécifier le point d'attache du Render buffer, sa valeur est pour le moment égale à la constante GL_DEPTH_STENCIL_ATTACHMENT :

// Association du Depth et du Stencil Buffer

glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_depthBufferID);

Dans le cas où le booléen est à false, il faudrait changer cette constante car le Stencil Buffer n'existe pas. Celle-ci deviendrait alors GL_DEPTH_ATTACHMENT :

// Association du Depth Buffer

glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depthBufferID);

En utilisant le booléen, l'association du Render Buffer ressemblerait donc à :

bool FrameBuffer::charger()

{

    // Début de la méthode

    ....

    // Association du Color Buffer

    ....

    // Création du Depth Buffer et du Stencil Buffer (si besoin)

    if(m_utiliserStencilBuffer == true)

        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_depthBufferID);

    else

        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depthBufferID);

    // Vérification de l'intégrité du FBO

    ....

}
Création d'un objet FrameBuffer

Au final, si vous souhaitez utiliser le Stencil Buffer avec un FBO vous devrez ajouter la valeur true au constructeur :

// Frame Buffer avec Stencil Buffer

FrameBuffer frameBuffer(512, 512, true);

Si vous ne souhaitez pas l'utiliser, soit vous ne mettez pas de booléen, soit vous mettez la valeur false :

// Frame Buffer sans Stencil Buffer

FrameBuffer frameBuffer(512, 512);

Le constructeur de copie

Le constructeur de copie est une pseudo-méthode que nous avons l'habitude de rencontrer maintenant. Vous savez que les objets OpenGL ne peuvent être copiés comme de simples variables, ils doivent être totalement rechargés dans leur copie exactement comme s'il s'agissait de pointeurs. On ne risque ainsi pas de perdre des données si un objet original est détruit.

Le constructeur de copie est assez simple puisqu'il ne prend en paramètre qu'une référence constante sur un objet de même type, ici FrameBuffer :

FrameBuffer(const FrameBuffer &frameBufferACopier);

Son implémentation commencera comme d'habitude par la copie des attributs variables. Si on regarde notre liste d'attributs, on remarque qu'il n'y en a que trois : m_largeur, m_hauteur et m_utiliserStencilBuffer.

FrameBuffer::FrameBuffer(const FrameBuffer &frameBufferACopier)

{

    // Copie de la largeur, de la hauteur et du booléen

    m_largeur = frameBufferACopier.m_largeur;

    m_hauteur = frameBufferACopier.m_hauteur;

    m_utiliserStencilBuffer = frameBufferACopier.m_utiliserStencilBuffer;

}

Ces trois attributs peuvent être copiés directement. En revanche, les autres sont( ou contiennent) des objets OpenGL ils ne peuvent donc pas être copiés par le signe =. La méthode qui leur donne une valeur est la méthode charger(). C'est elle qui charge le FBO et qui permet de gérer un identifiant à tous ces attributs.

Nous l'appellerons donc cette méthode pour simuler une copie de ces attributs :

FrameBuffer::FrameBuffer(const FrameBuffer &frameBufferACopier)

{

    // Copie de la largeur, de la hauteur et du booléen

    m_largeur = frameBufferACopier.m_largeur;

    m_hauteur = frameBufferACopier.m_hauteur;

    m_utiliserStencilBuffer = frameBufferACopier.m_utiliserStencilBuffer;

    // Chargement de la copie du Frame Buffer

    charger();

}

Le constructeur de copie est maintenant complet. :)

Gérer plusieurs Color Buffers

Cette partie est en cours de ré-écriture, sa précédente version était trop lourde et inutilement complexe. Néanmoins, vous avez déjà pratiquement tout vu sur les FBO.

Télécharger (Windows, UNIX/Linux, Mac OS X) : Code Source C++ du chapitre sur les Frame Buffers Objects

Ce chapitre était un peu compliqué et il y avait pas mal de notions à assimiler en une fois. Vous comprendrez pourquoi je l'ai placé à la fin des notions avancées.

Les Frame Buffers permettent de faire pas mal de choses mais il faut avoir un minimum de connaissances en OpenGL pour connaitre leur fonctionnement. Je vous rassure tout de suite, si je vous ai fait un chapitre dessus c'est parce que vous êtes tout à fait capables de les utiliser.

Après tout ce que vous avez vu jusqu'à présent, vous avez une bonne vu d'ensemble de ce que propose OpenGL. Il y a plein d'autres fonctionnalités à voir évidemment, mais à partir de maintenant nous n'allons pas vraiment apprendre de nouvelles notions pures et dures mais nous allons plutôt utiliser tout ce que nous avons vu pour faire de véritables rendus. Nous allons apprendre à charger des modèles 3D statiques et animés, utiliser des SkyBox, des heigh maps, et bien évidemment des effets ralistes avec les shaders.

Avant de passer à ce programme, je vous invite à faire un petit TP, basé sur le premier que vous avez déjà fait, pour mettre en pratique ce que nous avons vu dans cette deuxième partie. Il y aura des VBO, des VAO, des shaders et bien évidemment des FBO. :p

Ce tutoriel est loin d'être terminé, nous avons encore pas mal de choses à voir et à apprendre ensemble [image] . Pour vous donner un avant-goût des prochains chapitres, sachez que :

  • La troisième partie sera consacrée aux techniques du jeu vidéo en général, comme le chargement de modèles 3D animés et non-animés, les polices d'écriture, ...

  • La quatrième partie sera consacrée à l'élaboration d'effets avancés comme la lumière, le bump mapping, l'eau, ...

Si vous avez des remarques ou des éléments que vous aimeriez que je développe, merci de m'en faire part dans les commentaires de ce tutoriel. Toute critique (constructive) sera la bienvenue :) .


Accueil

Accueil CHM

Normal, dit comme ça,
vous ne risquez pas de comprendre.


glAccum.3gl

Name

  glAccum - operate on the accumulation	buffer

C Specification

  void glAccum(	GLenum op,
        GLfloat	value )

Parameters

  op	 Specifies the accumulation buffer operation.  Symbolic	constants
     GL_ACCUM, GL_LOAD, GL_ADD, GL_MULT, and GL_RETURN are accepted.

  value	 Specifies a floating-point value used in the accumulation buffer
     operation.  op	determines how value is	used.

Description

  The accumulation buffer is an	extended-range color buffer.  Images are not
  rendered into	it.  Rather, images rendered into one of the color buffers
  are added to the contents of the accumulation	buffer after rendering.
  Effects such as antialiasing (of points, lines, and polygons), motion	blur,
  and depth of field can be created by accumulating images generated with
  different transformation matrices.

  Each pixel in	the accumulation buffer	consists of red, green,	blue, and
  alpha	values.	 The number of bits per	component in the accumulation buffer
  depends on the implementation. You can examine this number by	calling
  glGetIntegerv	four times, with arguments GL_ACCUM_RED_BITS,
  GL_ACCUM_GREEN_BITS, GL_ACCUM_BLUE_BITS, and GL_ACCUM_ALPHA_BITS,
  respectively.	 Regardless of the number of bits per component, however, the
  range	of values stored by each component is [-1, 1].	The accumulation
  buffer pixels	are mapped one-to-one with frame buffer	pixels.

  glAccum operates on the accumulation buffer.	The first argument, op,	is a
  symbolic constant that selects an accumulation buffer	operation.  The
  second argument, value, is a floating-point value to be used in that
  operation.  Five operations are specified: GL_ACCUM, GL_LOAD,	GL_ADD,
  GL_MULT, and GL_RETURN.

  All accumulation buffer operations are limited to the	area of	the current
  scissor box and are applied identically to the red, green, blue, and alpha
  components of	each pixel.  The contents of an	accumulation buffer pixel
  component are	undefined if the glAccum operation results in a	value outside
  the range [-1,1].  The operations are	as follows:

  GL_ACCUM  Obtains R, G, B, and A values from the buffer currently selected
        for	reading	(see glReadBuffer).  Each component value is divided
        by 2n-1, where n is	the number of bits allocated to	each color
        component in the currently selected	buffer.	 The result is a
        floating-point value in the	range [0,1], which is multiplied by
        value and added to the corresponding pixel component in the
        accumulation buffer, thereby updating the accumulation buffer.

  GL_LOAD   Similar to GL_ACCUM, except	that the current value in the
        accumulation buffer	is not used in the calculation of the new
        value.  That is, the R, G, B, and A	values from the	currently
        selected buffer are	divided	by 2n-1, multiplied by value, and
        then stored	in the corresponding accumulation buffer cell,
        overwriting	the current value.

  GL_ADD    Adds value to each R, G, B,	and A in the accumulation buffer.

  GL_MULT   Multiplies each R, G, B, and A in the accumulation buffer by
        value and returns the scaled component to its corresponding
        accumulation buffer	location.

  GL_RETURN Transfers accumulation buffer values to the	color buffer or
        buffers currently selected for writing.  Each R, G,	B, and A
        component is multiplied by value, then multiplied by 2n-1,
        clamped to the range [0,2n-1], and stored in the corresponding
        display buffer cell.  The only fragment operations that are
        applied to this transfer are pixel ownership, scissor, dithering,
        and	color writemasks.

  The accumulation buffer is cleared by	specifying R, G, B, and	A values to
  set it to with the glClearAccum directive, and issuing a glClear command
  with the accumulation	buffer enabled.

Notes

  Only those pixels within the current scissor box are updated by any glAccum
  operation.

Errors

  GL_INVALID_ENUM is generated if op is	not an accepted	value.

  GL_INVALID_OPERATION is generated if there is	no accumulation	buffer.

  GL_INVALID_OPERATION is generated if glAccum is executed between the
  execution of	glBegin	and the	corresponding execution	of glEnd.

Associated Gets

  glGet	with argument GL_ACCUM_RED_BITS
  glGet	with argument GL_ACCUM_GREEN_BITS
  glGet	with argument GL_ACCUM_BLUE_BITS
  glGet	with argument GL_ACCUM_ALPHA_BITS

See Also

  glBlendFunc, glClear,	glClearAccum, glCopyPixels, glGet, glLogicOp,
  glPixelStore,	glPixelTransfer, glReadPixels, glReadBuffer, glScissor,
  glStencilOp

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glAlphaFunc.3gl

Name

  glAlphaFunc -	specify	the alpha test function

C Specification

  void glAlphaFunc( GLenum func,
            GLclampf ref )

Parameters

  func	Specifies the alpha comparison function.  Symbolic constants
    GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL,	GL_GREATER, GL_NOTEQUAL,
    GL_GEQUAL, and GL_ALWAYS are accepted.	The default function is
    GL_ALWAYS.

  ref	Specifies the reference	value that incoming alpha values are compared
    to.  This value	is clamped to the range	0 through 1, where 0
    represents the lowest possible alpha value and 1 the highest possible
    value.	The default reference is 0.

Description

  The alpha test discards fragments depending on the outcome of	a comparison
  between the incoming fragment's alpha	value and a constant reference value.
   glAlphaFunc specifies the reference and comparison function.	 The
  comparison is	performed only if alpha	testing	is enabled.  (See glEnable
  and glDisable	of GL_ALPHA_TEST.)

  func and ref specify the conditions under which the pixel is drawn.  The
  incoming alpha value is compared to ref using	the function specified by
  func.	 If the	comparison passes, the incoming	fragment is drawn,
  conditional on subsequent stencil and	depth buffer tests. If the comparison
  fails, no change is made to the frame	buffer at that pixel location.

  The comparison functions are as follows:

  GL_NEVER	    Never passes.

  GL_LESS	    Passes if the incoming alpha value is less than the
            reference value.

  GL_EQUAL	    Passes if the incoming alpha value is equal	to the
            reference value.

  GL_LEQUAL	    Passes if the incoming alpha value is less than or equal
            to the reference value.

  GL_GREATER	    Passes if the incoming alpha value is greater than the
            reference value.

  GL_NOTEQUAL	    Passes if the incoming alpha value is not equal to the
            reference value.

  GL_GEQUAL	    Passes if the incoming alpha value is greater than or
            equal to the reference value.

  GL_ALWAYS	    Always passes.

  glAlphaFunc operates on all pixel writes, including those resulting from
  the scan conversion of points, lines,	polygons, and bitmaps, and from	pixel
  draw and copy	operations.  glAlphaFunc does not affect screen	clear
  operations.

Notes

  Alpha	testing	is done	only in	RGBA mode.

Errors

  GL_INVALID_ENUM is generated if func is not an accepted value.

  GL_INVALID_OPERATION is generated if glAlphaFunc is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_ALPHA_TEST_FUNC
  glGet	with argument GL_ALPHA_TEST_REF
  glIsEnabled with argument GL_ALPHA_TEST

See Also

  glBlendFunc, glClear,	glDepthFunc, glEnable, glStencilFunc

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glArrayElementEXT.3gl

Name

  glArrayElementEXT - specify the array	elements used to render	a vertex

C Specification

  void glArrayElementEXT( GLint	i )

Parameters

  i  Specifies an index	in the enabled arrays.

Description

  glArrayElementEXT commands are used within glBegin/glEnd pairs to specify
  vertex and attribute data for	point, line and	polygon	primitives. When
  glArrayElementEXT is called, a single	vertex is drawn, using vertex and
  attribute data taken from location i of the enabled arrays.

  Use glArrayElementEXT	to construct primitives	by indexing vertex data,
  rather than by streaming through arrays of data in first-to-last order.
  Because each call specifies only a single vertex, it is possible to
  explicitly specify per-primitive attributes, such as a single	normal per
  individual triangle.

Notes

  glArrayElementEXT may	be included in display lists. If glArrayElementEXT is
  entered into a display list, the necessary array data	(determined by the
  array	pointers and enables) is also entered into the display list.  Because
  the array pointers and enables are client side state,	their values affect
  display lists	when the lists are created, not	when the lists are executed.

  Static array data may	be read	and cached by the implementation at any	time.
  If static array elements are modified	and the	arrays are not respecified,
  the results of any subsequent	calls to glArrayElementEXT are undefined.

  glArrayElementEXT executes even if GL_VERTEX_ARRAY_EXT is not	enabled.  No
  drawing occurs in this case, but the attributes corresponding	to enabled
  arrays are modified.

  Although it is not an	error to respecify an array between the	execution of
  glBegin and the corresponding	execution of glEnd, the	result of such
  respecification is undefined.

  glArrayElementEXT is part of the EXT_vertex_array extension, not
  part of the core GL command set. If "GL_EXT_vertex_array" is
  included in the string returned by glGetString, when called with argument
  GL_EXTENSIONS, extension EXT_vertex_array is supported.

See Also

  glColorPointerEXT, glDrawArraysEXT, glEdgeFlagPointerEXT, glGetPointervEXT,
  glIndexPointerEXT, glNormalPointerEXT, glTexCoordPointerEXT,
  glVertexPointerEXT

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glBegin.3gl

Name

  glBegin, glEnd - delimit the vertices	of a primitive or a group of like
  primitives

C Specification

  void glBegin(	GLenum mode )

Parameters

  mode	Specifies the primitive	or primitives that will	be created from
    vertices presented between glBegin and the subsequent glEnd.  Ten
    symbolic constants are accepted: GL_POINTS, GL_LINES, GL_LINE_STRIP,
    GL_LINE_LOOP, GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN,
    GL_QUADS, GL_QUAD_STRIP, and GL_POLYGON.

C Specification

  void glEnd( void )

Description

  glBegin and glEnd delimit the	vertices that define a primitive or a group
  of like primitives.  glBegin accepts a single	argument that specifies	which
  of ten ways the vertices are interpreted.  Taking n as an integer count
  starting at one, and N as the	total number of	vertices specified, the
  interpretations are as follows:

  GL_POINTS Treats each	vertex as a single point.  Vertex n defines point n.
        N points are drawn.

  GL_LINES  Treates each pair of vertices as an	independent line segment.
        Vertices 2n-1 and 2n define	line n.	 N/2 lines are drawn.

  GL_LINE_STRIP
        Draws a connected group of line segments from the first vertex to
        the	last.  Vertices	n and n+1 define line n.  N-1 lines are
        drawn.

  GL_LINE_LOOP
        Draws a connected group of line segments from the first vertex to
        the	last, then back	to the first.  Vertices	n and n+1 define line
        n.	The last line, however,	is defined by vertices N and 1.	 N
        lines are drawn.

  GL_TRIANGLES
        Treates each triplet of vertices as	an independent triangle.
        Vertices 3n-2, 3n-1, and 3n	define triangle	n.  N/3	triangles are
        drawn.

  GL_TRIANGLE_STRIP
        Draws a connected group of triangles.  One triangle	is defined
        for	each vertex presented after the	first two vertices.  For odd
        n, vertices	n, n+1,	and n+2	define triangle	n.  For	even n,
        vertices n+1, n, and n+2 define triangle n.	 N-2 triangles are
        drawn.

  GL_TRIANGLE_FAN
        Draws a connected group of triangles.  One triangle	is defined
        for	each vertex presented after the	first two vertices.  Vertices
        1, n+1, and	n+2 define triangle n.	N-2 triangles are drawn.

  GL_QUADS  Treats each	group of four vertices as an independent
        quadrilateral.  Vertices 4n-3, 4n-2, 4n-1, and 4n define
        quadrilateral n.  N/4 quadrilaterals are drawn.

  GL_QUAD_STRIP
        Draws a connected group of quadrilaterals.	One quadrilateral is
        defined for	each pair of vertices presented	after the first	pair.
        Vertices 2n-1, 2n, 2n+2, and 2n+1 define quadrilateral n.  N/2-1
        quadrilaterals are drawn.  Note that the order in which vertices
        are	used to	construct a quadrilateral from strip data is
        different from that	used with independent data.

  GL_POLYGON
        Draws a single, convex polygon.  Vertices 1	through	N define this
        polygon.

  Only a subset	of GL commands can be used between glBegin and glEnd.  The
  commands are glVertex, glColor, glIndex, glNormal, glTexCoord, glEvalCoord,
  glEvalPoint, glMaterial, and glEdgeFlag.  Also, it is	acceptable to use
  glCallList or	glCallLists to execute display lists that include only the
  preceding commands.  If any other GL command is executed between glBegin
  and glEnd, the error flag is set and the command is ignored.

  Regardless of	the value chosen for mode, there is no limit to	the number of
  vertices that	can be defined between glBegin and glEnd.  Lines, triangles,
  quadrilaterals, and polygons that are	incompletely specified are not drawn.
  Incomplete specification results when	either too few vertices	are provided
  to specify even a single primitive or	when an	incorrect multiple of
  vertices is specified. The incomplete	primitive is ignored; the rest are
  drawn.

  The minimum specification of vertices	for each primitive is as follows: 1
  for a	point, 2 for a line, 3 for a triangle, 4 for a quadrilateral, and 3
  for a	polygon.  Modes	that require a certain multiple	of vertices are
  GL_LINES (2),	GL_TRIANGLES (3), GL_QUADS (4),	and GL_QUAD_STRIP (2).

Errors

  GL_INVALID_ENUM is generated if mode is set to an unaccepted value.

  GL_INVALID_OPERATION is generated if a command other than glVertex,
  glColor, glIndex, glNormal, glTexCoord, glEvalCoord, glEvalPoint,
  glMaterial, glEdgeFlag, glCallList, or glCallLists is	executed between
  glBegin and the corresponding	glEnd.

  GL_INVALID_OPERATION is generated if a glBegin is executed between a
  glBegin and the corresponding	execution of glEnd.

  GL_INVALID_OPERATION is generated if glEnd is	executed without being
  preceded by a	glBegin.

See Also

  glCallList, glCallLists, glColor, glEdgeFlag,	glEvalCoord, glEvalPoint,
  glIndex, glMaterial, glNormal, glTexCoord, glVertex

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glBitmap.3gl

Name

  glBitmap - draw a bitmap

C Specification

  void glBitmap( GLsizei width,
         GLsizei height,
         GLfloat xorig,
         GLfloat yorig,
         GLfloat xmove,
         GLfloat ymove,
         const GLubyte *bitmap )

Parameters

  width, height	Specify	the pixel width	and height of the bitmap image.

  xorig, yorig	Specify	the location of	the origin in the bitmap image.	 The
        origin is measured from	the lower left corner of the bitmap,
        with right and up being	the positive axes.

  xmove, ymove	Specify	the x and y offsets to be added	to the current raster
        position after the bitmap is drawn.

  bitmap	Specifies the address of the bitmap image.

Description

  A bitmap is a	binary image.  When drawn, the bitmap is positioned relative
  to the current raster	position, and frame buffer pixels corresponding	to
  ones in the bitmap are written using the current raster color	or index.
  Frame	buffer pixels corresponding to zeros in	the bitmap are not modified.

  glBitmap takes seven arguments.  The first pair specify the width and
  height of the	bitmap image.  The second pair specify the location of the
  bitmap origin	relative to the	lower left corner of the bitmap	image.	The
  third	pair of	arguments specify x and	y offsets to be	added to the current
  raster position after	the bitmap has been drawn.  The	final argument is a
  pointer to the bitmap	image itself.

  The bitmap image is interpreted like image data for the glDrawPixels
  command, with	width and height corresponding to the width and	height
  arguments of that command, and with type set to GL_BITMAP and	format set to
  GL_COLOR_INDEX.  Modes specified using glPixelStore affect the
  interpretation of bitmap image data; modes specified using glPixelTransfer
  do not.

  If the current raster	position is invalid, glBitmap is ignored.  Otherwise,
  the lower left corner	of the bitmap image is positioned at the window
  coordinates

                   x  = floor (x - x )
                w           r   o

                   y  = floor (y - y )
                w           r   o

  where	(x ,y )	is the raster position and (x ,y ) is the bitmap origin.
          r  r                               o  o

  Fragments are then generated for each pixel corresponding to a one in	the
  bitmap image.	 These fragments are generated using the current raster	z
  coordinate, color or color index, and	current	raster texture coordinates.
  They are then	treated	just as	if they	had been generated by a	point, line,
  or polygon, including	texture	mapping, fogging, and all per-fragment
  operations such as alpha and depth testing.

  After	the bitmap has been drawn, the x and y coordinates of the current
  raster position are offset by	xmove and ymove.  No change is made to the z
  coordinate of	the current raster position, or	to the current raster color,
  index, or texture coordinates.

Errors

  GL_INVALID_VALUE is generated	if width or height is negative.

  GL_INVALID_OPERATION is generated if glBitmap	is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_CURRENT_RASTER_POSITION
  glGet	with argument GL_CURRENT_RASTER_COLOR
  glGet	with argument GL_CURRENT_RASTER_INDEX
  glGet	with argument GL_CURRENT_RASTER_TEXTURE_COORDS
  glGet	with argument GL_CURRENT_RASTER_POSITION_VALID

See Also

  glDrawPixels,	glRasterPos, glPixelStore, glPixelTransfer

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glBlendFunc.3gl

Name

  glBlendFunc -	specify	pixel arithmetic

C Specification

  void glBlendFunc( GLenum sfactor,
            GLenum dfactor )

Parameters

  sfactor  Specifies how the red, green, blue, and alpha source blending
       factors are computed.  Nine symbolic	constants are accepted:
       GL_ZERO, GL_ONE, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR,
       GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA,
       GL_ONE_MINUS_DST_ALPHA, and GL_SRC_ALPHA_SATURATE.

  dfactor  Specifies how the red, green, blue, and alpha destination blending
       factors are computed.  Eight	symbolic constants are accepted:
       GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR,
       GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, and
       GL_ONE_MINUS_DST_ALPHA.

Description

  In RGB mode, pixels can be drawn using a function that blends	the incoming
  (source) RGBA	values with the	RGBA values that are already in	the frame
  buffer (the destination values).  By default,	blending is disabled.  Use
  glEnable and glDisable with argument GL_BLEND	to enable and disable
  blending.

  glBlendFunc defines the operation of blending	when it	is enabled.  sfactor
  specifies which of nine methods is used to scale the source color
  components.  dfactor specifies which of eight	methods	is used	to scale the
  destination color components.	 The eleven possible methods are described in
  the table below.  Each method	defines	four scale factors, one	each for red,
  green, blue, and alpha.

  In the table and in subsequent equations, source and destination color
  components are referred to as	(R ,G ,B ,A ) and (R ,G	,B ,A ).  They are
                                  s  s  s  s        d  d  d  d
  understood to	have integer values between zero and (k ,k ,k ,k ), where
                               R  G  B	A
                      mc
                    k  = 2   - 1
                                     c

  and (mR,mG,mB,mA) is the number of red, green, blue, and alpha bitplanes.

  Source and destination scale factors are referred to as (s ,s ,s ,s )	and
                                                            R  G  B  A
  (d ,d ,d ,d ).  The scale factors described in the table, denoted
    R  G  B  A
  (f ,f ,f ,f ), represent either source or destination	factors.  All scale
    R  G  B  A
  factors have range [0,1].

    -----------------------------------------------------------------------
    |     parameter         |            (f ,  f ,  f ,  f )              |
    |                       |              R    G    B    A               |
    -----------------------------------------------------------------------
    |	           	    |		                 		  |
    |	    GL_ZERO	    |		     (0, 0, 0, 0)		  |
    |	           	    |		                 		  |
    |	     GL_ONE	    |		     (1, 1, 1, 1)		  |
    |	           	    |		                 		  |
    |	  GL_SRC_COLOR	    |	     (R	/k , G /k , B /k , A /k	)	  |
    |	           	    |	       s  R   s  G   s  B   s  A	  |
    |	           	    |		                 		  |
    |GL_ONE_MINUS_SRC_COLOR | (1, 1, 1, 1) - (R	/k , G /k , B /k , A /k	) |
    |	           	    |	               s  R   s	 G   s  B   s  A  |
    |	           	    |		                 		  |
    |	  GL_DST_COLOR	    |	     (R	/k , G /k , B /k , A /k	)	  |
    |	           	    |	       d  R   d  G   d  B   d  A	  |
    |	           	    |		                 		  |
    |GL_ONE_MINUS_DST_COLOR | (1, 1, 1, 1) - (R	/k , G /k , B /k , A /k	) |
    |	           	    |	               d  R   d	 G   d  B   d  A  |
    |	           	    |		                 		  |
    |	  GL_SRC_ALPHA	    |	     (A	/k , A /k , A /k , A /k	)	  |
    |	           	    |	       s  R   s  G   s  B   s  A	  |
    |	           	    |		                 		  |
    |	           	    |		                 		  |
    |GL_ONE_MINUS_SRC_ALPHA | (1, 1, 1, 1) - (A	/k , A /k , A /k , A /k	) |
    |	           	    |	               s  R   s  G   s  B   s  A  |
    |	           	    |		                 		  |
    |	           	    |		                 		  |
    |	  GL_DST_ALPHA	    |	     (A	/k , A /k , A /k , A /k	)	  |
    |	           	    |	       d  R   d  G   d  B   d  A	  |
    |	           	    |		                 		  |
    |GL_ONE_MINUS_DST_ALPHA | (1, 1, 1, 1) - (A	/k , A /k , A /k , A /k	) |
    |	           	    |	               d  R   d  G   d  B   d  A  |
    |	           	    |		                 		  |
    |	           	    |		                 		  |
    |GL_SRC_ALPHA_SATURATE  |		     (i,  i,  i,  1)              |
    |	           	    |		                 		  |
    -----------------------------------------------------------------------

  In the table,

                i =	min(A ,	k -A ) / k
                                     s   A  d     A

  To determine the blended RGBA	values of a pixel when drawing in RGB mode,
  the system uses the following	equations:

                  R  = min(k ,  R s +R d )
                               d        R    s R  d R

                  G  = min(k ,  G s +G d )
                               d        G    s G  d G

                  B  = min(k ,  B s +B d )
                               d        B    s B  d B

                  A  = min(k ,  A s +A d )
                               d        A    s A  d A

  Despite the apparent precision of the	above equations, blending arithmetic
  is not exactly specified, because blending operates with imprecise integer
  color	values.	 However, a blend factor that should be	equal to one is
  guaranteed not to modify its multiplicand, and a blend factor	equal to zero
  reduces its multiplicand to zero.  Thus, for example,	when sfactor is
  GL_SRC_ALPHA,	dfactor	is GL_ONE_MINUS_SRC_ALPHA, and A  is equal to k  the
                                                         s              A
  equations reduce to simple replacement:

                      R  = R
                                       d    s

                      G  = G
                                       d    s

                      B  = B
                                       d    s

                      A  = A
                                       d    s

Examples

  Transparency is best implemented using blend function	(GL_SRC_ALPHA,
  GL_ONE_MINUS_SRC_ALPHA) with primitives sorted from farthest to nearest.
  Note that this transparency calculation does not require the presence	of
  alpha	bitplanes in the frame buffer.

  Blend	function (GL_SRC_ALPHA,	GL_ONE_MINUS_SRC_ALPHA)	is also	useful for
  rendering antialiased	points and lines in arbitrary order.

  Polygon antialiasing is optimized using blend	function
  (GL_SRC_ALPHA_SATURATE, GL_ONE) with polygons	sorted from nearest to
  farthest.  (See the glEnable,	glDisable reference page and the
  GL_POLYGON_SMOOTH argument for information on	polygon	antialiasing.)
  Destination alpha bitplanes, which must be present for this blend function
  to operate correctly,	store the accumulated coverage.

Notes

  Incoming (source) alpha is correctly thought of as a material	opacity,
  ranging from 1.0 (K ), representing complete opacity,	to 0.0 (0),
                     A
  representing complete transparency.

  When more than one color buffer is enabled for drawing, blending is done
  separately for each enabled buffer, using for	destination color the
  contents of that buffer.  (See glDrawBuffer.)

  Blending affects only	RGB rendering.	It is ignored by color index
  renderers.

Errors

  GL_INVALID_ENUM is generated if either sfactor or dfactor is not an
  accepted value.

  GL_INVALID_OPERATION is generated if glBlendFunc is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_BLEND_SRC
  glGet	with argument GL_BLEND_DST
  glIsEnabled with argument GL_BLEND

See Also

  glAlphaFunc, glClear,	glDrawBuffer, glEnable,	glLogicOp, glStencilFunc

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


glCallList.3gl

Name

  glCallList - execute a display list

C Specification

  void glCallList( GLuint list )

Parameters

  list	Specifies the integer name of the display list to be executed.

Description

  glCallList causes the	named display list to be executed.  The	commands
  saved	in the display list are	executed in order, just	as if they were
  called without using a display list.	If list	has not	been defined as	a
  display list,	glCallList is ignored.

  glCallList can appear	inside a display list.	To avoid the possibility of
  infinite recursion resulting from display lists calling one another, a
  limit	is placed on the nesting level of display lists	during display-list
  execution.  This limit is at least 64, and it	depends	on the
  implementation.

  GL state is not saved	and restored across a call to glCallList.  Thus,
  changes made to GL state during the execution	of a display list remain
  after	execution of the display list is completed.  Use glPushAttrib,
  glPopAttrib, glPushMatrix, and glPopMatrix to	preserve GL state across
  glCallList calls.

Notes

  Display lists	can be executed	between	a call to glBegin and the
  corresponding	call to	glEnd, as long as the display list includes only
  commands that	are allowed in this interval.

Associated Gets

  glGet	with argument GL_MAX_LIST_NESTING
  glIsList

See Also

  glCallLists, glDeleteLists, glGenLists, glNewList, glPushAttrib,
  glPushMatrix

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glCallLists.3gl

Name

  glCallLists -	execute	a list of display lists

C Specification

  void glCallLists( GLsizei n,
            GLenum type,
            const GLvoid *lists	)

Parameters

  n	 Specifies the number of display lists to be executed.

  type	 Specifies the type of values in lists.	 Symbolic constants GL_BYTE,
     GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT,	GL_INT,
     GL_UNSIGNED_INT, GL_FLOAT, GL_2_BYTES,	GL_3_BYTES, and	GL_4_BYTES
     are accepted.

  lists	 Specifies the address of an array of name offsets in the display
     list.	The pointer type is void because the offsets can be bytes,
     shorts, ints, or floats, depending on the value of type.

Description

  glCallLists causes each display list in the list of names passed as lists
  to be	executed.  As a	result,	the commands saved in each display list	are
  executed in order, just as if	they were called without using a display
  list.	 Names of display lists	that have not been defined are ignored.

  glCallLists provides an efficient means for executing	display	lists.	type
  allows lists with various name formats to be accepted.  The formats are as
  follows:

  GL_BYTE		   lists is treated as an array	of signed bytes, each
               in the range	-128 through 127.

  GL_UNSIGNED_BYTE	   lists is treated as an array	of unsigned bytes,
               each	in the range 0 through 255.

  GL_SHORT		   lists is treated as an array	of signed two-byte
               integers, each in the range -32768 through 32767.

  GL_UNSIGNED_SHORT	   lists is treated as an array	of unsigned two-byte
               integers, each in the range 0 through 65535.

  GL_INT		   lists is treated as an array	of signed four-byte
               integers.

  GL_UNSIGNED_INT	   lists is treated as an array	of unsigned four-byte
               integers.

  GL_FLOAT		   lists is treated as an array	of four-byte
               floating-point values.

  GL_2_BYTES		   lists is treated as an array	of unsigned bytes.
               Each	pair of	bytes specifies	a single display-list
               name.  The value of the pair	is computed as 256
               times the unsigned value of the first byte plus
               the unsigned	value of the second byte.

  GL_3_BYTES		   lists is treated as an array	of unsigned bytes.
               Each	triplet	of bytes specifies a single display-
               list	name.  The value of the	triplet	is computed
               as 65536 times the unsigned value of	the first
               byte, plus 256 times	the unsigned value of the
               second byte,	plus the unsigned value	of the third
               byte.

  GL_4_BYTES		   lists is treated as an array	of unsigned bytes.
               Each	 quadruplet of bytes specifies a single
               display-list	name.  The value of the	quadruplet is
               computed as 16777216	times the unsigned value of
               the first byte, plus	65536 times the	unsigned
               value of the	second byte, plus 256 times the
               unsigned value of the third byte, plus the
               unsigned value of the fourth	byte.

  The list of display list names is not	null-terminated.  Rather, n specifies
  how many names are to	be taken from lists.

  An additional	level of indirection is	made available with the	glListBase
  command, which specifies an unsigned offset that is added to each display-
  list name specified in lists before that display list	is executed.

  glCallLists can appear inside	a display list.	 To avoid the possibility of
  infinite recursion resulting from display lists calling one another, a
  limit	is placed on the nesting level of display lists	during display-list
  execution.  This limit must be at least 64, and it depends on	the
  implementation.

  GL state is not saved	and restored across a call to glCallLists.  Thus,
  changes made to GL state during the execution	of the display lists remain
  after	execution is completed.	 Use glPushAttrib, glPopAttrib,	glPushMatrix,
  and glPopMatrix to preserve GL state across glCallLists calls.

Notes

  Display lists	can be executed	between	a call to glBegin and the
  corresponding	call to	glEnd, as long as the display list includes only
  commands that	are allowed in this interval.

Errors

  GL_INVALID_VALUE is generated	if n is	negative.

  GL_INVALID_ENUM is generated if type is not one of GL_BYTE,
  GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT,
  GL_FLOAT, GL_2_BYTES,	GL_3_BYTES, GL_4_BYTES.

Associated Gets

  glGet	with argument GL_LIST_BASE
  glGet	with argument GL_MAX_LIST_NESTING
  glIsList

See Also

  glCallList, glDeleteLists, glGenLists, glListBase, glNewList,	glPushAttrib,
  glPushMatrix

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glClear.3gl

Name

  glClear - clear buffers to preset values

C Specification

  void glClear(	GLbitfield mask	)

Parameters

  mask	Bitwise	OR of masks that indicate the buffers to be cleared.  The
    four masks are GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT,
    GL_ACCUM_BUFFER_BIT, and GL_STENCIL_BUFFER_BIT.

Description

  glClear sets the bitplane area of the	window to values previously selected
  by glClearColor, glClearIndex, glClearDepth, glClearStencil, and
  glClearAccum.	 Multiple color	buffers	can be cleared simultaneously by
  selecting more than one buffer at a time using glDrawBuffer.

  The pixel ownership test, the	scissor	test, dithering, and the buffer
  writemasks affect the	operation of glClear.  The scissor box bounds the
  cleared region.  Alpha function, blend function, logical operation,
  stenciling, texture mapping, and z-buffering are ignored by glClear.

  glClear takes	a single argument that is the bitwise OR of several values
  indicating which buffer is to	be cleared.

  The values are as follows:

  GL_COLOR_BUFFER_BIT	      Indicates	the buffers currently enabled for
                  color  writing.

  GL_DEPTH_BUFFER_BIT	      Indicates	the depth buffer.

  GL_ACCUM_BUFFER_BIT	      Indicates	the accumulation buffer.

  GL_STENCIL_BUFFER_BIT	      Indicates	the stencil buffer.

  The value to which each buffer is cleared depends on the setting of the
  clear	value for that buffer.

Notes

  If a buffer is not present, then a glClear directed at that buffer has no
  effect.

Errors

  GL_INVALID_VALUE is generated	if any bit other than the four defined bits
  is set in mask.

  GL_INVALID_OPERATION is generated if glClear is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_ACCUM_CLEAR_VALUE
  glGet	with argument GL_DEPTH_CLEAR_VALUE
  glGet	with argument GL_INDEX_CLEAR_VALUE
  glGet	with argument GL_COLOR_CLEAR_VALUE
  glGet	with argument GL_STENCIL_CLEAR_VALUE

See Also

  glClearAccum,	glClearColor, glClearDepth, glClearIndex, glClearStencil,
  glDrawBuffer,	glScissor

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glClearAccum.3gl

Name

  glClearAccum - specify clear values for the accumulation buffer

C Specification

  void glClearAccum( GLfloat red,
             GLfloat green,
             GLfloat blue,
             GLfloat alpha )

Parameters

  red, green, blue, alpha
            Specify	the red, green,	blue, and alpha	values used
            when the accumulation buffer is	cleared.  The default
            values are all zero.

Description

  glClearAccum specifies the red, green, blue, and alpha values	used by
  glClear to clear the accumulation buffer.

  Values specified by glClearAccum are clamped to the range [-1,1].

Errors

  GL_INVALID_OPERATION is generated if glClearAccum is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_ACCUM_CLEAR_VALUE

See Also

  glClear

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glClearColor.3gl

Name

  glClearColor - specify clear values for the color buffers

C Specification

  void glClearColor( GLclampf red,
             GLclampf green,
             GLclampf blue,
             GLclampf alpha )

Parameters

  red, green, blue, alpha
            Specify	the red, green,	blue, and alpha	values used
            when the color buffers are cleared.  The default
            values are all zero.

Description

  glClearColor specifies the red, green, blue, and alpha values	used by
  glClear to clear the color buffers.  Values specified	by glClearColor	are
  clamped to the range [0,1].

Errors

  GL_INVALID_OPERATION is generated if glClearColor is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_COLOR_CLEAR_VALUE

See Also

  glClear

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glClearDepth.3gl

Name

  glClearDepth - specify the clear value for the depth buffer

C Specification

  void glClearDepth( GLclampd depth )

Parameters

  depth	 Specifies the depth value used	when the depth buffer is cleared.

Description

  glClearDepth specifies the depth value used by glClear to clear the depth
  buffer.  Values specified by glClearDepth are	clamped	to the range [0,1].

Errors

  GL_INVALID_OPERATION is generated if glClearDepth is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_DEPTH_CLEAR_VALUE

See Also

  glClear

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glClearIndex.3gl

Name

  glClearIndex - specify the clear value for the color index buffers

C Specification

  void glClearIndex( GLfloat c )

Parameters

  c  Specifies the index used when the color index buffers are cleared.	 The
     default value is zero.

Description

  glClearIndex specifies the index used	by glClear to clear the	color index
  buffers.  c is not clamped.  Rather, c is converted to a fixed-point value
  with unspecified precision to	the right of the binary	point.	The integer
  part of this value is	then masked with 2m-1, where m is the number of	bits
  in a color index stored in the frame buffer.

Errors

  GL_INVALID_OPERATION is generated if glClearIndex is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_INDEX_CLEAR_VALUE
  glGet	with argument GL_INDEX_BITS

See Also

  glClear

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glClearStencil.3gl

Name

  glClearStencil - specify the clear value for the stencil buffer

C Specification

  void glClearStencil( GLint s )

Parameters

  s  Specifies the index used when the stencil buffer is cleared.  The
     default value is zero.

Description

  glClearStencil specifies the index used by glClear to	clear the stencil
  buffer.  s is	masked with 2m-1, where	m is the number	of bits	in the
  stencil buffer.

Errors

  GL_INVALID_OPERATION is generated if glClearStencil is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_STENCIL_CLEAR_VALUE
  glGet	with argument GL_STENCIL_BITS

See Also

  glClear

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glClipPlane.3gl

Name

  glClipPlane -	specify	a plane	against	which all geometry is clipped

C Specification

  void glClipPlane( GLenum plane,
            const GLdouble *equation )

Parameters

  plane	    Specifies which clipping plane is being positioned.	 Symbolic
        names of the form GL_CLIP_PLANEi, where i is an integer between 0
        and	GL_MAX_CLIP_PLANES -1, are accepted.

  equation  Specifies the address of an	array of four double-precision
        floating-point values.  These values are interpreted as a plane
        equation.

Description

  Geometry is always clipped against the boundaries of a six-plane frustum in
  x, y,	and z. glClipPlane allows the specification of additional planes, not
  necessarily perpendicular to the x, y, or z axis, against which all
  geometry is clipped.	Up to GL_MAX_CLIP_PLANES planes	can be specified,
  where	GL_MAX_CLIP_PLANES is at least six in all implementations.  Because
  the resulting	clipping region	is the intersection of the defined half-
  spaces, it is	always convex.

  glClipPlane specifies	a half-space using a four-component plane equation.
  When glClipPlane is called, equation is transformed by the inverse of	the
  modelview matrix and stored in the resulting eye coordinates.	 Subsequent
  changes to the modelview matrix have no effect on the	stored plane-equation
  components.  If the dot product of the eye coordinates of a vertex with the
  stored plane equation	components is positive or zero,	the vertex is in with
  respect to that clipping plane.  Otherwise, it is out.

  Clipping planes are enabled and disabled with	glEnable and glDisable,	and
  called with the argument GL_CLIP_PLANEi, where i is the plane	number.

  By default, all clipping planes are defined as (0,0,0,0) in eye coordinates
  and are disabled.

Notes

  It is	always the case	that GL_CLIP_PLANEi = GL_CLIP_PLANE0 + i.

Errors

  GL_INVALID_ENUM is generated if plane	is not an accepted value.

  GL_INVALID_OPERATION is generated if glClipPlane is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGetClipPlane
  glIsEnabled with argument GL_CLIP_PLANEi

See Also

  glEnable

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glColor.3gl

Name

  glColor3b, glColor3d,	glColor3f, glColor3i, glColor3s, glColor3ub,
  glColor3ui, glColor3us, glColor4b, glColor4d,	glColor4f, glColor4i,
  glColor4s, glColor4ub, glColor4ui, glColor4us, glColor3bv, glColor3dv,
  glColor3fv, glColor3iv, glColor3sv, glColor3ubv, glColor3uiv,	glColor3usv,
  glColor4bv, glColor4dv, glColor4fv, glColor4iv, glColor4sv, glColor4ubv,
  glColor4uiv, glColor4usv - set the current color

C Specification

  void glColor3b( GLbyte red,
          GLbyte green,
          GLbyte blue )
  void glColor3d( GLdouble red,
          GLdouble green,
          GLdouble blue	)
  void glColor3f( GLfloat red,
          GLfloat green,
          GLfloat blue )
  void glColor3i( GLint	red,
          GLint	green,
          GLint	blue )
  void glColor3s( GLshort red,
          GLshort green,
          GLshort blue )
  void glColor3ub( GLubyte red,
           GLubyte green,
           GLubyte blue	)
  void glColor3ui( GLuint red,
           GLuint green,
           GLuint blue )
  void glColor3us( GLushort red,
           GLushort green,
           GLushort blue )
  void glColor4b( GLbyte red,
          GLbyte green,
          GLbyte blue,
          GLbyte alpha )
  void glColor4d( GLdouble red,
          GLdouble green,
          GLdouble blue,
          GLdouble alpha )
  void glColor4f( GLfloat red,
          GLfloat green,
          GLfloat blue,
          GLfloat alpha	)
  void glColor4i( GLint	red,
          GLint	green,
          GLint	blue,
          GLint	alpha )
  void glColor4s( GLshort red,
          GLshort green,
          GLshort blue,
          GLshort alpha	)
  void glColor4ub( GLubyte red,
           GLubyte green,
           GLubyte blue,
           GLubyte alpha )
  void glColor4ui( GLuint red,
           GLuint green,
           GLuint blue,
           GLuint alpha	)
  void glColor4us( GLushort red,
           GLushort green,
           GLushort blue,
           GLushort alpha )

Parameters

  red, green, blue
          Specify new red, green, and blue values for the current
          color.

  alpha		  Specifies a new alpha	value for the current color.
          Included only	in the four-argument glColor4 command.

C Specification

  void glColor3bv( const GLbyte	*v )
  void glColor3dv( const GLdouble *v )
  void glColor3fv( const GLfloat *v )
  void glColor3iv( const GLint *v )
  void glColor3sv( const GLshort *v )
  void glColor3ubv( const GLubyte *v )
  void glColor3uiv( const GLuint *v )
  void glColor3usv( const GLushort *v )
  void glColor4bv( const GLbyte	*v )
  void glColor4dv( const GLdouble *v )
  void glColor4fv( const GLfloat *v )
  void glColor4iv( const GLint *v )
  void glColor4sv( const GLshort *v )
  void glColor4ubv( const GLubyte *v )
  void glColor4uiv( const GLuint *v )
  void glColor4usv( const GLushort *v )

Parameters

  v    Specifies a pointer to an array that contains red, green, blue, and
       (sometimes) alpha values.

Description

  The GL stores	both a current single-valued color index and a current four-
  valued RGBA color.  glColor sets a new four-valued RGBA color.  glColor has
  two major variants: glColor3 and glColor4.  glColor3 variants	specify	new
  red, green, and blue values explicitly, and set the current alpha value to
  1.0 implicitly.  glColor4 variants specify all four color components
  explicitly.

  glColor3b, glColor4b,	glColor3s, glColor4s, glColor3i, and glColor4i take
  three	or four	signed byte, short, or long integers as	arguments.  When v is
  appended to the name,	the color commands can take a pointer to an array of
  such values.

  Current color	values are stored in floating-point format, with unspecified
  mantissa and exponent	sizes.	Unsigned integer color components, when
  specified, are linearly mapped to floating-point values such that the
  largest representable	value maps to 1.0 (full	intensity), and	zero maps to
  0.0 (zero intensity).	 Signed	integer	color components, when specified, are
  linearly mapped to floating-point values such	that the most positive
  representable	value maps to 1.0, and the most	negative representable value
  maps to -1.0.	 Floating-point	values are mapped directly.

  Neither floating-point nor signed integer values are clamped to the range
  [0,1]	before updating	the current color.  However, color components are
  clamped to this range	before they are	interpolated or	written	into a color
  buffer.

Notes

  The current color can	be updated at any time.	 In particular,	glColor	can
  be called between a call to glBegin and the corresponding call to glEnd.

Associated Gets

  glGet	with argument GL_CURRENT_COLOR
  glGet	with argument GL_RGBA_MODE

See Also

  glIndex

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glColorMask.3gl

Name

  glColorMask -	enable and disable writing of frame buffer color components

C Specification

  void glColorMask( GLboolean red,
            GLboolean green,
            GLboolean blue,
            GLboolean alpha )

Parameters

  red, green, blue, alpha
            Specify	whether	red, green, blue, and alpha can	or
            cannot be written into the frame buffer.  The default
            values are all GL_TRUE,	indicating that	the color
            components can be written.

Description

  glColorMask specifies	whether	the individual color components	in the frame
  buffer can or	cannot be written.  If red is GL_FALSE,	for example, no
  change is made to the	red component of any pixel in any of the color
  buffers, regardless of the drawing operation attempted.

  Changes to individual	bits of	components cannot be controlled.  Rather,
  changes are either enabled or	disabled for entire color components.

Errors

  GL_INVALID_OPERATION is generated if glColorMask is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_COLOR_WRITEMASK
  glGet	with argument GL_RGBA_MODE

See Also

  glColor, glIndex, glIndexMask, glDepthMask, glStencilMask

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glColorMaterial.3gl

Name

  glColorMaterial - cause a material color to track the	current	color

C Specification

  void glColorMaterial(	GLenum face,
            GLenum mode )

Parameters

  face	Specifies whether front, back, or both front and back material
    parameters should track	the current color.  Accepted values are
    GL_FRONT, GL_BACK, and GL_FRONT_AND_BACK.  The default value is
    GL_FRONT_AND_BACK.

  mode	Specifies which	of several material parameters track the current
    color.	Accepted values	are GL_EMISSION, GL_AMBIENT, GL_DIFFUSE,
    GL_SPECULAR, and GL_AMBIENT_AND_DIFFUSE.  The default value is
    GL_AMBIENT_AND_DIFFUSE.

Description

  glColorMaterial specifies which material parameters track the	current
  color.  When GL_COLOR_MATERIAL is enabled, the material parameter or
  parameters specified by mode,	of the material	or materials specified by
  face,	track the current color	at all times.	GL_COLOR_MATERIAL is enabled
  and disabled using the commands glEnable and glDisable, called with
  GL_COLOR_MATERIAL as their argument.	By default, it is disabled.

Notes

  glColorMaterial allows a subset of material parameters to be changed for
  each vertex using only the glColor command, without calling glMaterial.  If
  only such a subset of	parameters is to be specified for each vertex,
  glColorMaterial is preferred over calling glMaterial.

  Call glColorMaterial before enabling the GL_COLOR_MATERIAL.

Errors

  GL_INVALID_ENUM is generated if face or mode is not an accepted value.

  GL_INVALID_OPERATION is generated if glColorMaterial is executed between
  the execution	of glBegin and the corresponding execution of glEnd.

Associated Gets

  glIsEnabled with argument GL_COLOR_MATERIAL
  glGet	with argument GL_COLOR_MATERIAL_PARAMETER
  glGet	with argument GL_COLOR_MATERIAL_FACE

See Also

  glColor, glEnable, glLight, glLightModel, glMaterial

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glColorPointerEXT.3gl

Name

  glColorPointerEXT - define a array of	colors

C Specification

  void glColorPointerEXT( GLint	size,
              GLenum type,
              GLsizei stride,
              GLsizei count,
              const	GLvoid *pointer	)

Parameters

  size	   Specifies the number	of components per color. It must be 3 or 4.

  type	   Specifies the data type of each color component in the array.
       Symbolic constants GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT,
       GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FLOAT, or
       GL_DOUBLE_EXT, are accepted.

  stride   Specifies the byte offset between consecutive colors.  If stride
       is zero the colors are understood to	be tightly packed in the
       array.

  count	   Specifies the number	of colors, counting from the first, that are
       static.

  pointer  Specifies a pointer to the first component of the first color
       element in the array.

Description

  glColorPointerEXT specifies the location and data format of an array of
  color components to use when rendering using the vertex array extension.
  size specifies the number of components per color, and must be 3 or 4.
  type specifies the data type of each color component and stride gives the
  byte stride from one color to the next allowing vertexes and attributes to
  be packed into a single array or stored in separate arrays. (Single-array
  storage may be more efficient on some implementations.) count indicates
  the number of array elements (counting from the first) that are static.
  Static elements may be modified by the application, but once they are
  modified, the application must explicitly respecify the array before using
  it for any rendering. When a color array is specified, size, type, stride,
  count and pointer are saved as client-side state, and static array elements
  may be cached by the implementation.

  The color array is enabled and disabled using glEnable and glDisable with
  the argument GL_COLOR_ARRAY_EXT. If enabled, the color array is used when
  glDrawArraysEXT or glArrayElementEXT is called.

  Use glDrawArraysEXT to define a sequence of primitives (all of the same
  type) from pre-specified vertex and vertex attribute arrays. Use
  glArrayElementEXT to specify primitives by indexing vertexes and vertex
  attributes.

Notes

  Non-static array elements are not accessed until glArrayElementEXT or
  glDrawArraysEXT is executed.

  By default the color array is disabled and it won't be accessed when
  glArrayElementEXT or glDrawArraysEXT is called.

  Although it is not an error to call glColorPointerEXT between the
  execution of glBegin and the corresponding execution of glEnd, the results
  are undefined.

  glColorPointerEXT will typically be implemented on the client	side with no
  protocol.

  Since	the color array	parameters are client side state, they are not saved
  or restored by glPushAttrib and glPopAttrib.

  glColorPointerEXT commands are not entered into display lists.

  glColorPointerEXT is part of the EXT_vertex_array extension, not
  part of the core GL command set. If "GL_EXT_vertex_array" is
  included in the string returned by glGetString, when called with argument
  GL_EXTENSIONS, extension EXT_vertex_array is supported.

Errors

  GL_INVALID_VALUE is generated	if size	is not 3 or 4.

  GL_INVALID_ENUM is generated if type is not an accepted value.

  GL_INVALID_VALUE is generated	if stride or count is negative.

Associated Gets

  glIsEnabled with argument GL_COLOR_ARRAY_EXT
  glGet	with argument GL_COLOR_ARRAY_SIZE_EXT
  glGet	with argument GL_COLOR_ARRAY_TYPE_EXT
  glGet	with argument GL_COLOR_ARRAY_STRIDE_EXT
  glGet	with argument GL_COLOR_ARRAY_COUNT_EXT
  glGetPointervEXT with	argument GL_COLOR_ARRAY_POINTER_EXT

See Also

  glArrayElementEXT, glDrawArraysEXT, glEdgeFlagPointerEXT, glGetPointervEXT,
  glIndexPointerEXT, glNormalPointerEXT, glTexCoordPointerEXT,
  glVertexPointerEXT, glEnable

Introduction | Alphabetic | Specification

Last Edited: Fri Feb 24, 1995

AFV


glCopyPixels.3gl

Name

  glCopyPixels - copy pixels in	the frame buffer

C Specification

  void glCopyPixels( GLint x,
             GLint y,
             GLsizei width,
             GLsizei height,
             GLenum type )

Parameters

  x, y Specify the window coordinates of the lower left	corner of the
       rectangular region of pixels to be copied.

  width, height
       Specify the dimensions of the rectangular region	of pixels to be
       copied.	Both must be nonnegative.

  type Specifies whether color values, depth values, or	stencil	values are to
       be copied.  Symbolic constants GL_COLOR,	GL_DEPTH, and GL_STENCIL are
       accepted.

Description

  glCopyPixels copies a	screen-aligned rectangle of pixels from	the specified
  frame	buffer location	to a region relative to	the current raster position.
  Its operation	is well	defined	only if	the entire pixel source	region is
  within the exposed portion of	the window.  Results of	copies from outside
  the window, or from regions of the window that are not exposed, are
  hardware dependent and undefined.

  x and	y specify the window coordinates of the	lower left corner of the
  rectangular region to	be copied.  width and height specify the dimensions
  of the rectangular region to be copied.  Both	width and height must not be
  negative.

  Several parameters control the processing of the pixel data while it is
  being	copied.	 These parameters are set with three commands:
  glPixelTransfer, glPixelMap, and glPixelZoom.	 This reference	page
  describes the	effects	on glCopyPixels	of most, but not all, of the
  parameters specified by these	three commands.

  glCopyPixels copies values from each pixel with the lower left-hand corner
  at (x	+ i, y + j) for	0<=i<width  and 0<=j<height.  This pixel is said to be
  the ith pixel	in the jth row.	Pixels are copied in row order from the
  lowest to the	highest	row, left to right in each row.

  type specifies whether color,	depth, or stencil data is to be	copied.	 The
  details of the transfer for each data	type are as follows:

  GL_COLOR	 Indices or RGBA colors	are read from the buffer currently
         specified as the read source buffer (see glReadBuffer).  If
         the GL	is in color index mode,	each index that	is read	from
         this buffer is	converted to a fixed-point format with an
         unspecified number of bits to the right of the	binary point.
         Each index is then shifted left by GL_INDEX_SHIFT bits, and
         added to GL_INDEX_OFFSET.  If GL_INDEX_SHIFT is negative,
         the shift is to the right.  In	either case, zero bits fill
         otherwise unspecified bit locations in	the result.  If
         GL_MAP_COLOR is true, the index is replaced with the value
         that it references in lookup table GL_PIXEL_MAP_I_TO_I.
         Whether the lookup replacement	of the index is	done or	not,
                                   b
         the integer part of the index is then ANDed with 2 -1,	where
         b is the number of bits in a color index buffer.

         If the	GL is in RGBA mode, the	red, green, blue, and alpha
         components of each pixel that is read are converted to	an
         internal floating-point format	with unspecified precision.
         The conversion	maps the largest representable component
         value to 1.0, and component value zero	to 0.0.	 The
         resulting floating-point color	values are then	multiplied by
         GL_c_SCALE and	added to GL_c_BIAS, where c is RED, GREEN,
         BLUE, and ALPHA for the respective color components.  The
         results are clamped to	the range [0,1].  If GL_MAP_COLOR is
         true, each color component is scaled by the size of lookup
         table GL_PIXEL_MAP_c_TO_c, then replaced by the value that
         it references in that table.  c is R, G, B, or	A,
         respectively.

         The resulting indices or RGBA colors are then converted to
         fragments by attaching	the current raster position z
         coordinate and	texture	coordinates to each pixel, then
         assigning window coordinates (x +i,y +j), where (x ,y ) is
                                                r    r             r  r
         the current raster position, and the pixel was	the ith	pixel
         in the	jth row.  These	pixel fragments	are then treated just
         like the fragments generated by rasterizing points, lines,
         or polygons.  Texture mapping,	fog, and all the fragment
         operations are	applied	before the fragments are written to
         the frame buffer.

  GL_DEPTH	 Depth values are read from the	depth buffer and converted
         directly to an	internal floating-point	format with
         unspecified precision.	 The resulting floating-point depth
         value is then multiplied by GL_DEPTH_SCALE and	added to
         GL_DEPTH_BIAS.	 The result is clamped to the range [0,1].

         The resulting depth components	are then converted to
         fragments by attaching	the current raster position color or
         color index and texture coordinates to	each pixel, then
         assigning window coordinates (x +i,y +j), where (x ,y ) is
                                                r    r             r  r
         the current raster position, and the pixel was	the ith	pixel
         in the	jth row.  These	pixel fragments	are then treated just
         like the fragments generated by rasterizing points, lines,
         or polygons.  Texture mapping,	fog, and all the fragment
         operations are	applied	before the fragments are written to
         the frame buffer.

  GL_STENCIL	 Stencil indices are read from the stencil buffer and
         converted to an internal fixed-point format with an
         unspecified number of bits to the right of the	binary point.
         Each fixed-point index	is then	shifted	left by
         GL_INDEX_SHIFT	bits, and added	to GL_INDEX_OFFSET.  If
         GL_INDEX_SHIFT	is negative, the shift is to the right.	 In
         either	case, zero bits	fill otherwise unspecified bit
         locations in the result.  If GL_MAP_STENCIL is	true, the
         index is replaced with	the value that it references in
         lookup	table GL_PIXEL_MAP_S_TO_S.  Whether the	lookup
         replacement of	the index is done or not, the integer part of
                                               b
         the index is then ANDed with 2 -1, where b is the number of
         bits in the stencil buffer.  The resulting stencil indices
         are then written to the stencil buffer	such that the index
         read from the ith location of the jth row is written to
         location (x +i,y +j), where (x	,y ) is	the current raster
                            r    r             r  r
         position.  Only the pixel ownership test, the scissor test,
         and the stencil writemask affect these	writes.

  The rasterization described thus far assumes pixel zoom factors of 1.0.  If
  glPixelZoom is used to change	the x and y pixel zoom factors,	pixels are
  converted to fragments as follows.  If (x , y	) is the current raster
                                           r   r
  position, and	a given	pixel is in the	ith location in	the jth	row of the
  source pixel rectangle, then fragments are generated for pixels whose
  centers are in the rectangle with corners at

                (x +zoom i,	y +zoom	j)
                  r	    x	 r     y

                      and

            (x +zoom (i+1),	y +zoom	(j+1))
              r	x	 r     y

  where	zoom  is the value of GL_ZOOM_X	and zoom  is the value of GL_ZOOM_Y.
        x					y

Examples

  To copy the color pixel in the lower left corner of the window to the
  current raster position, use glCopyPixels(0, 0, 1, 1,	GL_COLOR);

Notes

  Modes	specified by glPixelStore have no effect on the	operation of
  glCopyPixels.

Errors

  GL_INVALID_ENUM is generated if type is not an accepted value.

  GL_INVALID_VALUE is generated	if either width	or height is negative.

  GL_INVALID_OPERATION is generated if type is GL_DEPTH	and there is no	depth
  buffer.

  GL_INVALID_OPERATION is generated if type is GL_STENCIL and there is no
  stencil buffer.

  GL_INVALID_OPERATION is generated if glCopyPixels is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_CURRENT_RASTER_POSITION
  glGet	with argument GL_CURRENT_RASTER_POSITION_VALID

See Also

  glDepthFunc, glDrawBuffer, glDrawPixels, glPixelMap, glPixelTransfer,
  glPixelZoom, glRasterPos, glReadBuffer, glReadPixels,	glStencilFunc

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glCullFace.3gl

Name

  glCullFace - specify whether front- or back-facing facets can	be culled

C Specification

  void glCullFace( GLenum mode )

Parameters

  mode	Specifies whether front- or back-facing	facets are candidates for
    culling.  Symbolic constants GL_FRONT, GL_BACK,	and GL_FRONT_AND_BACK
    are accepted.  The default value is GL_BACK.

Description

  glCullFace specifies whether front- or back-facing facets are	culled (as
  specified by mode) when facet	culling	is enabled.  Facet culling is enabled
  and disabled using the glEnable and glDisable	commands with the argument
  GL_CULL_FACE.	 Facets	include	triangles, quadrilaterals, polygons, and
  rectangles.

  glFrontFace specifies	which of the clockwise and counterclockwise facets
  are front-facing and back-facing.  See glFrontFace.

Notes

  If the mode is GL_FRONT_AND_BACK then	no facets are drawn, but other
  primitives such as points and	lines are.

Errors

  GL_INVALID_ENUM is generated if mode is not an accepted value.

  GL_INVALID_OPERATION is generated if glCullFace is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glIsEnabled with argument GL_CULL_FACE
  glGet	with argument GL_CULL_FACE_MODE

See Also

  glEnable, glFrontFace

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glDeleteLists.3gl

Name

  glDeleteLists	- delete a contiguous group of display lists

C Specification

  void glDeleteLists( GLuint list,
              GLsizei range )

Parameters

  list	 Specifies the integer name of the first display list to delete.

  range	 Specifies the number of display lists to delete.

Description

  glDeleteLists	causes a contiguous group of display lists to be deleted.
  list is the name of the first	display	list to	be deleted, and	range is the
  number of display lists to delete.  All display lists	d with list <= d <=
  list + range - 1 are deleted.

  All storage locations	allocated to the specified display lists are freed,
  and the names	are available for reuse	at a later time.  Names	within the
  range	that do	not have an associated display list are	ignored.  If range is
  zero,	nothing	happens.

Errors

  GL_INVALID_VALUE is generated	if range is negative.

  GL_INVALID_OPERATION is generated if glDeleteLists is	executed between the
  execution of glBegin and the corresponding execution of glEnd.

See Also

  glCallList, glCallLists, glGenLists, glIsList, glNewList

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glDepthFunc.3gl

Name

  glDepthFunc -	specify	the value used for depth buffer	comparisons

C Specification

  void glDepthFunc( GLenum func	)

Parameters

  func	Specifies the depth comparison function.  Symbolic constants
    GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL,	GL_GREATER, GL_NOTEQUAL,
    GL_GEQUAL, and GL_ALWAYS are accepted.	The default value is GL_LESS.

Description

  glDepthFunc specifies	the function used to compare each incoming pixel z
  value	with the z value present in the	depth buffer.  The comparison is
  performed only if depth testing is enabled.  (See glEnable and glDisable of
  GL_DEPTH_TEST.)

  func specifies the conditions	under which the	pixel will be drawn.  The
  comparison functions are as follows:

  GL_NEVER	      Never passes.

  GL_LESS	      Passes if	the incoming z value is	less than the stored
              z	value.

  GL_EQUAL	      Passes if	the incoming z value is	equal to the stored z
              value.

  GL_LEQUAL	      Passes if	the incoming z value is	less than or equal to
              the stored z value.

  GL_GREATER	      Passes if	the incoming z value is	greater	than the
              stored z value.

  GL_NOTEQUAL	      Passes if	the incoming z value is	not equal to the
              stored z value.

  GL_GEQUAL	      Passes if	the incoming z value is	greater	than or	equal
              to the stored z value.

  GL_ALWAYS	      Always passes.

  The default value of func is GL_LESS.	 Initially, depth testing is
  disabled.

Errors

  GL_INVALID_ENUM is generated if func is not an accepted value.

  GL_INVALID_OPERATION is generated if glDepthFunc is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_DEPTH_FUNC
  glIsEnabled with argument GL_DEPTH_TEST

See Also

  glDepthRange,	glEnable

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glDepthMask.3gl

Name

  glDepthMask -	enable or disable writing into the depth buffer

C Specification

  void glDepthMask( GLboolean flag )

Parameters

  flag	Specifies whether the depth buffer is enabled for writing.  If flag
    is zero, depth buffer writing is disabled.  Otherwise, it is enabled.
    Initially, depth buffer	writing	is enabled.

Description

  glDepthMask specifies	whether	the depth buffer is enabled for	writing.  If
  flag is zero,	depth buffer writing is	disabled.  Otherwise, it is enabled.
  Initially, depth buffer writing is enabled.

Errors

  GL_INVALID_OPERATION is generated if glDepthMask is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_DEPTH_WRITEMASK

See Also

  glColorMask, glDepthFunc, glDepthRange, glIndexMask, glStencilMask

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glDepthRange.3gl

Name

  glDepthRange - specify the mapping of	z values from normalized device
  coordinates to window	coordinates

C Specification

  void glDepthRange( GLclampd near,
             GLclampd far )

Parameters

  near	Specifies the mapping of the near clipping plane to window
    coordinates.  The default value	is 0.

  far	Specifies the mapping of the far clipping plane	to window
    coordinates.  The default value	is 1.

Description

  After	clipping and division by w, z coordinates range	from -1.0 to 1.0,
  corresponding	to the near and	far clipping planes.  glDepthRange specifies
  a linear mapping of the normalized z coordinates in this range to window z
  coordinates.	Regardless of the actual depth buffer implementation, window
  coordinate depth values are treated as though	they range from	0.0 through
  1.0 (like color components).	Thus, the values accepted by glDepthRange are
  both clamped to this range before they are accepted.

  The default mapping of 0,1 maps the near plane to 0 and the far plane	to 1.
  With this mapping, the depth buffer range is fully utilized.

Notes

  It is	not necessary that near	be less	than far.  Reverse mappings such as
  1,0 are acceptable.

Errors

  GL_INVALID_OPERATION is generated if glDepthRange is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_DEPTH_RANGE

See Also

  glDepthFunc, glViewport

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glDrawArraysEXT.3gl

Name

  glDrawArraysEXT - render primitives from array data

C Specification

  void glDrawArraysEXT(	GLenum mode,
            GLint first,
            GLsizei	count )

Parameters

  mode	 Specifies what	kind of	primitives to render.  Symbolic	constants
     GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_TRIANGLE_STRIP,
     GL_TRIANGLE_FAN, GL_TRIANGLES,	GL_QUAD_STRIP, GL_QUADS, and
     GL_POLYGON are	accepted.

  first	 Specifies the starting	index in the enabled arrays.

  count	 Specifies the number of indices which should be rendered.

Description

  glDrawArraysEXT makes	it possible to specify multiple	geometric primitives
  with very few	subroutine calls. Instead of calling an	OpenGL procedure to
  pass each individual vertex, normal, or color, separate arrays of vertexes,
  normals, and colors can be prespecified, and used to define a	sequence of
  primitives (all of the same type) with a single call to glDrawArraysEXT.

  When glDrawArraysEXT is called, count	sequential elements from each enabled
  array	are used to construct a	sequence of geometric primitives, beginning
  with element first. mode specifies what kind of primitives are constructed,
  and how the array elements are used to construct these primitives. If
  GL_VERTEX_ARRAY_EXT is not enabled, no geometric primitives are generated.

  Vertex attributes that are modified by glDrawArraysEXT have an unspecified
  value	after glDrawArraysEXT returns.	For example, if	GL_COLOR_ARRAY_EXT is
  enabled, the value of	the current color is undefined after glDrawArraysEXT
  executes.  Attributes	that aren't modified remain well defined.

  Operation of glDrawArraysEXT is atomic with respect to error generation.
  If an	error is generated, no other operations	take place.

Notes

  glDrawArraysEXT may be included in display lists. If glDrawArraysEXT is
  entered into a display list, the necessary array data	(determined by the
  array	pointers and enables) is also entered into the display list.  Because
  the array pointers and enables are client side state,	their values affect
  display lists	when the lists are created, not	when the lists are executed.

  Static array data may	be read	and cached by the implementation at any	time.
  If static array elements are modified	and the	arrays are not respecified,
  the results of any subsequent	calls to glDrawArraysEXT are undefined.

  Although it is not an	error to respecify an array between the	execution of
  glBegin and the corresponding	execution of glEnd, the	result of such
  respecification is undefined.

  glDrawArraysEXT is part of the EXT_vertex_array extension, not
  part of the core GL command set. If "GL_EXT_vertex_array" is
  included in the string returned by glGetString, when called with argument
  GL_EXTENSIONS, extension EXT_vertex_array is supported.

Errors

  GL_INVALID_ENUM is generated if mode is not an accepted value.

  GL_INVALID_VALUE is generated	if count is negative.

  GL_INVALID_OPERATION is generated if glDrawArraysEXT is called between the
  execution of glBegin and the corresponding execution of glEnd.

See Also

  glArrayElementEXT, glColorPointerEXT,	glEdgeFlagPointerEXT,
  glGetPointervEXT, glIndexPointerEXT, glNormalPointerEXT,
  glTexCoordPointerEXT,	glVertexPointerEXT

Introduction | Alphabetic | Specification

Last Edited: Fri Feb 24, 1995

AFV


glDrawBuffer.3gl

Name

  glDrawBuffer - specify which color buffers are to be drawn into

C Specification

  void glDrawBuffer( GLenum mode )

Parameters

  mode	Specifies up to	four color buffers to be drawn into.  Symbolic
    constants GL_NONE, GL_FRONT_LEFT, GL_FRONT_RIGHT, GL_BACK_LEFT,
    GL_BACK_RIGHT, GL_FRONT, GL_BACK, GL_LEFT, GL_RIGHT,
    GL_FRONT_AND_BACK, and GL_AUXi,	where i	is between 0 and
    ``GL_AUX_BUFFERS'' -1, are accepted (GL_AUX_BUFFERS is not the upper
    limit; use glGet to query the number of	available aux buffers.)	The
    default	value is GL_FRONT for single-buffered contexts,	and GL_BACK
    for double-buffered contexts.

Description

  When colors are written to the frame buffer, they are	written	into the
  color	buffers	specified by glDrawBuffer.  The	specifications are as
  follows:

  GL_NONE		   No color buffers are	written.

  GL_FRONT_LEFT		   Only	the front left color buffer is written.

  GL_FRONT_RIGHT	   Only	the front right	color buffer is	written.

  GL_BACK_LEFT		   Only	the back left color buffer is written.

  GL_BACK_RIGHT		   Only	the back right color buffer is written.

  GL_FRONT		   Only	the front left and front right color buffers
               are written.	 If there is no	front right color
               buffer, only	the front left color buffer is
               written.

  GL_BACK		   Only	the back left and back right color buffers
               are written.	 If there is no	back right color
               buffer, only	the back left color buffer is
               written.

  GL_LEFT		   Only	the front left and back	left color buffers
               are written.	 If there is no	back left color
               buffer, only	the front left color buffer is
               written.

  GL_RIGHT		   Only	the front right	and back right color buffers
               are written.	 If there is no	back right color
               buffer, only	the front right	color buffer is
               written.

  GL_FRONT_AND_BACK	   All the front and back color	buffers	(front left,
               front right,	back left, back	right) are written.
               If there are	no back	color buffers, only the	front
               left	and front right	color buffers are written.
               If there are	no right color buffers,	only the
               front left and back left color buffers are
               written.  If	there are no right or back color
               buffers, only the front left	color buffer is
               written.

  GL_AUXi		   Only	auxiliary color	buffer i is written.

  If more than one color buffer	is selected for	drawing, then blending or
  logical operations are computed and applied independently for	each color
  buffer and can produce different results in each buffer.

  Monoscopic contexts include only left	buffers, and stereoscopic contexts
  include both left and	right buffers.	Likewise, single-buffered contexts
  include only front buffers, and double-buffered contexts include both	front
  and back buffers.  The context is selected at	GL initialization.

Notes

  It is	always the case	that GL_AUXi = GL_AUX0 + i.

Errors

  GL_INVALID_ENUM is generated if mode is not an accepted value.

  GL_INVALID_OPERATION is generated if none of the buffers indicated by	mode
  exists.

  GL_INVALID_OPERATION is generated if glDrawBuffer is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_DRAW_BUFFER
  glGet	with argument GL_AUX_BUFFERS

See Also

  glBlendFunc, glColorMask, glIndexMask, glLogicOp, glReadBuffer

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glDrawPixels.3gl

Name

  glDrawPixels - write a block of pixels to the	frame buffer

C Specification

  void glDrawPixels( GLsizei width,
             GLsizei height,
             GLenum format,
             GLenum type,
             const GLvoid *pixels )

Parameters

  width, height	Specify	the dimensions of the pixel rectangle that will	be
        written	into the frame buffer.

  format	Specifies the format of	the pixel data.	 Symbolic constants
        GL_COLOR_INDEX,	GL_STENCIL_INDEX, GL_DEPTH_COMPONENT,
        GL_RGBA, GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB,
        GL_LUMINANCE, and GL_LUMINANCE_ALPHA are accepted.

  type		Specifies the data type	for pixels.  Symbolic constants
        GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT,
        GL_SHORT, GL_UNSIGNED_INT, GL_INT, and GL_FLOAT	are accepted.

  pixels	Specifies a pointer to the pixel data.

Description

  glDrawPixels reads pixel data	from memory and	writes it into the frame
  buffer relative to the current raster	position.  Use glRasterPos to set the
  current raster position, and use glGet with argument
  GL_CURRENT_RASTER_POSITION to	query the raster position.

  Several parameters define the	encoding of pixel data in memory and control
  the processing of the	pixel data before it is	placed in the frame buffer.
  These	parameters are set with	four commands: glPixelStore, glPixelTransfer,
  glPixelMap, and  glPixelZoom.	 This reference	page describes the effects on
  glDrawPixels of many,	but not	all, of	the parameters specified by these
  four commands.

  Data is read from pixels as a	sequence of signed or unsigned bytes, signed
  or unsigned shorts, signed or	unsigned integers, or single-precision
  floating-point values, depending on type.  Each of these bytes, shorts,
  integers, or floating-point values is	interpreted as one color or depth
  component, or	one index, depending on	format.	 Indices are always treated
  individually.	 Color components are treated as groups	of one,	two, three,
  or four values, again	based on format.  Both individual indices and groups
  of components	are referred to	as pixels.  If type is GL_BITMAP, the data
  must be unsigned bytes, and format must be either GL_COLOR_INDEX or
  GL_STENCIL_INDEX.  Each unsigned byte	is treated as eight 1-bit pixels,
  with bit ordering determined by GL_UNPACK_LSB_FIRST (see glPixelStore).

  widthxheight pixels are read from memory, starting at	location pixels.  By
  default, these pixels	are taken from adjacent	memory locations, except that
  after	all width pixels are read, the read pointer is advanced	to the next
  four-byte boundary.  The four-byte row alignment is specified	by
  glPixelStore with argument GL_UNPACK_ALIGNMENT, and it can be	set to one,
  two, four, or	eight bytes.  Other pixel store	parameters specify different
  read pointer advancements, both before the first pixel is read, and after
  all width pixels are read.  Refer to the glPixelStore	reference page for
  details on these options.

  The widthxheight pixels that are read	from memory are	each operated on in
  the same way,	based on the values of several parameters specified by
  glPixelTransfer and glPixelMap.  The details of these	operations, as well
  as the target	buffer into which the pixels are drawn,	are specific to	the
  format of the	pixels,	as specified by	format.	 format	can assume one of
  eleven symbolic values:

  GL_COLOR_INDEX
        Each pixel is a single value, a color index.  It is	converted to
        fixed-point	format,	with an	unspecified number of bits to the
        right of the binary	point, regardless of the memory	data type.
        Floating-point values convert to true fixed-point values.  Signed
        and	unsigned integer data is converted with	all fraction bits set
        to zero.  Bitmap data convert to either 0.0	or 1.0.

        Each fixed-point index is then shifted left	by GL_INDEX_SHIFT
        bits and added to GL_INDEX_OFFSET.	If GL_INDEX_SHIFT is
        negative, the shift	is to the right.  In either case, zero bits
        fill otherwise unspecified bit locations in	the result.

        If the GL is in RGBA mode, the resulting index is converted	to an
        RGBA pixel using the GL_PIXEL_MAP_I_TO_R, GL_PIXEL_MAP_I_TO_G,
        GL_PIXEL_MAP_I_TO_B, and GL_PIXEL_MAP_I_TO_A tables.  If the GL
        is in color	index mode, and	if GL_MAP_COLOR	is true, the index is
        replaced with the value that it references in lookup table
        GL_PIXEL_MAP_I_TO_I.  Whether the lookup replacement of the	index
        is done or not, the	integer	part of	the index is then ANDed	with
             b
        2 -1, where	b is the number	of bits	in a color index buffer.

        The	resulting indices or RGBA colors are then converted to
        fragments by attaching the current raster position z coordinate
        and	texture	coordinates to each pixel, then	assigning x and	y
        window coordinates to the nth fragment such	that

                 x  = x	 + n mod width
                  n    r

                        y  = y  + floor (n/width)
                   n	r

        where (x ,y	) is the current raster	position.  These pixel
                    r  r
        fragments are then treated just like the fragments generated by
        rasterizing	points,	lines, or polygons.  Texture mapping, fog,
        and	all the	fragment operations are	applied	before the fragments
        are	written	to the frame buffer.

  GL_STENCIL_INDEX
        Each pixel is a single value, a stencil index.  It is converted
        to fixed-point format, with	an unspecified number of bits to the
        right of the binary	point, regardless of the memory	data type.
        Floating-point values convert to true fixed-point values.  Signed
        and	unsigned integer data is converted with	all fraction bits set
        to zero.  Bitmap data convert to either 0.0	or 1.0.

        Each fixed-point index is then shifted left	by GL_INDEX_SHIFT
        bits, and added to GL_INDEX_OFFSET.	 If GL_INDEX_SHIFT is
        negative, the shift	is to the right.  In either case, zero bits
        fill otherwise unspecified bit locations in	the result.  If
        GL_MAP_STENCIL is true, the	index is replaced with the value that
        it references in lookup table GL_PIXEL_MAP_S_TO_S.	Whether	the
        lookup replacement of the index is done or not, the	integer	part
                                             b
        of the index is then ANDed with 2 -1, where	b is the number	of
        bits in the	stencil	buffer.	 The resulting stencil indices are
        then written to the	stencil	buffer such that the nth index is
        written to location

                   x  = x  + n mod width
                n    r

                   y  = y  + floor (n/width)
                n    r

       where (x	,y ) is	the current raster position.  Only the pixel
               r  r
       ownership test, the scissor test, and the stencil writemask affect
       these writes.

  GL_DEPTH_COMPONENT
       Each pixel is a single-depth component.	Floating-point data is
       converted directly to an	internal floating-point	format with
       unspecified precision.  Signed integer data is mapped linearly to the
       internal	floating-point format such that	the most positive
       representable integer value maps	to 1.0,	and the	most negative
       representable value maps	to -1.0.  Unsigned integer data	is mapped
       similarly: the largest integer value maps to 1.0, and zero maps to
       0.0.  The resulting floating-point depth	value is then multiplied by
       GL_DEPTH_SCALE and added	to GL_DEPTH_BIAS.  The result is clamped to
       the range [0,1].

       The resulting depth components are then converted to fragments by
       attaching the current raster position color or color index and texture
       coordinates to each pixel, then assigning x and y window	coordinates
       to the nth fragment such	that

                   x  = x  + n mod width
                n    r

                   y  = y  + floor (n/width)
                n    r

       where (x	,y ) is	the current raster position.  These pixel fragments
               r  r
       are then treated	just like the fragments	generated by rasterizing
       points, lines, or polygons.  Texture mapping, fog, and all the
       fragment	operations are applied before the fragments are	written	to
       the frame buffer.

  GL_RGBA
       Each pixel is a four-component group: red first,	followed by green,
       followed	by blue, followed by alpha.  Floating-point values are
       converted directly to an	internal floating-point	format with
       unspecified precision.  Signed integer values are mapped	linearly to
       the internal floating-point format such that the	most positive
       representable integer value maps	to 1.0,	and the	most negative
       representable value maps	to -1.0.  Unsigned integer data	is mapped
       similarly: the largest integer value maps to 1.0, and zero maps to
       0.0.  The resulting floating-point color	values are then	multiplied by
       GL_c_SCALE and added to GL_c_BIAS, where	c is RED, GREEN, BLUE, and
       ALPHA for the respective	color components.  The results are clamped to
       the range [0,1].

       If GL_MAP_COLOR is true,	each color component is	scaled by the size of
       lookup table GL_PIXEL_MAP_c_TO_c, then replaced by the value that it
       references in that table.  c is R, G, B,	or A, respectively.

       The resulting RGBA colors are then converted to fragments by attaching
       the current raster position z coordinate	and texture coordinates	to
       each pixel, then	assigning x and	y window coordinates to	the nth
       fragment	such that

                   x  = x  + n mod width
                n    r

                   y  = y  + floor (n/width)
                n    r

       where (x	,y ) is	the current raster position.  These pixel fragments
               r  r
       are then treated	just like the fragments	generated by rasterizing
       points, lines, or polygons.  Texture mapping, fog, and all the
       fragment	operations are applied before the fragments are	written	to
       the frame buffer.

  GL_RED
       Each pixel is a single red component.  This component is	converted to
       the internal floating-point format in the same way as the red
       component of an RGBA pixel is, then it is converted to an RGBA pixel
       with green and blue set to 0.0, and alpha set to	1.0.  After this
       conversion, the pixel is	treated	just as	if it had been read as an
       RGBA pixel.

  GL_GREEN
       Each pixel is a single green component.	This component is converted
       to the internal floating-point format in	the same way as	the green
       component of an RGBA pixel is, then it is converted to an RGBA pixel
       with red	and blue set to	0.0, and alpha set to 1.0.  After this
       conversion, the pixel is	treated	just as	if it had been read as an
       RGBA pixel.

  GL_BLUE
       Each pixel is a single blue component.  This component is converted to
       the internal floating-point format in the same way as the blue
       component of an RGBA pixel is, then it is converted to an RGBA pixel
       with red	and green set to 0.0, and alpha	set to 1.0.  After this
       conversion, the pixel is	treated	just as	if it had been read as an
       RGBA pixel.

  GL_ALPHA
       Each pixel is a single alpha component.	This component is converted
       to the internal floating-point format in	the same way as	the alpha
       component of an RGBA pixel is, then it is converted to an RGBA pixel
       with red, green,	and blue set to	0.0.  After this conversion, the
       pixel is	treated	just as	if it had been read as an RGBA pixel.

  GL_RGB
       Each pixel is a three-component group: red first, followed by green,
       followed	by blue.  Each component is converted to the internal
       floating-point format in	the same way as	the red, green,	and blue
       components of an	RGBA pixel are.	 The color triple is converted to an
       RGBA pixel with alpha set to 1.0.  After	this conversion, the pixel is
       treated just as if it had been read as an RGBA pixel.

  GL_LUMINANCE
       Each pixel is a single luminance	component.  This component is
       converted to the	internal floating-point	format in the same way as the
       red component of	an RGBA	pixel is, then it is converted to an RGBA
       pixel with red, green, and blue set to the converted luminance value,
       and alpha set to	1.0.  After this conversion, the pixel is treated
       just as if it had been read as an RGBA pixel.

  GL_LUMINANCE_ALPHA
       Each pixel is a two-component group: luminance first, followed by
       alpha.  The two components are converted	to the internal	floating-
       point format in the same	way as the red component of an RGBA pixel is,
       then they are converted to an RGBA pixel	with red, green, and blue set
       to the converted	luminance value, and alpha set to the converted	alpha
       value.  After this conversion, the pixel	is treated just	as if it had
       been read as an RGBA pixel.

  The following	table summarizes the meaning of	the valid constants for	the
  type parameter:

     -------------------------------------------------------------
     |      type        |          corresponding type            |
     -------------------------------------------------------------
     |GL_UNSIGNED_BYTE  |	      unsigned 8-bit integer	     |
     |     GL_BYTE	    |	       signed 8-bit integer	     |
     |    GL_BITMAP	    | single bits in unsigned 8-bit integers |
     |GL_UNSIGNED_SHORT |	     unsigned 16-bit integer	     |
     |    GL_SHORT	    |	      signed 16-bit integer	     |
     | GL_UNSIGNED_INT  |	     unsigned 32-bit integer	     |
     |     GL_INT	    |		  32-bit integer	     |
     |    GL_FLOAT	    |	 single-precision floating-point     |
     -------------------------------------------------------------

  The rasterization described thus far assumes pixel zoom factors of 1.0.  If
  glPixelZoom is used to change	the x and y pixel zoom factors,	pixels are
  converted to fragments as follows.  If (x , y ) is the current raster
                                           r   r
  position, and	a given	pixel is in the	nth column and mth row of the pixel
  rectangle, then fragments are	generated for pixels whose centers are in the
  rectangle with corners at

                  (x + zoom  n, y + zoom  m)
                                r      x     r      y

                      (x + zoom  (n+1), y + zoom  (m+1))
                            r      x         r      y

  where	zoom  is the value of GL_ZOOM_X	and zoom  is the value of GL_ZOOM_Y.
            x                                   y

Errors

  GL_INVALID_VALUE is generated	if either width	or height is negative.

  GL_INVALID_ENUM is generated if format or type is not	one of the accepted
  values.

  GL_INVALID_OPERATION is generated if format is GL_RED, GL_GREEN, GL_BLUE,
  GL_ALPHA, GL_RGB, GL_RGBA, GL_LUMINANCE, or GL_LUMINANCE_ALPHA, and the GL
  is in	color index mode.

  GL_INVALID_ENUM is generated if type is GL_BITMAP and	format is not either
  GL_COLOR_INDEX or GL_STENCIL_INDEX.

  GL_INVALID_OPERATION is generated if format is GL_STENCIL_INDEX and there
  is no	stencil	buffer.

  GL_INVALID_OPERATION is generated if glDrawPixels is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_CURRENT_RASTER_POSITION
  glGet	with argument GL_CURRENT_RASTER_POSITION_VALID

See Also

  glAlphaFunc, glBlendFunc, glCopyPixels, glDepthFunc, glLogicOp, glPixelMap,
  glPixelStore,	glPixelTransfer, glPixelZoom, glRasterPos, glReadPixels,
  glScissor, glStencilFunc

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glEdgeFlag.3gl

Name

  glEdgeFlag, glEdgeFlagv - flag edges as either boundary or nonboundary

C Specification

  void glEdgeFlag( GLboolean flag )

Parameters

  flag	Specifies the current edge flag	value, either true or false.

C Specification

  void glEdgeFlagv( const GLboolean *flag )

Parameters

  flag Specifies a pointer to an array that contains a single Boolean
       element,	which replaces the current edge	flag value.

Description

  Each vertex of a polygon, separate triangle, or separate quadrilateral
  specified between a glBegin/glEnd pair is marked as the start	of either a
  boundary or nonboundary edge.	 If the	current	edge flag is true when the
  vertex is specified, the vertex is marked as the start of a boundary edge.
  Otherwise, the vertex	is marked as the start of a nonboundary	edge.
  glEdgeFlag sets the edge flag	to true	if flag	is nonzero, false otherwise.

  The vertices of connected triangles and connected quadrilaterals are always
  marked as boundary, regardless of the	value of the edge flag.

  Boundary and nonboundary edge	flags on vertices are significant only if
  GL_POLYGON_MODE is set to GL_POINT or	GL_LINE.  See glPolygonMode.

  Initially, the edge flag bit is true.

Notes

  The current edge flag	can be updated at any time.  In	particular,
  glEdgeFlag can be called between a call to glBegin and the corresponding
  call to glEnd.

Associated Gets

  glGet	with argument GL_EDGE_FLAG

See Also

  glBegin, glPolygonMode

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glEdgeFlagPointerEXT.3gl

Name

  glEdgeFlagPointerEXT - define	an array of edge flags

C Specification

  void glEdgeFlagPointerEXT( GLsizei stride,
                 GLsizei count,
                 const GLboolean *pointer )

Parameters

  stride   Specifies the byte offset between consecutive edge flags.  If
       stride is zero the edge flags are understood	to be tightly packed
       in the array.

  count	   Specifies the number	of edge	flags, counting	from the first,	that
       are static.

  pointer  Specifies a pointer to the first edge flag in the array.

Description

  glEdgeFlagPointerEXT specifies the location and data format of an array of
  boolean edge flags to use when rendering when using the vertex array
  extension. stride gives the byte stride from one edge flag to the next
  allowing vertexes and attributes to be packed into a single array or stored
  in separate arrays.  (Single-array storage may be more efficient on some
  implementations.) count indicates the number of array elements (counting
  from the first) that are static. Static elements may be modified by the
  application, but once they are modified, the application must explicitly
  respecify the array before using it for any rendering. When an edge flag
  array is specified, stride, count and pointer are saved as client-side state,
  and static array elements may be cached by the implementation.

  The edge flag	array is enabled and disabled using glEnable and glDisable
  with the argument GL_EDGE_FLAG_ARRAY_EXT. If enabled,	the edge flag array
  is used when glDrawArraysEXT or glArrayElementEXT is called.

  Use glDrawArraysEXT to define	a sequence of primitives (all of the same
  type)	from pre-specified vertex and vertex attribute arrays.	Use
  glArrayElementEXT to specify primitives by indexing vertexes and vertex
  attributes.

Notes

  Non-static array elements are	not accessed until glArrayElementEXT or
  glDrawArraysEXT is executed.

  By default the edge flag array is disabled and it won't be accessed when
  glArrayElementEXT or glDrawArraysEXT is called.

  Although it is not an error to call glEdgeFlagPointerEXT between the
  execution of glBegin and the corresponding execution of glEnd, the results
  are undefined.

  glEdgeFlagPointerEXT will typically be implemented on	the client side	with
  no protocol.

  Since	the edge flag array parameters are client side state, they are not
  saved	or restored by glPushAttrib and	glPopAttrib.

  glEdgeFlagPointerEXT commands	are not	entered	into display lists.

  glEdgeFlagPointerEXT is part of the EXT_vertex_array extension,
  not part of the core GL command set. If "GL_EXT_vertex_array" is
  included in the string returned by glGetString, when called with argument
  GL_EXTENSIONS, extension EXT_vertex_array is supported.

Errors

  GL_INVALID_ENUM is generated if stride  or count is negative.

Associated Gets

  glIsEnabled with argument GL_EDGE_FLAG_ARRAY_EXT
  glGet	with argument GL_EDGE_FLAG_ARRAY_STRIDE_EXT
  glGet	with argument GL_EDGE_FLAG_ARRAY_COUNT_EXT
  glGetPointervEXT with	argument GL_EDGE_FLAG_ARRAY_POINTER_EXT

See Also

  glArrayElementEXT, glColorPointerEXT,	glDrawArraysEXT, glGetPointervEXT,
  glIndexPointerEXT, glNormalPointerEXT, glTexCoordPointerEXT,
  glVertexPointerEXT, glEnable

Introduction | Alphabetic | Specification

Last Edited: Fri Feb 24, 1995

AFV


glEnable.3gl

Name

  glEnable, glDisable -	enable or disable GL capabilities

C Specification

  void glEnable( GLenum	cap )

Parameters

  cap  Specifies a symbolic constant indicating	a GL capability.

C Specification

  void glDisable( GLenum cap )

Parameters

  cap  Specifies a symbolic constant indicating	a GL capability.

Description

  glEnable and glDisable enable	and disable various capabilities.  Use
  glIsEnabled or glGet to determine the	current	setting	of any capability.

  Both glEnable	and glDisable take a single argument, cap, which can assume
  one of the following values:

  GL_ALPHA_TEST		   If enabled, do alpha	testing. See glAlphaFunc.

  GL_AUTO_NORMAL	   If enabled, compute surface normal vectors
               analytically	when either GL_MAP2_VERTEX_3 or
               GL_MAP2_VERTEX_4 is used to generate	vertices.
               See glMap2.

  GL_BLEND		   If enabled, blend the incoming RGBA color values
               with	the values in the color	buffers.  See
               glBlendFunc.

  GL_CLIP_PLANEi	   If enabled, clip geometry against user-defined
               clipping plane i.  See glClipPlane.

  GL_COLOR_ARRAY_EXT	   If enabled, colors are taken	from the color array
               when	glArrayElementEXT or glDrawArraysEXT is
               called.  See	glColorPointerEXT, glArrayElementEXT
               and glDrawArraysEXT.

  GL_COLOR_MATERIAL	   If enabled, have one	or more	material parameters
               track the current color.  See glColorMaterial.

  GL_CULL_FACE		   If enabled, cull polygons based on their winding
               in window coordinates.  See glCullFace.

  GL_DEPTH_TEST		   If enabled, do depth	comparisons and	update the
               depth buffer.  See glDepthFunc and  glDepthRange.

  GL_DITHER		   If enabled, dither color components or indices
               before they are written to the color	buffer.

  GL_EDGE_FLAG_ARRAY_EXT   If enabled, edge flags are taken from the edge
               flags array when glArrayElementEXT or
               glDrawArraysEXT is called.  See
               glEdgeFlagPointerEXT, glArrayElementEXT and
               glDrawArraysEXT.

  GL_FOG		   If enabled, blend a fog color into the
               posttexturing color.	 See glFog.

  GL_INDEX_ARRAY_EXT	   If enabled, color indexes are taken from the	color
               index array when glArrayElementEXT or
               glDrawArraysEXT is called.  See glIndexPointerEXT,
               glArrayElementEXT and glDrawArraysEXT.

  GL_LIGHTi		   If enabled, include light i in the evaluation of
               the lighting	equation.  See glLightModel and
               glLight.

  GL_LIGHTING		   If enabled, use the current lighting	parameters to
               compute the vertex color or index.  Otherwise,
               simply associate the	current	color or index with
               each	vertex.	 See glMaterial, glLightModel, and
               glLight.

  GL_LINE_SMOOTH	   If enabled, draw lines with correct filtering.
               Otherwise, draw aliased lines.  See glLineWidth.

  GL_LINE_STIPPLE	   If enabled, use the current line stipple pattern
               when	drawing	lines.	See glLineStipple.

  GL_LOGIC_OP		   If enabled, apply the currently selected logical
               operation to	the incoming and color buffer
               indices.  See glLogicOp.

  GL_MAP1_COLOR_4	   If enabled, calls to	glEvalCoord1, glEvalMesh1,
               and glEvalPoint1 will generate RGBA values.	See
               glMap1.

  GL_MAP1_INDEX		   If enabled, calls to	glEvalCoord1, glEvalMesh1,
               and glEvalPoint1 will generate color	indices.  See
               glMap1.

  GL_MAP1_NORMAL	   If enabled, calls to	glEvalCoord1, glEvalMesh1,
               and glEvalPoint1 will generate normals.  See
               glMap1.

  GL_MAP1_TEXTURE_COORD_1  If enabled, calls to	glEvalCoord1, glEvalMesh1,
               and glEvalPoint1 will generate s texture
               coordinates.	 See glMap1.

  GL_MAP1_TEXTURE_COORD_2  If enabled, calls to	glEvalCoord1, glEvalMesh1,
               and glEvalPoint1 will generate s and	t texture
               coordinates.	 See glMap1.

  GL_MAP1_TEXTURE_COORD_3  If enabled, calls to	glEvalCoord1, glEvalMesh1,
               and glEvalPoint1 will generate s, t,	and r texture
               coordinates.	 See glMap1.

  GL_MAP1_TEXTURE_COORD_4  If enabled, calls to	glEvalCoord1, glEvalMesh1,
               and glEvalPoint1 will generate s, t,	r, and q
               texture coordinates.	 See glMap1.

  GL_MAP1_VERTEX_3	   If enabled, calls to	glEvalCoord1, glEvalMesh1,
               and glEvalPoint1 will generate will generate	x, y,
               and z vertex	coordinates.  See glMap1.

  GL_MAP1_VERTEX_4	   If enabled, calls to	glEvalCoord1, glEvalMesh1,
               and glEvalPoint1 will generate homogeneous x, y,
               z, and w vertex coordinates.	 See glMap1.

  GL_MAP2_COLOR_4	   If enabled, calls to	glEvalCoord2, glEvalMesh2,
               and glEvalPoint2 will generate RGBA values.	See
               glMap2.

  GL_MAP2_INDEX		   If enabled, calls to	glEvalCoord2, glEvalMesh2,
               and glEvalPoint2 will generate color	indices.  See
               glMap2.

  GL_MAP2_NORMAL	   If enabled, calls to	glEvalCoord2, glEvalMesh2,
               and glEvalPoint2 will generate normals.  See
               glMap2.

  GL_MAP2_TEXTURE_COORD_1  If enabled, calls to	glEvalCoord2, glEvalMesh2,
               and glEvalPoint2 will generate s texture
               coordinates.	 See glMap2.

  GL_MAP2_TEXTURE_COORD_2  If enabled, calls to	glEvalCoord2, glEvalMesh2,
               and glEvalPoint2 will generate s and	t texture
               coordinates.	 See glMap2.

  GL_MAP2_TEXTURE_COORD_3  If enabled, calls to	glEvalCoord2, glEvalMesh2,
               and glEvalPoint2 will generate s, t,	and r texture
               coordinates.	 See glMap2.

  GL_MAP2_TEXTURE_COORD_4  If enabled, calls to	glEvalCoord2, glEvalMesh2,
               and glEvalPoint2 will generate s, t,	r, and q
               texture coordinates.	 See glMap2.

  GL_MAP2_VERTEX_3	   If enabled, calls to	glEvalCoord2, glEvalMesh2,
               and glEvalPoint2 will generate will generate	x, y,
               and z vertex	coordinates.  See glMap2.

  GL_MAP2_VERTEX_4	   If enabled, calls to	glEvalCoord2, glEvalMesh2,
               and glEvalPoint2 will generate homogeneous x, y,
               z, and w vertex coordinates.	 See glMap2.

  GL_NORMAL_ARRAY_EXT	   If enabled, normals are taken from the normal
               array when glArrayElementEXT	or glDrawArraysEXT is
               called.  See	glNormalPointerEXT, glArrayElementEXT
               and glDrawArraysEXT.

  GL_NORMALIZE		   If enabled, normal vectors specified	with glNormal
               are scaled to unit length after transformation.
               See glNormal.

  GL_POINT_SMOOTH	   If enabled, draw points with	proper filtering.
               Otherwise, draw aliased points.  See	glPointSize.

  GL_POLYGON_SMOOTH	   If enabled, draw polygons with proper filtering.
               Otherwise, draw aliased polygons.  See
               glPolygonMode.

  GL_POLYGON_STIPPLE	   If enabled, use the current polygon stipple
               pattern when	rendering polygons.  See
               glPolygonStipple.

  GL_SCISSOR_TEST	   If enabled, discard fragments that are outside the
               scissor rectangle.  See glScissor.

  GL_STENCIL_TEST	   If enabled, do stencil testing and update the
               stencil buffer.  See	glStencilFunc and
               glStencilOp.

  GL_TEXTURE_1D		   If enabled, one-dimensional texturing is performed
               (unless two-dimensional texturing is	also
               enabled).  See glTexImage1D.

  GL_TEXTURE_2D		   If enabled, two-dimensional texturing is
               performed.  See glTexImage2D.

  GL_TEXTURE_COORD_ARRAY_EXT
               If enabled, texture coordinates are taken from the
               texture coordinates array when glArrayElementEXT
               or glDrawArraysEXT is called.  See
               glTexCoordPointerEXT, glArrayElementEXT and
               glDrawArraysEXT.

  GL_TEXTURE_GEN_Q	   If enabled, the q texture coordinate	is computed
               using the texture generation	function defined with
               glTexGen.  Otherwise, the current q texture
               coordinate is used.	See glTexGen.

  GL_TEXTURE_GEN_R	   If enabled, the r texture coordinate	is computed
               using the texture generation	function defined with
               glTexGen.  Otherwise, the current r texture
               coordinate is used.	See glTexGen.

  GL_TEXTURE_GEN_S	   If enabled, the s texture coordinate	is computed
               using the texture generation	function defined with
               glTexGen.  Otherwise, the current s texture
               coordinate is used.	See glTexGen.

  GL_TEXTURE_GEN_T	   If enabled, the t texture coordinate	is computed
               using the texture generation	function defined with
               glTexGen.  Otherwise, the current t texture
               coordinate is used.	See glTexGen.

  GL_VERTEX_ARRAY_EXT	   If enabled, vertexes	are taken from the vertex
               array when glArrayElementEXT	or glDrawArraysEXT is
               called.  See	glVertexPointerEXT, glArrayElementEXT
               and glDrawArraysEXT.

Errors

  GL_INVALID_ENUM is generated if cap is not one of the	values listed above.

  GL_INVALID_OPERATION is generated if glEnable	or glDisable is	executed
  between the execution	of  glBegin and	the corresponding execution of glEnd.

See Also

  glAlphaFunc, glBlendFunc, glClipPlane, glColorMaterial, glCullFace,
  glDepthFunc, glDepthRange, glFog, glGet, glIsEnabled,	glLight,
  glLightModel,	glLineWidth, glLineStipple, glLogicOp, glMap1, glMap2,
  glMaterial, glNormal,	glPointSize, glPolygonMode, glPolygonStipple,
  glScissor, glStencilFunc, glStencilOp, glTexGen, glTexImage1D, glTexImage2D

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glEvalCoord.3gl

Name

  glEvalCoord1d, glEvalCoord1f,	glEvalCoord2d, glEvalCoord2f, glEvalCoord1dv,
  glEvalCoord1fv, glEvalCoord2dv, glEvalCoord2fv - evaluate enabled one- and
  two-dimensional maps

C Specification

  void glEvalCoord1d( GLdouble u )
  void glEvalCoord1f( GLfloat u	)
  void glEvalCoord2d( GLdouble u,
              GLdouble v )
  void glEvalCoord2f( GLfloat u,
              GLfloat v	)

Parameters

  u  Specifies a value that is the domain coordinate u to the basis function
     defined in	a previous glMap1 or glMap2 command.

  v  Specifies a value that is the domain coordinate v to the basis function
     defined in	a previous glMap2 command.  This argument is not present in
     an	glEvalCoord1 command.

C Specification

  void glEvalCoord1dv( const GLdouble *u )
  void glEvalCoord1fv( const GLfloat *u	)
  void glEvalCoord2dv( const GLdouble *u )
  void glEvalCoord2fv( const GLfloat *u	)

Parameters

  u    Specifies a pointer to an array containing either one or	two domain
       coordinates.  The first coordinate is u.	 The second coordinate is v,
       which is	present	only in	glEvalCoord2 versions.

Description

  glEvalCoord1 evaluates enabled one-dimensional maps at argument u.
  glEvalCoord2 does the	same for two-dimensional maps using two	domain
  values, u and	v.  Maps are defined with glMap1 and glMap2 and	enabled	and
  disabled with	 glEnable and glDisable.

  When one of the glEvalCoord commands is issued, all currently	enabled	maps
  of the indicated dimension are evaluated.  Then, for each enabled map, it
  is as	if the corresponding GL	command	was issued with	the computed value.
  That is, if GL_MAP1_INDEX or GL_MAP2_INDEX is	enabled, a glIndex command is
  simulated.  If GL_MAP1_COLOR_4 or GL_MAP2_COLOR_4 is enabled,	a glColor
  command is simulated.	 If GL_MAP1_NORMAL or GL_MAP2_NORMAL is	enabled, a
  normal vector	is produced, and if any	of GL_MAP1_TEXTURE_COORD_1,
  GL_MAP1_TEXTURE_COORD_2, GL_MAP1_TEXTURE_COORD_3, GL_MAP1_TEXTURE_COORD_4,
  GL_MAP2_TEXTURE_COORD_1, GL_MAP2_TEXTURE_COORD_2, GL_MAP2_TEXTURE_COORD_3,
  or GL_MAP2_TEXTURE_COORD_4 is	enabled, then an appropriate glTexCoord
  command is simulated.

  The GL uses evaluated	values instead of current values for those
  evaluations that are enabled,	and current values otherwise, for color,
  color	index, normal, and texture coordinates.	 However, the evaluated
  values do not	update the current values.  Thus, if glVertex commands are
  interspersed with glEvalCoord	commands, the color, normal, and texture
  coordinates associated with the glVertex commands are	not affected by	the
  values generated by the glEvalCoord commands,	but rather only	by the most
  recent glColor, glIndex, glNormal, and glTexCoord commands.

  No commands are issued for maps that are not enabled.	 If more than one
  texture evaluation is	enabled	for a particular dimension (for	example,
  GL_MAP2_TEXTURE_COORD_1 and GL_MAP2_TEXTURE_COORD_2),	then only the
  evaluation of	the map	that produces the larger number	of coordinates (in
  this case, GL_MAP2_TEXTURE_COORD_2) is carried out.  GL_MAP1_VERTEX_4
  overrides GL_MAP1_VERTEX_3, and GL_MAP2_VERTEX_4 overrides
  GL_MAP2_VERTEX_3, in the same	manner.	 If neither a three- nor four-
  component vertex map is enabled for the specified dimension, the
  glEvalCoord command is ignored.

  If automatic normal generation is enabled, by	calling	glEnable with
  argument GL_AUTO_NORMAL, glEvalCoord2	generates surface normals
  analytically,	regardless of the contents or enabling of the GL_MAP2_NORMAL
  map.	Let

                                        Dp   Dp
                    m = -- · --
                                        Du   Dv

  where D represents the partial differential operator.

  Then the generated normal n is

                           m
                                    n =  -----
                         ||m||

  If automatic normal generation is disabled, the corresponding	normal map
  GL_MAP2_NORMAL, if enabled, is used to produce a normal.  If neither
  automatic normal generation nor a normal map is enabled, no normal is
  generated for	glEvalCoord2 commands.

Associated Gets

  glIsEnabled with argument GL_MAP1_VERTEX_3
  glIsEnabled with argument GL_MAP1_VERTEX_4
  glIsEnabled with argument GL_MAP1_INDEX
  glIsEnabled with argument GL_MAP1_COLOR_4
  glIsEnabled with argument GL_MAP1_NORMAL
  glIsEnabled with argument GL_MAP1_TEXTURE_COORD_1
  glIsEnabled with argument GL_MAP1_TEXTURE_COORD_2
  glIsEnabled with argument GL_MAP1_TEXTURE_COORD_3
  glIsEnabled with argument GL_MAP1_TEXTURE_COORD_4
  glIsEnabled with argument GL_MAP2_VERTEX_3
  glIsEnabled with argument GL_MAP2_VERTEX_4
  glIsEnabled with argument GL_MAP2_INDEX
  glIsEnabled with argument GL_MAP2_COLOR_4
  glIsEnabled with argument GL_MAP2_NORMAL
  glIsEnabled with argument GL_MAP2_TEXTURE_COORD_1
  glIsEnabled with argument GL_MAP2_TEXTURE_COORD_2
  glIsEnabled with argument GL_MAP2_TEXTURE_COORD_3
  glIsEnabled with argument GL_MAP2_TEXTURE_COORD_4
  glIsEnabled with argument GL_AUTO_NORMAL
  glGetMap

See Also

  glBegin, glColor, glEnable, glEvalMesh, glEvalPoint, glIndex,	glMap1,
  glMap2, glMapGrid, glNormal, glTexCoord, glVertex

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glEvalMesh.3gl

Name

  glEvalMesh1, glEvalMesh2 - compute a one- or two-dimensional grid of points
  or lines

C Specification

  void glEvalMesh1( GLenum mode,
            GLint i1,
            GLint i2 )

Parameters

  mode	In glEvalMesh1,	specifies whether to compute a one-dimensional mesh
    of points or lines.  Symbolic constants	GL_POINT and GL_LINE are
    accepted.

  i1, i2
    Specify	the first and last integer values for grid domain variable i.

C Specification

  void glEvalMesh2( GLenum mode,
            GLint i1,
            GLint i2,
            GLint j1,
            GLint j2 )

Parameters

  mode In glEvalMesh2, specifies whether to compute a two-dimensional mesh of
       points, lines, or polygons.  Symbolic constants GL_POINT, GL_LINE, and
       GL_FILL are accepted.

  i1, i2
       Specify the first and last integer values for grid domain variable i.

  j1, j2
       Specify the first and last integer values for grid domain variable j.

Description

  glMapGrid and	glEvalMesh are used in tandem to efficiently generate and
  evaluate a series of evenly spaced map domain	values.	 glEvalMesh steps
  through the integer domain of	a one- or two-dimensional grid,	whose range
  is the domain	of the evaluation maps specified by glMap1 and glMap2.	mode
  determines whether the resulting vertices are	connected as points, lines,
  or filled polygons.

  In the one-dimensional case, glEvalMesh1, the	mesh is	generated as if	the
  following code fragment were executed:

    glBegin(type);
    for (i = i1; i <= i2; i += 1)
        glEvalCoord1(i · du + u1)
    glEnd();

  where

       du = (u2-u1)/n

  and n, u1, and u2 are	the arguments to the most recent glMapGrid1 command.
  type is GL_POINT if mode is GL_POINT, or GL_LINES if	mode is	GL_LINE.  The
  one absolute numeric requirement is that if i	= n, then the value computed
  from i·du + u1 is exactly u2.

  In the two-dimensional case, glEvalMesh2, let

             du = (u2-u1)/n

             dv = (v2-v1)/m

  where	n, u1, u2, m, v1, and v2 are the arguments to the most recent
  glMapGrid2 comand.  Then, if mode is	GL_FILL, the glEvalMesh2 command is
  equivalent to:

  for (j = j1;  j < j2; j += 1) {
      glBegin(GL_QUAD_STRIP);
      for (i = i1; i <=	i2; i += 1) {
      glEvalCoord2(i·du + u1, j·dv + v1);
      glEvalCoord2(i·du + u1, (j+1)·dv + v1);
      }
      glEnd();
  }

  If mode is GL_LINE, then a call to glEvalMesh2 is equivalent to:

  for (j = j1;  j <= j2; j += 1) {
      glBegin(GL_LINE_STRIP);
      for (i = i1; i <= i2; i += 1)
      glEvalCoord2(i·du + u1, j·dv + v1);
      glEnd();
  }
  for (i = i1; i <= i2; i += 1) {
      glBegin(GL_LINE_STRIP);
      for (j = j1; j <= j1; j += 1)
      glEvalCoord2(i·du + u1, j·dv + v1);
      glEnd();
  }

  And finally, if mode is GL_POINT, then a call	to glEvalMesh2 is equivalent
  to:

  glBegin(GL_POINTS);
  for (j = j1; j <= j2; j += 1) {
      for (i = i1; i <= i2; i += 1) {
      glEvalCoord2(i·du + u1, j·dv + v1);
      }
  }
  glEnd();

  In all three cases, the only absolute	numeric	requirements are that if
  i = n, then the value	computed from i·du + u1	is exactly u2, and if j	= m,
  then the value computed from j·dv + v1 is exactly v2.

Errors

  GL_INVALID_ENUM is generated if mode is not an accepted value.

  GL_INVALID_OPERATION is generated if glEvalMesh is called between a call to
  glBegin and the corresponding	call to	glEnd.

Associated Gets

  glGet	with argument GL_MAP1_GRID_DOMAIN
  glGet	with argument GL_MAP2_GRID_DOMAIN
  glGet	with argument GL_MAP1_GRID_SEGMENTS
  glGet	with argument GL_MAP2_GRID_SEGMENTS

See Also

  glBegin, glEvalCoord,	glEvalPoint, glMap1, glMap2, glMapGrid

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glEvalPoint.3gl

Name

  glEvalPoint1,	glEvalPoint2 - generate	and evaluate a single point in a mesh

C Specification

  void glEvalPoint1( GLint i )
  void glEvalPoint2( GLint i,
             GLint j )

Parameters

  i  Specifies the integer value for grid domain variable i.

  j  Specifies the integer value for grid domain variable j (glEvalPoint2
     only).

Description

  glMapGrid and	glEvalMesh are used in tandem to efficiently generate and
  evaluate a series of evenly spaced map domain	values.	 glEvalPoint can be
  used to evaluate a single grid point in the same gridspace that is
  traversed by glEvalMesh.  Calling glEvalPoint1 is equivalent to calling

               glEvalCoord1(i·du + u );
                                                1
                    where

                du = (u	-u )/n
                       2  1

  and n, u , and u  are	the arguments to the most recent glMapGrid1 command.
          1       2
  The one absolute numeric requirement is that if i = n, then the value
  computed from	i·du + u  is exactly u .
            1	      2

  In the two-dimensional case, glEvalPoint2, let

                 du=(u -u )/n
                      2	 1
                 dv=(v -v )/m
                      2	 1
  where	n, u , u , m, v	, and v	 are the arguments to the most recent
            1   2      1       2
  glMapGrid2 command.  Then the glEvalPoint2 command is	equivalent to calling

              glEvalCoord2(i·du	+ u , j·dv + v );
                           1          1

  The only absolute numeric requirements are that if i = n, then the value
  computed from	i·du + u  is exactly u , and if	j = m, then the	value
                        1             2
  computed from	j·dv + v  is exactly v .
            1	      2

Associated Gets

  glGet	with argument GL_MAP1_GRID_DOMAIN
  glGet	with argument GL_MAP2_GRID_DOMAIN
  glGet	with argument GL_MAP1_GRID_SEGMENTS
  glGet	with argument GL_MAP2_GRID_SEGMENTS

See Also

  glEvalCoord, glEvalMesh, glMap1, glMap2, glMapGrid

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glFeedbackBuffer.3gl

Name

  glFeedbackBuffer - controls feedback mode

C Specification

  void glFeedbackBuffer( GLsizei size,
             GLenum	type,
             GLfloat *buffer )

Parameters

  size	  Specifies the	maximum	number of values that can be written into
      buffer.

  type	  Specifies a symbolic constant	that describes the information that
      will be returned for each vertex.  GL_2D, GL_3D, GL_3D_COLOR,
      GL_3D_COLOR_TEXTURE, and GL_4D_COLOR_TEXTURE are accepted.

  buffer  Returns the feedback data.

Description

  The glFeedbackBuffer function	controls feedback.  Feedback, like selection,
  is a GL mode.	 The mode is selected by calling glRenderMode with
  GL_FEEDBACK.	When the GL is in feedback mode, no pixels are produced	by
  rasterization.  Instead, information about primitives	that would have	been
  rasterized is	fed back to the	application using the GL.

  glFeedbackBuffer has three arguments:	buffer is a pointer to an array	of
  floating-point values	into which feedback information	is placed.  size
  indicates the	size of	the array.  type is a symbolic constant	describing
  the information that is fed back for each vertex.  glFeedbackBuffer must be
  issued before	feedback mode is enabled (by calling glRenderMode with
  argument GL_FEEDBACK).  Setting GL_FEEDBACK without establishing the
  feedback buffer, or calling glFeedbackBuffer while the GL is in feedback
  mode,	is an error.

  The GL is taken out of feedback mode by calling glRenderMode with a
  parameter value other	than GL_FEEDBACK.  When	this is	done while the GL is
  in feedback mode, glRenderMode returns the number of entries placed in the
  feedback array.  The returned	value never exceeds size. If the feedback
  data required	more room than was available in	buffer,	glRenderMode returns
  a negative value.

  While	in feedback mode, each primitive that would be rasterized generates a
  block	of values that get copied into the feedback array.  If doing so	would
  cause	the number of entries to exceed	the maximum, the block is partially
  written so as	to fill	the array (if there is any room	left at	all), and an
  overflow flag	is set.	 Each block begins with	a code indicating the
  primitive type, followed by values that describe the primitive's vertices
  and associated data.	Entries	are also written for bitmaps and pixel
  rectangles.  Feedback	occurs after polygon culling and glPolyMode
  interpretation of polygons has taken place, so polygons that are culled are
  not returned in the feedback buffer.	It can also occur after	polygons with
  more than three edges	are broken up into triangles, if the GL
  implementation renders polygons by performing	this decomposition.

  The glPassThrough command can	be used	to insert a marker into	the feedback
  buffer.  See glPassThrough.

  Following is the grammar for the blocks of values written into the feedback
  buffer.  Each	primitive is indicated with a unique identifying value
  followed by some number of vertices.	Polygon	entries	include	an integer
  value	indicating how many vertices follow.  A	vertex is fed back as some
  number of floating-point values, as determined by type.  Colors are fed
  back as four values in RGBA mode and one value in color index	mode.

       feedbackList <- feedbackItem feedbackList | feedbackItem

       feedbackItem <- point | lineSegment | polygon | bitmap |
       pixelRectangle |	passThru

       point <-	GL_POINT_TOKEN vertex

       lineSegment <- GL_LINE_TOKEN vertex vertex | GL_LINE_RESET_TOKEN
       vertex vertex

       polygon <- GL_POLYGON_TOKEN n polySpec

       polySpec	<- polySpec vertex | vertex vertex vertex

       bitmap <- GL_BITMAP_TOKEN vertex

       pixelRectangle <- GL_DRAW_PIXEL_TOKEN vertex | GL_COPY_PIXEL_TOKEN
       vertex

       passThru	<- GL_PASS_THROUGH_TOKEN value

       vertex <- 2d | 3d | 3dColor | 3dColorTexture | 4dColorTexture

       2d <- value value

       3d <- value value value

       3dColor	<- value value value color

       3dColorTexture <- value value value color tex

       4dColorTexture <- value value value value color tex

       color <-	rgba | index

       rgba <- value value value value

       index <-	value

       tex <- value value value	value

  value	is a floating-point number, and	n is a floating-point integer giving
  the number of	vertices in the	polygon.  GL_POINT_TOKEN, GL_LINE_TOKEN,
  GL_LINE_RESET_TOKEN, GL_POLYGON_TOKEN, GL_BITMAP_TOKEN,
  GL_DRAW_PIXEL_TOKEN, GL_COPY_PIXEL_TOKEN and GL_PASS_THROUGH_TOKEN are
  symbolic floating-point constants.  GL_LINE_RESET_TOKEN is returned
  whenever the line stipple pattern is reset.  The data	returned as a vertex
  depends on the feedback type.

  The following	table gives the	correspondence between type and	the number of
  values per vertex.  k	is 1 in	color index mode and 4 in RGBA mode.

------------------------------------------------------------------------------
|       type         | coordinates | color | texture | total number of values |
------------------------------------------------------------------------------
|	GL_2D	     |	  x, y	   |	   |	     |		 2	      |
|	GL_3D	     |	 x, y, z   |	   |	     |		 3	      |
|    GL_3D_COLOR     |	 x, y, z   |   k   |	     |		3+k	      |
|GL_3D_COLOR_TEXTURE |	x, y, z,   |   k   |	4    |		7+k	      |
|GL_4D_COLOR_TEXTURE | x, y, z,	w  |   k   |	4    |		8+k	      |
------------------------------------------------------------------------------

  Feedback vertex coordinates are in window coordinates, except	w, which is
  in clip coordinates.	Feedback colors	are lighted, if	lighting is enabled.
  Feedback texture coordinates are generated, if texture coordinate
  generation is	enabled.  They are always transformed by the texture matrix.

Notes

  glFeedbackBuffer, when used in a display list, is not	compiled into the
  display list but rather is executed immediately.

Errors

  GL_INVALID_ENUM is generated if type is not an accepted value.

  GL_INVALID_VALUE is generated	if size	is negative.

  GL_INVALID_OPERATION is generated if glFeedbackBuffer	is called while	the
  render mode is GL_FEEDBACK, or if glRenderMode is called with	argument
  GL_FEEDBACK before glFeedbackBuffer is called	at least once.

  GL_INVALID_OPERATION is generated if glFeedbackBuffer	is executed between
  the execution	of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_RENDER_MODE

See Also

  glBegin, glLineStipple, glPassThrough, glPolygonMode,	glRenderMode,
  glSelectBuffer

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glFinish.3gl

Name

  glFinish - block until all GL	execution is complete

C Specification

  void glFinish( void )

Description

  glFinish does	not return until the effects of	all previously called GL
  commands are complete.  Such effects include all changes to GL state,	all
  changes to connection	state, and all changes to the frame buffer contents.

Notes

  glFinish requires a round trip to the	server.

Errors

  GL_INVALID_OPERATION is generated if glFinish	is executed between the
  execution of	glBegin	and the	corresponding execution	of glEnd.

See Also

  glFlush, glXWaitGL, glXWaitX

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glFlush.3gl

Name

  glFlush - force execution of GL commands in finite time

C Specification

  void glFlush(	void )

Description

  Different GL implementations buffer commands in several different
  locations, including network buffers and the graphics	accelerator itself.
  glFlush empties all of these buffers,	causing	all issued commands to be
  executed as quickly as they are accepted by the actual rendering engine.
  Though this execution	may not	be completed in	any particular time period,
  it does complete in finite time.

  Because any GL program might be executed over	a network, or on an
  accelerator that buffers commands, all programs should call glFlush
  whenever they	count on having	all of their previously	issued commands
  completed.  For example, call	glFlush	before waiting for user	input that
  depends on the generated image.

Notes

  glFlush can return at	any time.  It does not wait until the execution	of
  all previously issued	OpenGL commands	is complete.

Errors

  GL_INVALID_OPERATION is generated if glFlush is executed between the
  execution of	glBegin	and the	corresponding execution	of glEnd.

See Also

  glFinish

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glFog.3gl

Name

  glFogf, glFogi, glFogfv, glFogiv - specify fog parameters

C Specification

  void glFogf( GLenum pname,
           GLfloat param )
  void glFogi( GLenum pname,
           GLint param )

Parameters

  pname	  Specifies a single-valued fog	parameter.  GL_FOG_MODE,
      GL_FOG_DENSITY, GL_FOG_START,	GL_FOG_END, and	GL_FOG_INDEX are
      accepted.

  param	  Specifies the	value that pname will be set to.

C Specification

  void glFogfv(	GLenum pname,
        const GLfloat *params )
  void glFogiv(	GLenum pname,
        const GLint *params )

Parameters

  pname
       Specifies a fog parameter.  GL_FOG_MODE,	GL_FOG_DENSITY,	GL_FOG_START,
       GL_FOG_END, GL_FOG_INDEX, and GL_FOG_COLOR are accepted.

  params
       Specifies the value or values to	be assigned to pname.  GL_FOG_COLOR
       requires	an array of four values.  All other parameters accept an
       array containing	only a single value.

Description

  Fog is enabled and disabled with glEnable and	glDisable using	the argument
  GL_FOG.  While enabled, fog affects rasterized geometry, bitmaps, and	pixel
  blocks, but not buffer clear operations.

  glFog	assigns	the value or values in params to the fog parameter specified
  by pname.  The accepted values for pname are as follows:

  GL_FOG_MODE	      params is	a single integer or floating-point value that
              specifies	the equation to	be used	to compute the fog
              blend factor, f.	Three symbolic constants are
              accepted:	GL_LINEAR, GL_EXP, and GL_EXP2.	 The
              equations	corresponding to these symbolic	constants are
              defined below.  The default fog mode is GL_EXP.

  GL_FOG_DENSITY      params is	a single integer or floating-point value that
              specifies	density, the fog density used in both
              exponential fog equations.  Only nonnegative densities
              are accepted.  The default fog density is	1.0.

  GL_FOG_START	      params is	a single integer or floating-point value that
              specifies	start, the near	distance used in the linear
              fog equation.  The default near distance is 0.0.

  GL_FOG_END	      params is	a single integer or floating-point value that
              specifies	end, the far distance used in the linear fog
              equation.	 The default far distance is 1.0.

  GL_FOG_INDEX	      params is	a single integer or floating-point value that
              specifies	i , the	fog color index.  The default fog
                                 f
              index is 0.0.

  GL_FOG_COLOR	      params contains four integer or floating-point values
              that specify C , the fog color.  Integer values are
                                    f
              mapped linearly such that	the most positive
              representable value maps to 1.0, and the most negative
              representable value maps to -1.0.	 Floating-point
              values are mapped	directly.  After conversion, all
              color components are clamped to the range	[0,1].	The
              default fog color	is (0,0,0,0).

  Fog blends a fog color with each rasterized pixel fragment's posttexturing
  color	using a	blending factor	f.  Factor f is	computed in one	of three
  ways,	depending on the fog mode.  Let	z be the distance in eye coordinates
  from the origin to the fragment being	fogged.	 The equation for GL_LINEAR
  fog is

                  end - z
            f = -----------
                end	- start

  The equation for GL_EXP fog is

                     -(density · z)
                        f = e

  The equation for GL_EXP2 fog is

                                           2
                 -(density · z)
                f = e

  Regardless of	the fog	mode, f	is clamped to the range	[0,1] after it is
  computed.  Then, if the GL is	in RGBA	color mode, the	fragment's color Cr
  is replaced by

                   C' = fC +(1-f)C
                                r     r       f

  In color index mode, the fragment's color index i  is	replaced by
                                                   r

                   i' = i +(1-f)i
                                r    r       f

Errors

  GL_INVALID_ENUM is generated if pname	is not an accepted value, or if	pname
  is GL_FOG_MODE and params is not an accepted value.

  GL_INVALID_VALUE is generated	if pname is GL_FOG_DENSITY and params is
  negative.

  GL_INVALID_OPERATION is generated if glFog is	called between a call to
  glBegin and the corresponding	call to	glEnd.

Associated Gets

  glIsEnabled with argument GL_FOG
  glGet	with argument GL_FOG_COLOR
  glGet	with argument GL_FOG_INDEX
  glGet	with argument GL_FOG_DENSITY
  glGet	with argument GL_FOG_START
  glGet	with argument GL_FOG_END
  glGet	with argument GL_FOG_MODE

See Also

  glEnable

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glFrontFace.3gl

Name

  glFrontFace -	define front- and back-facing polygons

C Specification

  void glFrontFace( GLenum mode	)

Parameters

  mode	Specifies the orientation of front-facing polygons.  GL_CW and GL_CCW
    are accepted.  The default value is GL_CCW.

Description

  In a scene composed entirely of opaque closed	surfaces, back-facing
  polygons are never visible.  Eliminating these invisible polygons has	the
  obvious benefit of speeding up the rendering of the image.  Elimination of
  back-facing polygons is enabled and disabled with glEnable and glDisable
  using	argument GL_CULL_FACE.

  The projection of a polygon to window	coordinates is said to have clockwise
  winding if an	imaginary object following the path from its first vertex,
  its second vertex, and so on,	to its last vertex, and	finally	back to	its
  first	vertex,	moves in a clockwise direction about the interior of the
  polygon.  The	polygon's winding is said to be	counterclockwise if the
  imaginary object following the same path moves in a counterclockwise
  direction about the interior of the polygon.	glFrontFace specifies whether
  polygons with	clockwise winding in window coordinates, or counterclockwise
  winding in window coordinates, are taken to be front-facing.	Passing
  GL_CCW to mode selects counterclockwise polygons as front-facing; GL_CW
  selects clockwise polygons as	front-facing.  By default, counterclockwise
  polygons are taken to	be front-facing.

Errors

  GL_INVALID_ENUM is generated if mode is not an accepted value.

  GL_INVALID_OPERATION is generated if glFrontFace is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_FRONT_FACE

See Also

  glCullFace, glLightModel

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glFrustum.3gl

Name

  glFrustum - multiply the current matrix by a perspective matrix

C Specification

  void glFrustum( GLdouble left,
          GLdouble right,
          GLdouble bottom,
          GLdouble top,
          GLdouble near,
          GLdouble far )

Parameters

  left,	right Specify the coordinates for the left and right vertical
          clipping planes.

  bottom, top Specify the coordinates for the bottom and top horizontal
          clipping planes.

  near,	far   Specify the distances to the near	and far	depth clipping
          planes.  Both distances must be positive.

Description

  glFrustum describes a	perspective matrix that	produces a perspective
  projection.  (left, bottom, -near) and (right, top,  -near) specify the
  points on the	near clipping plane that are mapped to the lower left and
  upper	right corners of the window, respectively, assuming that the eye is
  located at (0, 0, 0).	 -far specifies	the location of	the far	clipping
  plane.  Both near and	far must be positive.  The corresponding matrix	is

           |					   |
           |    2·near  			   |
                   |  ----------                           |
           |  right-left       0       A       0   |
           |					   |
           |	  0	    2·near     B       0   |
           |              ----------               |
           |		  top-bottom		   |
           |	  0	       0       C       D   |
           |					   |
           |	  0	       0       -1      0   |

                    right+left
                A = ----------
                    right-left

                    top+bottom
                B = ----------
                    top-bottom

                      far+near
                C = - --------
                      far-near

                      2·far·near
                D = - ----------
                       far-near

  The current matrix is	multiplied by this matrix with the result replacing
  the current matrix.  That is,	if M is	the current matrix and F is the
  frustum perspective matrix, then M is	replaced with M·F.

  Use glPushMatrix and glPopMatrix to save and restore the current matrix
  stack.

Notes

  Depth	buffer precision is affected by	the values specified for near and
  far.	The greater the	ratio of far to	near is, the less effective the	depth
  buffer will be at distinguishing between surfaces that are near each other.
  If

                       far
                   r = ----
                       near

  roughly log  r bits of depth buffer precision are lost.  Because r
             2
  approaches infinity as near approaches zero, near must never be set to
  zero.

Errors

  GL_INVALID_VALUE is generated	if near	or far is not positive.

  GL_INVALID_OPERATION is generated if glFrustum is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_MATRIX_MODE
  glGet	with argument GL_MODELVIEW_MATRIX
  glGet	with argument GL_PROJECTION_MATRIX
  glGet	with argument GL_TEXTURE_MATRIX

See Also

  glOrtho, glMatrixMode, glMultMatrix, glPushMatrix, glViewport

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGenLists.3gl

Name

  glGenLists - generate	a contiguous set of empty display lists

C Specification

  GLuint glGenLists( GLsizei range )

Parameters

  range	 Specifies the number of contiguous empty display lists	to be
     generated.

Description

  glGenLists has one argument, range.  It returns an integer n such that
  range	contiguous empty display lists,	named n, n+1, ..., n+range -1, are
  created.  If range is	zero, if there is no group of range contiguous names
  available, or	if any error is	generated, no display lists are	generated,
  and zero is returned.

Errors

  GL_INVALID_VALUE is generated	if range is negative.

  GL_INVALID_OPERATION is generated if glGenLists is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glIsList

See Also

  glCallList, glCallLists, glDeleteLists, glNewList

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGet.3gl

Name

  glGetBooleanv, glGetDoublev, glGetFloatv, glGetIntegerv - return the value
  or values of a selected parameter

C Specification

  void glGetBooleanv( GLenum pname,
              GLboolean	*params	)

C Specification

  void glGetDoublev( GLenum pname,
             GLdouble *params )

C Specification

  void glGetFloatv( GLenum pname,
            GLfloat *params )

C Specification

  void glGetIntegerv( GLenum pname,
              GLint *params )

Parameters

  pname	  Specifies the	parameter value	to be returned.	 The symbolic
      constants in the list	below are accepted.

  params  Returns the value or values of the specified parameter.

Description

  These	four commands return values for	simple state variables in GL. pname
  is a symbolic	constant indicating the	state variable to be returned, and
  params is a pointer to an array of the indicated type	in which to place the
  returned data.

  Type conversion is performed if params has a different type than the state
  variable value being requested.  If glGetBooleanv is called, a floating-
  point	or integer value is converted to GL_FALSE if and only if it is zero.
  Otherwise, it	is converted to	GL_TRUE.  If glGetIntegerv is called, Boolean
  values are returned as GL_TRUE or GL_FALSE, and most floating-point values
  are rounded to the nearest integer value.  Floating-point colors and
  normals, however, are	returned with a	linear mapping that maps 1.0 to	the
  most positive	representable integer value, and -1.0 to the most negative
  representable	integer	value.	If glGetFloatv or glGetDoublev is called,
  Boolean values are returned as GL_TRUE or GL_FALSE, and integer values are
  converted to floating-point values.

  The following	symbolic constants are accepted	by pname:

  GL_ACCUM_ALPHA_BITS	   params returns one value, the number	of alpha
               bitplanes in	the accumulation buffer.

  GL_ACCUM_BLUE_BITS	   params returns one value, the number	of blue
               bitplanes in	the accumulation buffer.

  GL_ACCUM_CLEAR_VALUE	   params returns four values: the red,	green, blue,
               and alpha values used to clear the accumulation
               buffer.  Integer values, if requested, are
               linearly mapped from	the internal floating-point
               representation such that 1.0	returns	the most
               positive representable integer value, and -1.0
               returns the most negative representable integer
               value.  See glClearAccum.

  GL_ACCUM_GREEN_BITS	   params returns one value, the number	of green
               bitplanes in	the accumulation buffer.

  GL_ACCUM_RED_BITS	   params returns one value, the number	of red
               bitplanes in	the accumulation buffer.

  GL_ALPHA_BIAS		   params returns one value, the alpha bias factor
               used	during pixel transfers.	 See glPixelTransfer.

  GL_ALPHA_BITS		   params returns one value, the number	of alpha
               bitplanes in	each color buffer.

  GL_ALPHA_SCALE	   params returns one value, the alpha scale factor
               used	during pixel transfers.	 See glPixelTransfer.

  GL_ALPHA_TEST		   params returns a single Boolean value indicating
               whether alpha testing of fragments is enabled.
               See glAlphaFunc.

  GL_ALPHA_TEST_FUNC	   params returns one value, the symbolic name of the
               alpha test function.	 See glAlphaFunc.

  GL_ALPHA_TEST_REF	   params returns one value, the reference value for
               the alpha test.  See	glAlphaFunc.  An integer
               value, if requested,	is linearly mapped from	the
               internal floating-point representation such that
               1.0 returns the most	positive representable
               integer value, and -1.0 returns the most negative
               representable integer value.

  GL_ATTRIB_STACK_DEPTH	   params returns one value, the depth of the
               attribute stack.  If	the stack is empty, zero is
               returned.  See glPushAttrib.

  GL_AUTO_NORMAL	   params returns a single Boolean value indicating
               whether 2-D map evaluation automatically generates
               surface normals.  See glMap2.

  GL_AUX_BUFFERS	   params returns one value, the number	of auxiliary
               color buffers.

  GL_BLEND		   params returns a single Boolean value indicating
               whether blending is enabled.	 See glBlendFunc.

  GL_BLEND_DST		   params returns one value, the symbolic constant
               identifying the destination blend function.	See
               glBlendFunc.

  GL_BLEND_SRC		   params returns one value, the symbolic constant
               identifying the source blend	function.  See
               glBlendFunc.

  GL_BLUE_BIAS		   params returns one value, the blue bias factor
               used	during pixel transfers.	 See glPixelTransfer.

  GL_BLUE_BITS		   params returns one value, the number	of blue
               bitplanes in	each color buffer.

  GL_BLUE_SCALE		   params returns one value, the blue scale factor
               used	during pixel transfers.	 See glPixelTransfer.

  GL_CLIP_PLANEi	   params returns a single Boolean value indicating
               whether the specified clipping plane	is enabled.
               See glClipPlane.

  GL_COLOR_ARRAY_EXT	   params returns a single boolean value, indicating
               whether the color array is enabled.	See
               glColorPointerEXT.

  GL_COLOR_ARRAY_COUNT_EXT params returns one value, the number	of colors in
               the color array, counting from the first, that are
               static.  See	glColorPointerEXT.

  GL_COLOR_ARRAY_SIZE_EXT  params returns one value, the number	of components
               per color in	the color array.  See
               glColorPointerEXT.

  GL_COLOR_ARRAY_STRIDE_EXT
               params returns one value, the byte offset between
               consecutive colors in the color array.  See
               glColorPointerEXT.

  GL_COLOR_ARRAY_TYPE_EXT  params returns one value, the data type of each
               component in	the color array.  See
               glColorPointerEXT.

  GL_COLOR_CLEAR_VALUE	   params returns four values: the red,	green, blue,
               and alpha values used to clear the color buffers.
               Integer values, if requested, are linearly mapped
               from	the internal floating-point representation
               such	that 1.0 returns the most positive
               representable integer value,	and -1.0 returns the
               most	negative representable integer value.  See
               glClearColor.

  GL_COLOR_MATERIAL	   params returns a single Boolean value indicating
               whether one or more material	parameters are
               tracking the	current	color.	See glColorMaterial.

  GL_COLOR_MATERIAL_FACE   params returns one value, a symbolic	constant
               indicating which materials have a parameter that
               is tracking the current color.  See
               glColorMaterial.

  GL_COLOR_MATERIAL_PARAMETER
               params returns one value, a symbolic	constant
               indicating which material parameters	are tracking
               the current color.  See glColorMaterial.

  GL_COLOR_WRITEMASK	   params returns four Boolean values: the red,
               green, blue,	and alpha write	enables	for the	color
               buffers.  See glColorMask.

  GL_CULL_FACE		   params returns a single Boolean value indicating
               whether polygon culling is enabled.	See
               glCullFace.

  GL_CULL_FACE_MODE	   params returns one value, a symbolic	constant
               indicating which polygon faces are to be culled.
               See glCullFace.

  GL_CURRENT_COLOR	   params returns four values: the red,	green, blue,
               and alpha values of the current color.  Integer
               values, if requested, are linearly mapped from the
               internal floating-point representation such that
               1.0 returns the most	positive representable
               integer value, and -1.0 returns the most negative
               representable integer value.	 See glColor.

  GL_CURRENT_INDEX	   params returns one value, the current color index.
               See glIndex.

  GL_CURRENT_NORMAL	   params returns three	values:	the x, y, and z
               values of the current normal.  Integer values, if
               requested, are linearly mapped from the internal
               floating-point representation such that 1.0
               returns the most positive representable integer
               value, and -1.0 returns the most negative
               representable integer value.	 See glNormal.

  GL_CURRENT_RASTER_COLOR  params returns four values: the red,	green, blue,
               and alpha values of the current raster position.
               Integer values, if requested, are linearly mapped
               from	the internal floating-point representation
               such	that 1.0 returns the most positive
               representable integer value,	and -1.0 returns the
               most	negative representable integer value.  See
               glRasterPos.

  GL_CURRENT_RASTER_DISTANCE
               params returns one value, the distance from the
               eye to the current raster position.	See
               glRasterPos.

  GL_CURRENT_RASTER_INDEX  params returns one value, the color index of	the
               current raster position.  See glRasterPos.

  GL_CURRENT_RASTER_POSITION
               params returns four values: the x, y, z, and	w
               components of the current raster position.  x, y,
               and z are in	window coordinates, and	w is in	clip
               coordinates.	 See glRasterPos.

  GL_CURRENT_RASTER_TEXTURE_COORDS
               params returns four values: the s, t, r, and	q
               current raster texture coordinates.	See
               glRasterPos and glTexCoord.

  GL_CURRENT_RASTER_POSITION_VALID
               params returns a single Boolean value indicating
               whether the current raster position is valid.  See
               glRasterPos.

  GL_CURRENT_TEXTURE_COORDS
               params returns four values: the s, t, r, and	q
               current texture coordinates.	 See glTexCoord.

  GL_DEPTH_BIAS		   params returns one value, the depth bias factor
               used	during pixel transfers.	 See glPixelTransfer.

  GL_DEPTH_BITS		   params returns one value, the number	of bitplanes
               in the depth	buffer.

  GL_DEPTH_CLEAR_VALUE	   params returns one value, the value that is used
               to clear the	depth buffer.  Integer values, if
               requested, are linearly mapped from the internal
               floating-point representation such that 1.0
               returns the most positive representable integer
               value, and -1.0 returns the most negative
               representable integer value.	 See glClearDepth.

  GL_DEPTH_FUNC		   params returns one value, the symbolic constant
               that	indicates the depth comparison function.  See
               glDepthFunc.

  GL_DEPTH_RANGE	   params returns two values: the near and far
               mapping limits for the depth	buffer.	 Integer
               values, if requested, are linearly mapped from the
               internal floating-point representation such that
               1.0 returns the most	positive representable
               integer value, and -1.0 returns the most negative
               representable integer value.	 See glDepthRange.

  GL_DEPTH_SCALE	   params returns one value, the depth scale factor
               used	during pixel transfers.	 See glPixelTransfer.

  GL_DEPTH_TEST		   params returns a single Boolean value indicating
               whether depth testing of fragments is enabled.
               See glDepthFunc and glDepthRange.

  GL_DEPTH_WRITEMASK	   params returns a single Boolean value indicating
               if the depth	buffer is enabled for writing.	See
               glDepthMask.

  GL_DITHER		   params returns a single Boolean value indicating
               whether dithering of	fragment colors	and indices
               is enabled.

  GL_DOUBLEBUFFER	   params returns a single Boolean value indicating
               whether double buffering is supported.

  GL_DRAW_BUFFER	   params returns one value, a symbolic	constant
               indicating which buffers are	being drawn to.	 See
               glDrawBuffer.

  GL_EDGE_FLAG		   params returns a single Boolean value indication
               whether the current edge flag is true or false.
               See glEdgeFlag.

  GL_EDGE_FLAG_ARRAY_EXT   params returns a single boolean value, indicating
               whether the edge flag array is enabled.  See
               glEdgeFlagPointerEXT.

  GL_EDGE_FLAG_ARRAY_COUNT_EXT
               params returns one value, the number	of edge	flags
               in the edge flag array, counting from the first,
               that	are static.  See glEdgeFlagPointerEXT.

  GL_EDGE_FLAG_ARRAY_STRIDE_EXT
               params returns one value, the byte offset between
               consecutive edge flags in the edge flag array.
               See glEdgeFlagPointerEXT.

  GL_FOG		   params returns a single Boolean value indicating
               whether fogging is enabled.	See glFog.

  GL_FOG_COLOR		   params returns four values: the red,	green, blue,
               and alpha components	of the fog color.  Integer
               values, if requested, are linearly mapped from the
               internal floating-point representation such that
               1.0 returns the most	positive representable
               integer value, and -1.0 returns the most negative
               representable integer value.	 See glFog.

  GL_FOG_DENSITY	   params returns one value, the fog density
               parameter.  See glFog.

  GL_FOG_END		   params returns one value, the end factor for	the
               linear fog equation.	 See glFog.

  GL_FOG_HINT		   params returns one value, a symbolic	constant
               indicating the mode of the fog hint.	 See glHint.

  GL_FOG_INDEX		   params returns one value, the fog color index.
               See glFog.

  GL_FOG_MODE		   params returns one value, a symbolic	constant
               indicating which fog	equation is selected.  See
               glFog.

  GL_FOG_START		   params returns one value, the start factor for the
               linear fog equation.	 See glFog.

  GL_FRONT_FACE		   params returns one value, a symbolic	constant
               indicating whether clockwise	or counterclockwise
               polygon winding is treated as front-facing.	See
               glFrontFace.

  GL_GREEN_BIAS		   params returns one value, the green bias factor
               used	during pixel transfers.

  GL_GREEN_BITS		   params returns one value, the number	of green
               bitplanes in	each color buffer.

  GL_GREEN_SCALE	   params returns one value, the green scale factor
               used	during pixel transfers.	 See glPixelTransfer.

  GL_INDEX_ARRAY_EXT	   params returns a single boolean value, indicating
               whether the color index array is enabled.  See
               glIndexPointerEXT.

  GL_INDEX_ARRAY_COUNT_EXT params returns one value, the number	of color
               indexes in the color	index array, counting from
               the first, that are static.	See
               glIndexPointerEXT.

  GL_INDEX_ARRAY_STRIDE_EXT
               params returns one value, the byte offset between
               consecutive color indexes in	the color index
               array.  See glIndexPointerEXT.

  GL_INDEX_ARRAY_TYPE_EXT  params returns one value, the data type of indexes
               in the color	index array.  See glIndexPointerEXT.

  GL_INDEX_BITS		   params returns one value, the number	of bitplanes
               in each color index buffer.

  GL_INDEX_CLEAR_VALUE	   params returns one value, the color index used to
               clear the color index buffers.  See glClearIndex.

  GL_INDEX_MODE		   params returns a single Boolean value indicating
               whether the GL is in	color index mode (true)	or
               RGBA	mode (false).

  GL_INDEX_OFFSET	   params returns one value, the offset	added to
               color and stencil indices during pixel transfers.
               See glPixelTransfer.

  GL_INDEX_SHIFT	   params returns one value, the amount	that color
               and stencil indices are shifted during pixel
               transfers.  See glPixelTransfer.

  GL_INDEX_WRITEMASK	   params returns one value, a mask indicating which
               bitplanes of	each color index buffer	can be
               written.  See glIndexMask.

  GL_LIGHTi		   params returns a single Boolean value indicating
               whether the specified light is enabled.  See
               glLight and glLightModel.

  GL_LIGHTING		   params returns a single Boolean value indicating
               whether lighting is enabled.	 See glLightModel.

  GL_LIGHT_MODEL_AMBIENT   params returns four values: the red,	green, blue,
               and alpha components	of the ambient intensity of
               the entire scene.  Integer values, if requested,
               are linearly	mapped from the	internal floating-
               point representation	such that 1.0 returns the
               most	positive representable integer value, and
               -1.0	returns	the most negative representable
               integer value.  See glLightModel.

  GL_LIGHT_MODEL_LOCAL_VIEWER
               params returns a single Boolean value indicating
               whether specular reflection calculations treat the
               viewer as being local to the	scene.	See
               glLightModel.

  GL_LIGHT_MODEL_TWO_SIDE  params returns a single Boolean value indicating
               whether separate materials are used to compute
               lighting for	front- and back-facing polygons.  See
               glLightModel.

  GL_LINE_SMOOTH	   params returns a single Boolean value indicating
               whether antialiasing	of lines is enabled.  See
               glLineWidth.

  GL_LINE_SMOOTH_HINT	   params returns one value, a symbolic	constant
               indicating the mode of the line antialiasing	hint.
               See glHint.

  GL_LINE_STIPPLE	   params returns a single Boolean value indicating
               whether stippling of	lines is enabled.  See
               glLineStipple.

  GL_LINE_STIPPLE_PATTERN  params returns one value, the 16-bit	line stipple
               pattern.  See glLineStipple.

  GL_LINE_STIPPLE_REPEAT   params returns one value, the line stipple repeat
               factor.  See	glLineStipple.

  GL_LINE_WIDTH		   params returns one value, the line width as
               specified with glLineWidth.

  GL_LINE_WIDTH_GRANULARITY
               params returns one value, the width difference
               between adjacent supported widths for antialiased
               lines.  See glLineWidth.

  GL_LINE_WIDTH_RANGE	   params returns two values: the smallest and
               largest supported widths for	antialiased lines.
               See glLineWidth.

  GL_LIST_BASE		   params returns one value, the base offset added to
               all names in	arrays presented to glCallLists.  See
               glListBase.

  GL_LIST_INDEX		   params returns one value, the name of the display
               list	currently under	construction.  Zero is
               returned if no display list is currently under
               construction.  See glNewList.

  GL_LIST_MODE		   params returns one value, a symbolic	constant
               indicating the construction mode of the display
               list	currently being	constructed.  See glNewList.

  GL_LOGIC_OP		   params returns a single Boolean value indicating
               whether fragment indexes are	merged into the
               framebuffer using a logical operation.  See
               glLogicOp.

  GL_LOGIC_OP_MODE	   params returns one value, a symbolic	constant
               indicating the selected logic operational mode.
               See glLogicOp.

  GL_MAP1_COLOR_4	   params returns a single Boolean value indicating
               whether 1D evaluation generates colors.  See
               glMap1.

  GL_MAP1_GRID_DOMAIN	   params returns two values: the endpoints of the
               1-D map's grid domain.  See glMapGrid.

  GL_MAP1_GRID_SEGMENTS	   params returns one value, the number	of partitions
               in the 1-D map's grid domain.  See glMapGrid.

  GL_MAP1_INDEX		   params returns a single Boolean value indicating
               whether 1D evaluation generates color indices.
               See glMap1.

  GL_MAP1_NORMAL	   params returns a single Boolean value indicating
               whether 1D evaluation generates normals.  See
               glMap1.

  GL_MAP1_TEXTURE_COORD_1  params returns a single Boolean value indicating
               whether 1D evaluation generates 1D texture
               coordinates.	 See glMap1.

  GL_MAP1_TEXTURE_COORD_2  params returns a single Boolean value indicating
               whether 1D evaluation generates 2D texture
               coordinates.	 See glMap1.

  GL_MAP1_TEXTURE_COORD_3  params returns a single Boolean value indicating
               whether 1D evaluation generates 3D texture
               coordinates.	 See glMap1.

  GL_MAP1_TEXTURE_COORD_4  params returns a single Boolean value indicating
               whether 1D evaluation generates 4D texture
               coordinates.	 See glMap1.

  GL_MAP1_VERTEX_3	   params returns a single Boolean value indicating
               whether 1D evaluation generates 3D vertex
               coordinates.	 See glMap1.

  GL_MAP1_VERTEX_4	   params returns a single Boolean value indicating
               whether 1D evaluation generates 4D vertex
               coordinates.	 See glMap1.

  GL_MAP2_COLOR_4	   params returns a single Boolean value indicating
               whether 2D evaluation generates colors.  See
               glMap2.

  GL_MAP2_GRID_DOMAIN	   params returns four values: the endpoints of	the
               2-D map's i and j grid domains.  See	glMapGrid.

  GL_MAP2_GRID_SEGMENTS	   params returns two values: the number of
               partitions in the 2-D map's i and j grid domains.
               See glMapGrid.

  GL_MAP2_INDEX		   params returns a single Boolean value indicating
               whether 2D evaluation generates color indices.
               See glMap2.

  GL_MAP2_NORMAL	   params returns a single Boolean value indicating
               whether 2D evaluation generates normals.  See
               glMap2.

  GL_MAP2_TEXTURE_COORD_1  params returns a single Boolean value indicating
               whether 2D evaluation generates 1D texture
               coordinates.	 See glMap2.

  GL_MAP2_TEXTURE_COORD_2  params returns a single Boolean value indicating
               whether 2D evaluation generates 2D texture
               coordinates.	 See glMap2.

  GL_MAP2_TEXTURE_COORD_3  params returns a single Boolean value indicating
               whether 2D evaluation generates 3D texture
               coordinates.	 See glMap2.

  GL_MAP2_TEXTURE_COORD_4  params returns a single Boolean value indicating
               whether 2D evaluation generates 4D texture
               coordinates.	 See glMap2.

  GL_MAP2_VERTEX_3	   params returns a single Boolean value indicating
               whether 2D evaluation generates 3D vertex
               coordinates.	 See glMap2.

  GL_MAP2_VERTEX_4	   params returns a single Boolean value indicating
               whether 2D evaluation generates 4D vertex
               coordinates.	 See glMap2.

  GL_MAP_COLOR		   params returns a single Boolean value indicating
               if colors and color indices are to be replaced by
               table lookup	during pixel transfers.	 See
               glPixelTransfer.

  GL_MAP_STENCIL	   params returns a single Boolean value indicating
               if stencil indices are to be	replaced by table
               lookup during pixel transfers.  See
               glPixelTransfer.

  GL_MATRIX_MODE	   params returns one value, a symbolic	constant
               indicating which matrix stack is currently the
               target of all matrix	operations.  See
               glMatrixMode.

  GL_MAX_ATTRIB_STACK_DEPTH
               params returns one value, the maximum supported
               depth of the	attribute stack.  See glPushAttrib.

  GL_MAX_CLIP_PLANES	   params returns one value, the maximum number	of
               application-defined clipping	planes.	 See
               glClipPlane.

  GL_MAX_EVAL_ORDER	   params returns one value, the maximum equation
               order supported by 1-D and 2-D evaluators.  See
               glMap1 and glMap2.

  GL_MAX_LIGHTS		   params returns one value, the maximum number	of
               lights.  See	glLight.

  GL_MAX_LIST_NESTING	   params returns one value, the maximum recursion
               depth allowed during	display-list traversal.	 See
               glCallList.

  GL_MAX_MODELVIEW_STACK_DEPTH
               params returns one value, the maximum supported
               depth of the	modelview matrix stack.	 See
               glPushMatrix.

  GL_MAX_NAME_STACK_DEPTH  params returns one value, the maximum supported
               depth of the	selection name stack.  See
               glPushName.

  GL_MAX_PIXEL_MAP_TABLE   params returns one value, the maximum supported
               size	of a glPixelMap	lookup table.  See
               glPixelMap.

  GL_MAX_PROJECTION_STACK_DEPTH
               params returns one value, the maximum supported
               depth of the	projection matrix stack.  See
               glPushMatrix.

  GL_MAX_TEXTURE_SIZE	   params returns one value, the maximum width or
               height of any texture image (without	borders).
               See glTexImage1D and	glTexImage2D.

  GL_MAX_TEXTURE_STACK_DEPTH
               params returns one value, the maximum supported
               depth of the	texture	matrix stack.  See
               glPushMatrix.

  GL_MAX_VIEWPORT_DIMS	   params returns two values: the maximum supported
               width and height of the viewport.  See glViewport.

  GL_MODELVIEW_MATRIX	   params returns sixteen values: the modelview
               matrix on the top of	the modelview matrix stack.
               See glPushMatrix.

  GL_MODELVIEW_STACK_DEPTH params returns one value, the number	of matrices
               on the modelview matrix stack.  See glPushMatrix.

  GL_NAME_STACK_DEPTH	   params returns one value, the number	of names on
               the selection name stack.  See glPushMatrix.

  GL_NORMAL_ARRAY_EXT	   params returns a single boolean value, indicating
               whether the normal array is enabled.	 See
               glNormalPointerEXT.

  GL_NORMAL_ARRAY_COUNT_EXT
               params returns one value, the number	of normals in
               the normal array, counting from the first, that
               are static.	See glNormalPointerEXT.

  GL_NORMAL_ARRAY_STRIDE_EXT
               params returns one value, the byte offset between
               consecutive normals in the normal array.  See
               glNormalPointerEXT.

  GL_NORMAL_ARRAY_TYPE_EXT params returns one value, the data type of each
               coordinate in the normal array.  See
               glNormalPointerEXT.

  GL_NORMALIZE		   params returns a single Boolean value indicating
               whether normals are automatically scaled to unit
               length after	they have been transformed to eye
               coordinates.	 See glNormal.

  GL_PACK_ALIGNMENT	   params returns one value, the byte alignment	used
               for writing pixel data to memory.  See
               glPixelStore.

  GL_PACK_LSB_FIRST	   params returns a single Boolean value indicating
               whether single-bit pixels being written to memory
               are written first to	the least significant bit of
               each	unsigned byte.	See glPixelStore.

  GL_PACK_ROW_LENGTH	   params returns one value, the row length used for
               writing pixel data to memory.  See glPixelStore.

  GL_PACK_SKIP_PIXELS	   params returns one value, the number	of pixel
               locations skipped before the	first pixel is
               written into	memory.	 See glPixelStore.

  GL_PACK_SKIP_ROWS	   params returns one value, the number	of rows	of
               pixel locations skipped before the first pixel is
               written into	memory.	 See glPixelStore.

  GL_PACK_SWAP_BYTES	   params returns a single Boolean value indicating
               whether the bytes of	two-byte and four-byte pixel
               indices and components are swapped before being
               written to memory.  See glPixelStore.

  GL_PERSPECTIVE_CORRECTION_HINT
               params returns one value, a symbolic	constant
               indicating the mode of the perspective correction
               hint.  See glHint.

  GL_PIXEL_MAP_A_TO_A_SIZE params returns one value, the size of the alpha-
               to-alpha pixel translation table.  See glPixelMap.

  GL_PIXEL_MAP_B_TO_B_SIZE params returns one value, the size of the blue-
               to-blue pixel translation table.  See glPixelMap.

  GL_PIXEL_MAP_G_TO_G_SIZE params returns one value, the size of the green-
               to-green pixel translation table.  See glPixelMap.

  GL_PIXEL_MAP_I_TO_A_SIZE params returns one value, the size of the index-
               to-alpha pixel translation table.  See glPixelMap.

  GL_PIXEL_MAP_I_TO_B_SIZE params returns one value, the size of the index-
               to-blue pixel translation table.  See glPixelMap.

  GL_PIXEL_MAP_I_TO_G_SIZE params returns one value, the size of the index-
               to-green pixel translation table.  See glPixelMap.

  GL_PIXEL_MAP_I_TO_I_SIZE params returns one value, the size of the index-
               to-index pixel translation table.  See glPixelMap.

  GL_PIXEL_MAP_I_TO_R_SIZE params returns one value, the size of the index-
               to-red pixel	translation table.  See	glPixelMap.

  GL_PIXEL_MAP_R_TO_R_SIZE params returns one value, the size of the red-to-
               red pixel translation table.	 See glPixelMap.

  GL_PIXEL_MAP_S_TO_S_SIZE params returns one value, the size of the
               stencil-to-stencil pixel translation	table.	See
               glPixelMap.

  GL_POINT_SIZE		   params returns one value, the point size as
               specified by	glPointSize.

  GL_POINT_SIZE_GRANULARITY
               params returns one value, the size difference
               between adjacent supported sizes for	antialiased
               points.  See	glPointSize.

  GL_POINT_SIZE_RANGE	   params returns two values: the smallest and
               largest supported sizes for antialiased points.
               See glPointSize.

  GL_POINT_SMOOTH	   params returns a single Boolean value indicating
               whether antialiasing	of points is enabled.  See
               glPointSize.

  GL_POINT_SMOOTH_HINT	   params returns one value, a symbolic	constant
               indicating the mode of the point antialiasing
               hint.  See glHint.

  GL_POLYGON_MODE	   params returns two values: symbolic constants
               indicating whether front-facing and back-facing
               polygons are	rasterized as points, lines, or
               filled polygons.  See glPolygonMode.

  GL_POLYGON_SMOOTH	   params returns a single Boolean value indicating
               whether antialiasing	of polygons is enabled.	 See
               glPolygonMode.

  GL_POLYGON_SMOOTH_HINT   params returns one value, a symbolic	constant
               indicating the mode of the polygon antialiasing
               hint.  See glHint.

  GL_POLYGON_STIPPLE	   params returns a single Boolean value indicating
               whether stippling of	polygons is enabled.  See
               glPolygonStipple.

  GL_PROJECTION_MATRIX	   params returns sixteen values: the projection
               matrix on the top of	the projection matrix stack.
               See glPushMatrix.

  GL_PROJECTION_STACK_DEPTH
               params returns one value, the number	of matrices
               on the projection matrix stack.  See	glPushMatrix.

  GL_READ_BUFFER	   params returns one value, a symbolic	constant
               indicating which color buffer is selected for
               reading.
               See glReadPixels and	glAccum.

  GL_RED_BIAS		   params returns one value, the red bias factor used
               during pixel	transfers.

  GL_RED_BITS		   params returns one value, the number	of red
               bitplanes in	each color buffer.

  GL_RED_SCALE		   params returns one value, the red scale factor
               used	during pixel transfers.	 See glPixelTransfer.

  GL_RENDER_MODE	   params returns one value, a symbolic	constant
               indicating whether the GL is	in render, select, or
               feedback mode.  See glRenderMode.

  GL_RGBA_MODE		   params returns a single Boolean value indicating
               whether the GL is in	RGBA mode (true) or color
               index mode (false).	See glColor.

  GL_SCISSOR_BOX	   params returns four values: the x and y window
               coordinates of the scissor box, followed by its
               width and height.  See glScissor.

  GL_SCISSOR_TEST	   params returns a single Boolean value indicating
               whether scissoring is enabled.  See glScissor.

  GL_SHADE_MODEL	   params returns one value, a symbolic	constant
               indicating whether the shading mode is flat or
               smooth.  See	glShadeModel.

  GL_STENCIL_BITS	   params returns one value, the number	of bitplanes
               in the stencil buffer.

  GL_STENCIL_CLEAR_VALUE   params returns one value, the index to which	the
               stencil bitplanes are cleared.  See
               glClearStencil.

  GL_STENCIL_FAIL	   params returns one value, a symbolic	constant
               indicating what action is taken when	the stencil
               test	fails.	See glStencilOp.

  GL_STENCIL_FUNC	   params returns one value, a symbolic	constant
               indicating what function is used to compare the
               stencil reference value with	the stencil buffer
               value.  See glStencilFunc.

  GL_STENCIL_PASS_DEPTH_FAIL
               params returns one value, a symbolic	constant
               indicating what action is taken when	the stencil
               test	passes,	but the	depth test fails.  See
               glStencilOp.

  GL_STENCIL_PASS_DEPTH_PASS
               params returns one value, a symbolic	constant
               indicating what action is taken when	the stencil
               test	passes and the depth test passes.  See
               glStencilOp.

  GL_STENCIL_REF	   params returns one value, the reference value that
               is compared with the	contents of the	stencil
               buffer.  See	glStencilFunc.

  GL_STENCIL_TEST	   params returns a single Boolean value indicating
               whether stencil testing of fragments	is enabled.
               See glStencilFunc and glStencilOp.

  GL_STENCIL_VALUE_MASK	   params returns one value, the mask that is used to
               mask	both the stencil reference value and the
               stencil buffer value	before they are	compared.
               See glStencilFunc.

  GL_STENCIL_WRITEMASK	   params returns one value, the mask that controls
               writing of the stencil bitplanes.  glStencilMask.

  GL_STEREO		   params returns a single Boolean value indicating
               whether stereo buffers (left	and right) are
               supported.

  GL_SUBPIXEL_BITS	   params returns one value, an	estimate of the
               number of bits of subpixel resolution that are
               used	to position rasterized geometry	in window
               coordinates.

  GL_TEXTURE_1D		   params returns a single Boolean value indicating
               whether 1D texture mapping is enabled.  See
               glTexImage1D.

  GL_TEXTURE_2D		   params returns a single Boolean value indicating
               whether 2D texture mapping is enabled.  See
               glTexImage2D.

  GL_TEXTURE_COORD_ARRAY_EXT
               params returns a single boolean value, indicating
               whether the texture coordinate array	is enabled.
               See glTexCoordPointerEXT.

  GL_TEXTURE_COORD_ARRAY_COUNT_EXT
               params returns one value, the number	of elements
               in the texture coordinate array, counting from the
               first, that are static.  See	glTexCoordPointerEXT.

  GL_TEXTURE_COORD_ARRAY_SIZE_EXT
               params returns one value, the number	of
               coordinates per element in the texture coordinate
               array.  See glTexCoordPointerEXT.

  GL_TEXTURE_COORD_ARRAY_STRIDE_EXT
               params returns one value, the byte offset between
               consecutive elements	in the texture coordinate
               array.  See glTexCoordPointerEXT.

  GL_TEXTURE_COORD_ARRAY_TYPE_EXT
               params returns one value, the data type of the
               coordinates in the texture coordinate array.	 See
               glTexCoordPointerEXT.

  GL_TEXTURE_GEN_S	   params returns a single Boolean value indicating
               whether automatic generation	of the S texture
               coordinate is enabled.  See glTexGen.

  GL_TEXTURE_GEN_T	   params returns a single Boolean value indicating
               whether automatic generation	of the T texture
               coordinate is enabled.  See glTexGen.

  GL_TEXTURE_GEN_R	   params returns a single Boolean value indicating
               whether automatic generation	of the R texture
               coordinate is enabled.  See glTexGen.

  GL_TEXTURE_GEN_Q	   params returns a single Boolean value indicating
               whether automatic generation	of the Q texture
               coordinate is enabled.  See glTexGen.

  GL_TEXTURE_MATRIX	   params returns sixteen values: the texture matrix
               on the top of the texture matrix stack.  See
               glPushMatrix.

  GL_TEXTURE_STACK_DEPTH   params returns one value, the number	of matrices
               on the texture matrix stack.	 See glPushMatrix.

  GL_UNPACK_ALIGNMENT	   params returns one value, the byte alignment	used
               for reading pixel data from memory.	See
               glPixelStore.

  GL_UNPACK_LSB_FIRST	   params returns a single Boolean value indicating
               whether single-bit pixels being read	from memory
               are read first from the least significant bit of
               each	unsigned byte.	See glPixelStore.

  GL_UNPACK_ROW_LENGTH	   params returns one value, the row length used for
               reading pixel data from memory.  See	glPixelStore.

  GL_UNPACK_SKIP_PIXELS	   params returns one value, the number	of pixel
               locations skipped before the	first pixel is read
               from	memory.	 See glPixelStore.

  GL_UNPACK_SKIP_ROWS	   params returns one value, the number	of rows	of
               pixel locations skipped before the first pixel is
               read	from memory.  See glPixelStore.

  GL_UNPACK_SWAP_BYTES	   params returns a single Boolean value indicating
               whether the bytes of	two-byte and four-byte pixel
               indices and components are swapped after being
               read	from memory.  See glPixelStore.

  GL_VERTEX_ARRAY_EXT	   params returns a single boolean value, indicating
               whether the vertex array is enabled.	 See
               glVertexPointerEXT.

  GL_VERTEX_ARRAY_COUNT_EXT
               params returns one value, the number	of vertexes
               in the vertex array,	counting from the first, that
               are static.	See glVertexPointerEXT.

  GL_VERTEX_ARRAY_SIZE_EXT params returns one value, the number	of
               coordinates per vertex in the vertex	array.	See
               glVertexPointerEXT.

  GL_VERTEX_ARRAY_STRIDE_EXT
               params returns one value, the byte offset between
               consecutive vertexes	in the vertex array.  See
               glVertexPointerEXT.

  GL_VERTEX_ARRAY_TYPE_EXT params returns one value, the data type of each
               coordinate in the vertex array.  See
               glVertexPointerEXT.

  GL_VIEWPORT		   params returns four values: the x and y window
               coordinates of the viewport,	followed by its	width
               and height.	See glViewport.

  GL_ZOOM_X		   params returns one value, the x pixel zoom factor.
               See glPixelZoom.

  GL_ZOOM_Y		   params returns one value, the y pixel zoom factor.
               See glPixelZoom.

  Many of the Boolean parameters can also be queried more easily using
  glIsEnabled.

Errors

  GL_INVALID_ENUM is generated if pname	is not an accepted value.

  GL_INVALID_OPERATION is generated if glGet is	executed between the
  execution of glBegin and the corresponding execution of glEnd.

See Also

  glGetClipPlane, glGetError, glGetLight, glGetMap, glGetMaterial,
  glGetPixelMap, glGetPolygonStipple, glGetString, glGetTexEnv,	glGetTexGen,
  glGetTexImage, glGetTexLevelParameter, glGetTexParameter, glIsEnabled

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGetClipPlane.3gl

Name

  glGetClipPlane - return the coefficients of the specified clipping plane

C Specification

  void glGetClipPlane( GLenum plane,
               GLdouble	*equation )

Parameters

  plane	    Specifies a	clipping plane.	 The number of clipping	planes
        depends on the implementation, but at least	six clipping planes
        are	supported.  They are identified	by symbolic names of the form
        GL_CLIP_PLANEi where 0 <= i < GL_MAX_CLIP_PLANES.

  equation  Returns four double-precision values that are the coefficients of
        the	plane equation of plane	in eye coordinates.

Description

  glGetClipPlane returns in equation the four coefficients of the plane
  equation for plane.

Notes

  It is	always the case	that GL_CLIP_PLANEi = GL_CLIP_PLANE0 + i.

  If an	error is generated, no change is made to the contents of equation.

Errors

  GL_INVALID_ENUM is generated if plane	is not an accepted value.

  GL_INVALID_OPERATION is generated if glGetClipPlane is executed between the
  execution of glBegin and the corresponding execution of glEnd.

See Also

  glClipPlane

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGetError.3gl

Name

  glGetError - return error information

C Specification

  GLenum glGetError( void )

Description

  glGetError returns the value of the error flag.  Each	detectable error is
  assigned a numeric code and symbolic name.  When an error occurs, the	error
  flag is set to the appropriate error code value.  No other errors are
  recorded until glGetError is called, the error code is returned, and the
  flag is reset	to GL_NO_ERROR.	 If a call to glGetError returns GL_NO_ERROR,
  there	has been no detectable error since the last call to glGetError,	or
  since	the GL was initialized.

  To allow for distributed implementations, there may be several error flags.
  If any single	error flag has recorded	an error, the value of that flag is
  returned and that flag is reset to GL_NO_ERROR when glGetError is called.
  If more than one flag	has recorded an	error, glGetError returns and clears
  an arbitrary error flag value.  Thus,	glGetError should always be called in
  a loop, until	it returns GL_NO_ERROR,	if all error flags are to be reset.

  Initially, all error flags are set to	GL_NO_ERROR.

  The currently	defined	errors are as follows:

  GL_NO_ERROR			No error has been recorded.  The value of
                this symbolic constant is guaranteed to	be
                zero.

  GL_INVALID_ENUM		An unacceptable	value is specified for an
                enumerated argument.  The offending command
                is ignored, having no side effect other	than
                to set the error flag.

  GL_INVALID_VALUE		A numeric argument is out of range.  The
                offending command is ignored, having no	side
                effect other than to set the error flag.

  GL_INVALID_OPERATION		The specified operation	is not allowed in the
                current	state.	The offending command is
                ignored, having	no side	effect other than to
                set the	error flag.

  GL_STACK_OVERFLOW		This command would cause a stack overflow.
                The offending command is ignored, having no
                side effect other than to set the error	flag.

  GL_STACK_UNDERFLOW		This command would cause a stack underflow.
                The offending command is ignored, having no
                side effect other than to set the error	flag.

  GL_OUT_OF_MEMORY		There is not enough memory left	to execute
                the command.  The state	of the GL is
                undefined, except for the state	of the error
                flags, after this error	is recorded.

  When an error	flag is	set, results of	a GL operation are undefined only if
  GL_OUT_OF_MEMORY has occurred.  In all other cases, the command generating
  the error is ignored and has no effect on the	GL state or frame buffer
  contents.  If	the generating command returns a value,	it returns zero. If
  glGetError itself generates an error it returns zero.

Errors

  GL_INVALID_OPERATION is generated if glGetError is executed between the
  execution of glBegin and the corresponding execution of glEnd.  In this
  case glGetError returns zero.

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGetLight.3gl

Name

  glGetLightfv,	glGetLightiv - return light source parameter values

C Specification

  void glGetLightfv( GLenum light,
             GLenum pname,
             GLfloat *params )
  void glGetLightiv( GLenum light,
             GLenum pname,
             GLint *params )

Parameters

  light	  Specifies a light source.  The number	of possible lights depends on
      the implementation, but at least eight lights	are supported.	They
      are identified by symbolic names of the form GL_LIGHTi where 0 <= i
      < GL_MAX_LIGHTS.

  pname	  Specifies a light source parameter for light.	 Accepted symbolic
      names	are GL_AMBIENT,	GL_DIFFUSE, GL_SPECULAR, GL_POSITION,
      GL_SPOT_DIRECTION, GL_SPOT_EXPONENT, GL_SPOT_CUTOFF,
      GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION , and
      GL_QUADRATIC_ATTENUATION.

  params  Returns the requested	data.

Description

  glGetLight returns in	params the value or values of a	light source
  parameter.  light names the light and	is a symbolic name of the form
  GL_LIGHTi for	0<=i<GL_MAX_LIGHTS, where GL_MAX_LIGHTS is an implementation
  dependent constant that is greater than or equal to eight.  pname specifies
  one of ten light source parameters, again by symbolic	name.

  The parameters are as	follows:

  GL_AMBIENT	      params returns four integer or floating-point values
              representing the ambient intensity of the	light source.
              Integer values, when requested, are linearly mapped
              from the internal	floating-point representation such
              that 1.0 maps to the most	positive representable
              integer value, and -1.0 maps to the most negative
              representable integer value.  If the internal value is
              outside the range	[-1,1],	the corresponding integer
              return value is undefined.

  GL_DIFFUSE	      params returns four integer or floating-point values
              representing the diffuse intensity of the	light source.
              Integer values, when requested, are linearly mapped
              from the internal	floating-point representation such
              that 1.0 maps to the most	positive representable
              integer value, and -1.0 maps to the most negative
              representable integer value.  If the internal value is
              outside the range	[-1,1],	the corresponding integer
              return value is undefined.

  GL_SPECULAR	      params returns four integer or floating-point values
              representing the specular	intensity of the light
              source.  Integer values, when requested, are linearly
              mapped from the internal floating-point representation
              such that	1.0 maps to the	most positive representable
              integer value, and -1.0 maps to the most negative
              representable integer value.  If the internal value is
              outside the range	[-1,1],	the corresponding integer
              return value is undefined.

  GL_POSITION	      params returns four integer or floating-point values
              representing the position	of the light source.  Integer
              values, when requested, are computed by rounding the
              internal floating-point values to	the nearest integer
              value.  The returned values are those maintained in eye
              coordinates.  They will not be equal to the values
              specified	using glLight, unless the modelview matrix
              was identity at the time glLight was called.

  GL_SPOT_DIRECTION   params returns three integer or floating-point values
              representing the direction of the	light source.
              Integer values, when requested, are computed by
              rounding the internal floating-point values to the
              nearest integer value.  The returned values are those
              maintained in eye	coordinates.  They will	not be equal
              to the values specified using glLight, unless the
              modelview	matrix was identity at the time	glLight	was
              called.  Although	spot direction is normalized before
              being used in the	lighting equation, the returned
              values are the transformed versions of the specified
              values prior to normalization.

  GL_SPOT_EXPONENT    params returns a single integer or floating-point	value
              representing the spot exponent of	the light.  An
              integer value, when requested, is	computed by rounding
              the internal floating-point representation to the
              nearest integer.

  GL_SPOT_CUTOFF      params returns a single integer or floating-point	value
              representing the spot cutoff angle of the	light.	An
              integer value, when requested, is	computed by rounding
              the internal floating-point representation to the
              nearest integer.

  GL_CONSTANT_ATTENUATION
              params returns a single integer or floating-point	value
              representing the constant	(not distance related)
              attenuation of the light.	 An integer value, when
              requested, is computed by	rounding the internal
              floating-point representation to the nearest integer.

  GL_LINEAR_ATTENUATION
              params returns a single integer or floating-point	value
              representing the linear attenuation of the light.	 An
              integer value, when requested, is	computed by rounding
              the internal floating-point representation to the
              nearest integer.

  GL_QUADRATIC_ATTENUATION
              params returns a single integer or floating-point	value
              representing the quadratic attenuation of	the light.
              An integer value,	when requested,	is computed by
              rounding the internal floating-point representation to
              the nearest integer.

Notes

  It is	always the case	that GL_LIGHTi = GL_LIGHT0 + i.

  If an	error is generated, no change is made to the contents of params.

Errors

  GL_INVALID_ENUM is generated if light	or pname is not	an accepted value.

  GL_INVALID_OPERATION is generated if glGetLight is called between a call to
  glBegin and the corresponding	call to	glEnd.

See Also

  glLight

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


glGetMap.3gl

Name

  glGetMapdv, glGetMapfv, glGetMapiv - return evaluator	parameters

C Specification

  void glGetMapdv( GLenum target,
           GLenum query,
           GLdouble *v )
  void glGetMapfv( GLenum target,
           GLenum query,
           GLfloat *v )
  void glGetMapiv( GLenum target,
           GLenum query,
           GLint *v )

Parameters

  target  Specifies the	symbolic name of a map.	 Accepted values are
      GL_MAP1_COLOR_4, GL_MAP1_INDEX, GL_MAP1_NORMAL,
      GL_MAP1_TEXTURE_COORD_1, GL_MAP1_TEXTURE_COORD_2,
      GL_MAP1_TEXTURE_COORD_3, GL_MAP1_TEXTURE_COORD_4, GL_MAP1_VERTEX_3,
      GL_MAP1_VERTEX_4, GL_MAP2_COLOR_4 , GL_MAP2_INDEX, GL_MAP2_NORMAL,
      GL_MAP2_TEXTURE_COORD_1, GL_MAP2_TEXTURE_COORD_2,
      GL_MAP2_TEXTURE_COORD_3, GL_MAP2_TEXTURE_COORD_4, GL_MAP2_VERTEX_3,
      and GL_MAP2_VERTEX_4.

  query	  Specifies which parameter to return.	Symbolic names GL_COEFF,
      GL_ORDER, and	GL_DOMAIN are accepted.

  v	  Returns the requested	data.

Description

  glMap1 and glMap2 define evaluators.	glGetMap returns evaluator
  parameters.  target chooses a	map, query selects a specific parameter, and
  v points to storage where the	values will be returned.

  The acceptable values	for the	target parameter are described in the glMap1
  and  glMap2 reference	pages.

  query	can assume the following values:

  GL_COEFF	    v returns the control points for the evaluator function.
            One-dimensional evaluators return order control points,
            and	two-dimensional	evaluators return uorderxvorder
            control points.  Each control point	consists of one, two,
            three, or four integer, single-precision floating-point,
            or double-precision	floating-point values, depending on
            the	type of	the evaluator.	Two-dimensional	control
            points are returned	in row-major order, incrementing the
            uorder index quickly, and the vorder index after each
            row.  Integer values, when requested, are computed by
            rounding the internal floating-point values	to the
            nearest integer values.

  GL_ORDER	    v returns the order	of the evaluator function.  One-
            dimensional	evaluators return a single value, order.
            Two-dimensional evaluators return two values, uorder and
            vorder.

  GL_DOMAIN	    v returns the linear u and v mapping parameters.  One-
            dimensional	evaluators return two values, u1 and u2, as
            specified by glMap1.  Two-dimensional evaluators return
            four values	(u1, u2, v1, and v2) as	specified by glMap2.
            Integer values, when requested, are	computed by rounding
            the	internal floating-point	values to the nearest integer
            values.

Notes

  If an	error is generated, no change is made to the contents of v.

Errors

  GL_INVALID_ENUM is generated if either target	or query is not	an accepted
  value.

  GL_INVALID_OPERATION is generated if glGetMap	is called between a call to
  glBegin and the corresponding	call to	glEnd.

See Also

  glEvalCoord, glMap1, glMap2

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGetMaterial.3gl

Name

  glGetMaterialfv, glGetMaterialiv - return material parameters

C Specification

  void glGetMaterialfv(	GLenum face,
            GLenum pname,
            GLfloat	*params	)
  void glGetMaterialiv(	GLenum face,
            GLenum pname,
            GLint *params )

Parameters

  face	  Specifies which of the two materials is being	queried.  GL_FRONT or
      GL_BACK are accepted,	representing the front and back	materials,
      respectively.

  pname	  Specifies the	material parameter to return.  GL_AMBIENT,
      GL_DIFFUSE, GL_SPECULAR, GL_EMISSION,	GL_SHININESS, and
      GL_COLOR_INDEXES are accepted.

  params  Returns the requested	data.

Description

  glGetMaterial	returns	in params the value or values of parameter pname of
  material face.  Six parameters are defined:

  GL_AMBIENT	      params returns four integer or floating-point values
              representing the ambient reflectance of the material.
              Integer values, when requested, are linearly mapped
              from the internal	floating-point representation such
              that 1.0 maps to the most	positive representable
              integer value, and -1.0 maps to the most negative
              representable integer value.  If the internal value is
              outside the range	[-1,1],	the corresponding integer
              return value is undefined.

  GL_DIFFUSE	      params returns four integer or floating-point values
              representing the diffuse reflectance of the material.
              Integer values, when requested, are linearly mapped
              from the internal	floating-point representation such
              that 1.0 maps to the most	positive representable
              integer value, and -1.0 maps to the most negative
              representable integer value.  If the internal value is
              outside the range	[-1,1],	the corresponding integer
              return value is undefined.

  GL_SPECULAR	      params returns four integer or floating-point values
              representing the specular	reflectance of the material.
              Integer values, when requested, are linearly mapped
              from the internal	floating-point representation such
              that 1.0 maps to the most	positive representable
              integer value, and -1.0 maps to the most negative
              representable integer value.  If the internal value is
              outside the range	[-1,1],	the corresponding integer
              return value is undefined.

  GL_EMISSION	      params returns four integer or floating-point values
              representing the emitted light intensity of the
              material.	 Integer values, when requested, are linearly
              mapped from the internal floating-point representation
              such that	1.0 maps to the	most positive representable
              integer value, and -1.0 maps to the most negative
              representable integer value.  If the internal value is
              outside the range	[-1,1],	the corresponding integer
              return value is undefined.

  GL_SHININESS	      params returns one integer or floating-point value
              representing the specular	exponent of the	material.
              Integer values, when requested, are computed by
              rounding the internal floating-point value to the
              nearest integer value.

  GL_COLOR_INDEXES    params returns three integer or floating-point values
              representing the ambient,	diffuse, and specular indices
              of the material.	These indices are used only for	color
              index lighting.  (The other parameters are all used
              only for RGBA lighting.) Integer values, when
              requested, are computed by rounding the internal
              floating-point values to the nearest integer values.

Notes

  If an	error is generated, no change is made to the contents of params.

Errors

  GL_INVALID_ENUM is generated if face or pname	is not an accepted value.

  GL_INVALID_OPERATION is generated if glGetMaterial is	called between a call
  to glBegin and the corresponding call	to glEnd.

See Also

  glMaterial

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGetPixelMap.3gl

Name

  glGetPixelMapfv, glGetPixelMapuiv, glGetPixelMapusv -	return the specified
  pixel	map

C Specification

  void glGetPixelMapfv(	GLenum map,
            GLfloat	*values	)
  void glGetPixelMapuiv( GLenum	map,
             GLuint	*values	)
  void glGetPixelMapusv( GLenum	map,
             GLushort *values )

Parameters

  map	  Specifies the	name of	the pixel map to return.  Accepted values are
      GL_PIXEL_MAP_I_TO_I, GL_PIXEL_MAP_S_TO_S, GL_PIXEL_MAP_I_TO_R,
      GL_PIXEL_MAP_I_TO_G, GL_PIXEL_MAP_I_TO_B, GL_PIXEL_MAP_I_TO_A,
      GL_PIXEL_MAP_R_TO_R, GL_PIXEL_MAP_G_TO_G, GL_PIXEL_MAP_B_TO_B, and
      GL_PIXEL_MAP_A_TO_A.

  values  Returns the pixel map	contents.

Description

  Please see the glPixelMap reference page for a description of	the
  acceptable values for	the map	parameter.  glGetPixelMap returns in values
  the contents of the pixel map	specified in map.  Pixel maps are used during
  the execution	of glReadPixels, glDrawPixels, glCopyPixels, glTexImage1D,
  and glTexImage2D to map color	indices, stencil indices, color	components,
  and depth components to other	values.

  Unsigned integer values, if requested, are linearly mapped from the
  internal fixed or floating-point representation such that 1.0	maps to	the
  largest representable	integer	value, and 0.0 maps to zero.  Return unsigned
  integer values are undefined if the map value	was not	in the range [0,1].

  To determine the required size of map, call glGet with the appropriate
  symbolic constant.

Notes

  If an	error is generated, no change is made to the contents of values.

Errors

  GL_INVALID_ENUM is generated if map is not an	accepted value.

  GL_INVALID_OPERATION is generated if glGetPixelMap is	called between a call
  to glBegin and the corresponding call	to glEnd.

Associated Gets

  glGet	with argument GL_PIXEL_MAP_I_TO_I_SIZE
  glGet	with argument GL_PIXEL_MAP_S_TO_S_SIZE
  glGet	with argument GL_PIXEL_MAP_I_TO_R_SIZE
  glGet	with argument GL_PIXEL_MAP_I_TO_G_SIZE
  glGet	with argument GL_PIXEL_MAP_I_TO_B_SIZE
  glGet	with argument GL_PIXEL_MAP_I_TO_A_SIZE
  glGet	with argument GL_PIXEL_MAP_R_TO_R_SIZE
  glGet	with argument GL_PIXEL_MAP_G_TO_G_SIZE
  glGet	with argument GL_PIXEL_MAP_B_TO_B_SIZE
  glGet	with argument GL_PIXEL_MAP_A_TO_A_SIZE
  glGet	with argument GL_MAX_PIXEL_MAP_TABLE

See Also

  glCopyPixels,	glDrawPixels, glPixelMap, glPixelTransfer, glReadPixels,
  glTexImage1D,	glTexImage2D

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGetPointervEXT.3gl

Name

  glGetPointervEXT - return the	address	of a vertex data array

C Specification

  void glGetPointervEXT( GLenum	pname,
             GLvoid* *params )

Parameters

  pname	  Specifies the	array pointer to be returned.  Symbolic	constants
      GL_VERTEX_ARRAY_POINTER_EXT, GL_NORMAL_ARRAY_POINTER_EXT,
      GL_COLOR_ARRAY_POINTER_EXT, GL_INDEX_ARRAY_POINTER_EXT,
      GL_TEXTURE_COORD_ARRAY_POINTER_EXT, GL_EDGE_FLAG_ARRAY_POINTER_EXT,
      are accepted.

  params  returns the array pointer value specified by pname.

Description

  glGetPointervEXT returns array pointer information. pname is a symbolic
  constant indicating the array	pointer	to be returned,	and params is a
  pointer to a location	in which to place the returned data.

Notes

  The array pointers are client	side state.

  glGetPointervEXT is part of the EXT_vertex_array extension,	not
  part of the core GL command set. If "GL_EXT_vertex_array" is
  included in the string returned by glGetString, when called with argument
  GL_EXTENSIONS, extension EXT_vertex_array is supported.

Errors

  GL_INVALID_ENUM is generated if pname	is not an accepted value.

See Also

  glArrayElementEXT, glColorPointerEXT,	glDrawArraysEXT,
  glEdgeFlagPointerEXT,	glIndexPointerEXT, glNormalPointerEXT,
  glTexCoordPointerEXT,	glVertexPointerEXT

Introduction | Alphabetic | Specification

Last Edited: Fri Feb 24, 1995

AFV


glGetPolygonStipple.3gl

Name

  glGetPolygonStipple -	return the polygon stipple pattern

C Specification

  void glGetPolygonStipple( GLubyte *mask )

Parameters

  mask	Returns	the stipple pattern.

Description

  glGetPolygonStipple returns to mask a	32x32 polygon stipple pattern.	The
  pattern is packed into memory	as if glReadPixels with	both height and	width
  of 32, type of GL_BITMAP, and	format of GL_COLOR_INDEX were called, and the
  stipple pattern were stored in an internal 32x32 color index buffer.
  Unlike glReadPixels, however,	pixel transfer operations (shift, offset,
  pixel	map) are not applied to	the returned stipple image.

Notes

  If an	error is generated, no change is made to the contents of mask.

Errors

  GL_INVALID_OPERATION is generated if glGetPolygonStipple is executed
  between the execution	of glBegin and the corresponding execution of glEnd.

See Also

  glPixelStore,	glPixelTransfer, glPolygonStipple, glReadPixels

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGetString.3gl

Name

  glGetString -	returns	a string describing the	current	GL connection

C Specification

  const	GLubyte	* glGetString( GLenum name )

Parameters

  name	Specifies a symbolic constant, one of GL_VENDOR, GL_RENDERER,
    GL_VERSION, or GL_EXTENSIONS.

Description

  glGetString returns a	pointer	to a static string describing some aspect of
  the current GL connection.  name can be one of the following:

  GL_VENDOR	      Returns the company responsible for this GL
              implementation.  This name does not change from release
              to release.

  GL_RENDERER	      Returns the name of the renderer.	 This name is
              typically	specific to a particular configuration of a
              hardware platform.  It does not change from release to
              release.

  GL_VERSION	      Returns a	version	or release number.

  GL_EXTENSIONS	      Returns a	space-separated	list of	supported extensions
              to GL.

  Because GL does not include queries for the performance characteristics of
  an implementation, it	is expected that some applications will	be written to
  recognize known platforms and	will modify their GL usage based on known
  performance characteristics of these platforms.  Strings GL_VENDOR and
  GL_RENDERER together uniquely	specify	a platform, and	will not change	from
  release to release.  They should be used by such platform recognition
  algorithms.

  Some applications will want to make use of features which are	not part of
  the standard GL.  These features may be implemented as extensions to the
  standard GL.	The GL_EXTENSIONS string is a space seperated list of
  supported GL extensions. (Extension names never contain a space character.)

  The GL_VERSION string	begins with a version number.  The version number is
  of the form major_number.minor_number	or
  major_number.minor_number.release_number. Vendor specific information	may
  follow the version number. Its format	depends	on the implementation, but a
  space	always seperates the version number and	the vendor specific
  information.

  All strings are null-terminated.

Notes

  If an	error is generated, glGetString	returns	zero.

  The client and server	may support different versions or extensions.
  glGetString always returns a compatible version number or list of
  extensions. The release number always	describes the server.

Errors

  GL_INVALID_ENUM is generated if name is not an accepted value.

  GL_INVALID_OPERATION is generated if glGetString is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGetTexEnv.3gl

Name

  glGetTexEnvfv, glGetTexEnviv - return	texture	environment parameters

C Specification

  void glGetTexEnvfv( GLenum target,
              GLenum pname,
              GLfloat *params )
  void glGetTexEnviv( GLenum target,
              GLenum pname,
              GLint *params )

Parameters

  target  Specifies a texture environment.  Must be GL_TEXTURE_ENV.

  pname	  Specifies the	symbolic name of a texture environment parameter.
      Accepted values are GL_TEXTURE_ENV_MODE and GL_TEXTURE_ENV_COLOR.

  params  Returns the requested	data.

Description

  glGetTexEnv returns in params	selected values	of a texture environment that
  was specified	with glTexEnv.	target specifies a texture environment.
  Currently, only one texture environment is defined and supported:
  GL_TEXTURE_ENV.

  pname	names a	specific texture environment parameter.	 The two parameters
  are as follows:

  GL_TEXTURE_ENV_MODE
        params returns the single-valued texture environment mode, a
        symbolic constant.

  GL_TEXTURE_ENV_COLOR
        params returns four	integer	or floating-point values that are the
        texture environment	color.	Integer	values,	when requested,	are
        linearly mapped from the internal floating-point representation
        such that 1.0 maps to the most positive representable integer,
        and	-1.0 maps to the most negative representable integer.

Notes

  If an	error is generated, no change is made to the contents of params.

Errors

  GL_INVALID_ENUM is generated if target or pname is not an accepted value.

  GL_INVALID_OPERATION is generated if glGetTexEnv is called between a call
  to glBegin and the corresponding call	to glEnd.

See Also

  glTexEnv

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGetTexGen.3gl

Name

  glGetTexGendv, glGetTexGenfv,	glGetTexGeniv -	return texture coordinate
  generation parameters

C Specification

  void glGetTexGendv( GLenum coord,
              GLenum pname,
              GLdouble *params )
  void glGetTexGenfv( GLenum coord,
              GLenum pname,
              GLfloat *params )
  void glGetTexGeniv( GLenum coord,
              GLenum pname,
              GLint *params )

Parameters

  coord	  Specifies a texture coordinate.  Must	be GL_S, GL_T, GL_R, or	GL_Q.

  pname	  Specifies the	symbolic name of the value(s) to be returned.  Must
      be either GL_TEXTURE_GEN_MODE	or the name of one of the texture
      generation plane equations: GL_OBJECT_PLANE or GL_EYE_PLANE.

  params  Returns the requested	data.

Description

  glGetTexGen returns in params	selected parameters of a texture coordinate
  generation function that was specified using glTexGen.  coord	names one of
  the (s,t,r,q)	texture	coordinates, using the symbolic	constant GL_S, GL_T,
  GL_R,	or GL_Q.

  pname	specifies one of three symbolic	names:

  GL_TEXTURE_GEN_MODE		params returns the single-valued texture
                generation function, a symbolic	constant.

  GL_OBJECT_PLANE		params returns the four	plane equation
                coefficients that specify object linear-
                coordinate generation.	Integer	values,	when
                requested, are mapped directly from the
                internal floating-point	representation.

  GL_EYE_PLANE			params returns the four	plane equation
                coefficients that specify eye linear-
                coordinate generation.	Integer	values,	when
                requested, are mapped directly from the
                internal floating-point	representation.	 The
                returned values	are those maintained in	eye
                coordinates.  They are not equal to the
                values specified using glTexGen, unless	the
                modelview matrix was identity at the time
                glTexGen was called.

Notes

  If an	error is generated, no change is made to the contents of params.

Errors

  GL_INVALID_ENUM is generated if coord	or pname is not	an accepted value.

  GL_INVALID_OPERATION is generated if glGetTexGen is called between a call
  to glBegin and the corresponding call	to glEnd.

See Also

  glTexGen

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGetTexImage.3gl

Name

  glGetTexImage	- return a texture image

C Specification

  void glGetTexImage( GLenum target,
              GLint level,
              GLenum format,
              GLenum type,
              GLvoid *pixels )

Parameters

  target  Specifies which texture is to	be obtained.  GL_TEXTURE_1D and
      GL_TEXTURE_2D	are accepted.

  level	  Specifies the	level-of-detail	number of the desired image.  Level 0
      is the base image level.  Level n is the nth mipmap reduction
      image.

  format  Specifies a pixel format for the returned data.  The supported
      formats are GL_RED, GL_GREEN,	GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA,
      GL_LUMINANCE,	and GL_LUMINANCE_ALPHA.

  type	  Specifies a pixel type for the returned data.	 The supported types
      are GL_UNSIGNED_BYTE,	GL_BYTE, GL_UNSIGNED_SHORT, GL_SHORT,
      GL_UNSIGNED_INT, GL_INT, and GL_FLOAT.

  pixels  Returns the texture image.  Should be	a pointer to an	array of the
      type specified by type.

Description

  glGetTexImage	returns	a texture image	into pixels.  target specifies
  whether the desired texture image is one specified by	glTexImage1D
  (GL_TEXTURE_1D) or by	glTexImage2D (GL_TEXTURE_2D).  level specifies the
  level-of-detail number of the	desired	image.	format and type	specify	the
  format and type of the desired image array.  Please see the reference	pages
  glTexImage1D and glDrawPixels	for a description of the acceptable values
  for the format and type parameters, respectively.

  Operation of glGetTexImage is	best understood	by considering the selected
  internal four-component texture image	to be an RGBA color buffer the size
  of the image.	 The semantics of glGetTexImage	are then identical to those
  of glReadPixels called with the same format and type,	with x and y set to
  zero,	width set to the width of the texture image (including border if one
  was specified), and height set to one	for 1-D	images,	or to the height of
  the texture image (including border if one was specified) for	2-D images.
  Because the internal texture image is	an RGBA	image, pixel formats
  GL_COLOR_INDEX, GL_STENCIL_INDEX, and	GL_DEPTH_COMPONENT are not accepted,
  and pixel type GL_BITMAP is not accepted.

  If the selected texture image	does not contain four components, the
  following mappings are applied.  Single-component textures are treated as
  RGBA buffers with red	set to the single-component value, and green, blue,
  and alpha set	to zero.  Two-component	textures are treated as	RGBA buffers
  with red set to the value of component zero, alpha set to the	value of
  component one, and green and blue set	to zero.  Finally, three-component
  textures are treated as RGBA buffers with red	set to component zero, green
  set to component one,	blue set to component two, and alpha set to zero.

  To determine the required size of pixels, use	glGetTexLevelParameter to
  ascertain the	dimensions of the internal texture image, then scale the
  required number of pixels by the storage required for	each pixel, based on
  format and type.  Be sure to take the	pixel storage parameters into
  account, especially GL_PACK_ALIGNMENT.

Notes

  If an	error is generated, no change is made to the contents of pixels.

Errors

  GL_INVALID_ENUM is generated if target, format, or type is not an accepted
  value.

  GL_INVALID_VALUE is generated	if level is less than zero or greater than
  log max, where max is	the returned value of GL_MAX_TEXTURE_SIZE.
     2
  GL_INVALID_OPERATION is generated if glGetTexImage is	executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGetTexLevelParameter with argument GL_TEXTURE_WIDTH
  glGetTexLevelParameter with argument GL_TEXTURE_HEIGHT
  glGetTexLevelParameter with argument GL_TEXTURE_BORDER
  glGetTexLevelParameter with argument GL_TEXTURE_COMPONENTS
  glGet	with arguments GL_PACK_ALIGNMENT and others

See Also

  glDrawPixels,	glReadPixels, glTexImage1D, glTexImage2D

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGetTexLevelParameter.3gl

Name

  glGetTexLevelParameterfv, glGetTexLevelParameteriv - return texture
  parameter values for a specific level	of detail

C Specification

  void glGetTexLevelParameterfv( GLenum	target,
                 GLint level,
                 GLenum	pname,
                 GLfloat *params )
  void glGetTexLevelParameteriv( GLenum	target,
                 GLint level,
                 GLenum	pname,
                 GLint *params )

Parameters

  target  Specifies the	symbolic name of the target texture, either
      GL_TEXTURE_1D	or GL_TEXTURE_2D.

  level	  Specifies the	level-of-detail	number of the desired image.  Level 0
      is the base image level.  Level n is the nth mipmap reduction
      image.

  pname	  Specifies the	symbolic name of a texture parameter.
      GL_TEXTURE_WIDTH, GL_TEXTURE_HEIGHT, GL_TEXTURE_COMPONENTS, and
      GL_TEXTURE_BORDER are	accepted.

  params  Returns the requested	data.

Description

  glGetTexLevelParameter returns in params texture parameter values for	a
  specific level-of-detail value, specified as level.  target defines the
  target texture, either GL_TEXTURE_1D or GL_TEXTURE_2D, to specify one- or
  two-dimensional texturing.  pname specifies the texture parameter whose
  value	or values will be returned.

  The accepted parameter names are as follows:

  GL_TEXTURE_WIDTH
        params returns a single value, the width of	the texture image.
        This value includes	the border of the texture image.

  GL_TEXTURE_HEIGHT
        params returns a single value, the height of the texture image.
        This value includes	the border of the texture image.

  GL_TEXTURE_COMPONENTS
        params returns a single value, the number of components in the
        texture image.

  GL_TEXTURE_BORDER
        params returns a single value, the width in	pixels of the border
        of the texture image.

Notes

  If an	error is generated, no change is made to the contents of params.

Errors

  GL_INVALID_ENUM is generated if target or pname is not an accepted value.

  GL_INVALID_VALUE is generated	if level is less than zero or greater than
  log max, where max is	the returned value of GL_MAX_TEXTURE_SIZE.
     2
  GL_INVALID_OPERATION is generated if glGetTexLevelParameter is called
  between a call to glBegin and	the corresponding call to glEnd.

See Also

  glGetTexParameter, glTexImage1D, glTexImage2D, glTexParameter

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glGetTexParameter.3gl

Name

  glGetTexParameterfv, glGetTexParameteriv - return texture parameter values

C Specification

  void glGetTexParameterfv( GLenum target,
                GLenum pname,
                GLfloat *params )
  void glGetTexParameteriv( GLenum target,
                GLenum pname,
                GLint *params )

Parameters

  target  Specifies the	symbolic name of the target texture.  GL_TEXTURE_1D
      and GL_TEXTURE_2D are	accepted.

  pname	  Specifies the	symbolic name of a texture parameter.
      GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_FILTER,	GL_TEXTURE_WRAP_S,
      GL_TEXTURE_WRAP_T, and GL_TEXTURE_BORDER_COLOR are accepted.

  params  Returns the texture parameters.

Description

  glGetTexParameter returns in params the value	or values of the texture
  parameter specified as pname.	 target	defines	the target texture, either
  GL_TEXTURE_1D	or GL_TEXTURE_2D, to specify one- or two-dimensional
  texturing.  pname accepts the	same symbols as	glTexParameter,	with the same
  interpretations:

  GL_TEXTURE_MAG_FILTER		     Returns the single-valued texture
                     magnification filter, a symbolic
                     constant.

  GL_TEXTURE_MIN_FILTER		     Returns the single-valued texture
                     minification filter, a symbolic
                     constant.

  GL_TEXTURE_WRAP_S		     Returns the single-valued wrapping
                     function for texture coordinate s,	a
                     symbolic constant.

  GL_TEXTURE_WRAP_T		     Returns the single-valued wrapping
                     function for texture coordinate t,	a
                     symbolic constant.

  GL_TEXTURE_BORDER_COLOR	     Returns four integer or floating-point
                     numbers that comprise the RGBA color of
                     the texture border.  Floating-point
                     values are	returned in the	range [0,1].
                     Integer values are	returned as a linear
                     mapping of	the internal floating-point
                     representation such that 1.0 maps to the
                     most positive representable integer and
                     -1.0 maps to the most negative
                     representable integer.

Notes

  If an	error is generated, no change is made to the contents of params.

Errors

  GL_INVALID_ENUM is generated if target or pname is not an accepted value.

  GL_INVALID_OPERATION is generated if glGetTexParameter is called between a
  call to glBegin and the corresponding	call to	glEnd.

See Also

  glTexParameter

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glHint.3gl

Name

  glHint - specify implementation-specific hints

C Specification

  void glHint( GLenum target,
           GLenum mode )

Parameters

  target  Specifies a symbolic constant	indicating the behavior	to be
      controlled.  GL_FOG_HINT,  GL_LINE_SMOOTH_HINT,
      GL_PERSPECTIVE_CORRECTION_HINT, GL_POINT_SMOOTH_HINT,	and
      GL_POLYGON_SMOOTH_HINT are accepted.

  mode	  Specifies a symbolic constant	indicating the desired behavior.
      GL_FASTEST, GL_NICEST, and GL_DONT_CARE are accepted.

Description

  Certain aspects of GL	behavior, when there is	room for interpretation, can
  be  controlled with hints.  A	hint is	specified with two arguments.  target
  is a symbolic	constant indicating the	behavior to be controlled, and mode
  is another symbolic constant indicating the desired behavior.	 mode can be
  one of the following:

  GL_FASTEST	      The most efficient option	should be chosen.

  GL_NICEST	      The most correct,	or highest quality, option should be
              chosen.

  GL_DONT_CARE	      The client doesn't have a	preference.

  Though the implementation aspects that can be	hinted are well	defined, the
  interpretation of the	hints depends on the implementation.  The hint
  aspects that can be specified	with target, along with	suggested semantics,
  are as follows:

  GL_FOG_HINT	      Indicates	the accuracy of	fog calculation.  If per-
              pixel fog	calculation is not efficiently supported by
              the GL implementation, hinting GL_DONT_CARE or
              GL_FASTEST can result in per-vertex calculation of fog
              effects.

  GL_LINE_SMOOTH_HINT Indicates	the sampling quality of	antialiased lines.
              Hinting GL_NICEST	can result in more pixel fragments
              being generated during rasterization, if a larger
              filter function is applied.

  GL_PERSPECTIVE_CORRECTION_HINT
              Indicates	the quality of color and texture coordinate
              interpolation.  If perspective-corrected parameter
              interpolation is not efficiently supported by the	GL
              implementation, hinting GL_DONT_CARE or GL_FASTEST can
              result in	simple linear interpolation of colors and/or
              texture coordinates.

  GL_POINT_SMOOTH_HINT
              Indicates	the sampling quality of	antialiased points.
              Hinting GL_NICEST	can result in more pixel fragments
              being generated during rasterization, if a larger
              filter function is applied.

  GL_POLYGON_SMOOTH_HINT
              Indicates	the sampling quality of	antialiased polygons.
              Hinting GL_NICEST	can result in more pixel fragments
              being generated during rasterization, if a larger
              filter function is applied.

Notes

  The interpretation of	hints depends on the implementation.  glHint can be
  ignored.

Errors

  GL_INVALID_ENUM is generated if either target	or mode	is not an accepted
  value.

  GL_INVALID_OPERATION is generated if glHint is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glIndex.3gl

Name

  glIndexd, glIndexf, glIndexi,	glIndexs, glIndexdv, glIndexfv,	glIndexiv,
  glIndexsv - set the current color index

C Specification

  void glIndexd( GLdouble c )
  void glIndexf( GLfloat c )
  void glIndexi( GLint c )
  void glIndexs( GLshort c )

Parameters

  c  Specifies the new value for the current color index.

C Specification

  void glIndexdv( const	GLdouble *c )
  void glIndexfv( const	GLfloat	*c )
  void glIndexiv( const	GLint *c )
  void glIndexsv( const	GLshort	*c )

Parameters

  c    Specifies a pointer to a	one-element array that contains	the new	value
       for the current color index.

Description

  glIndex updates the current (single-valued) color index.  It takes one
  argument: the	new value for the current color	index.

  The current index is stored as a floating-point value.  Integer values are
  converted directly to	floating-point values, with no special mapping.

  Index	values outside the representable range of the color index buffer are
  not clamped.	However, before	an index is dithered (if enabled) and written
  to the frame buffer, it is converted to fixed-point format.  Any bits	in
  the integer portion of the resulting fixed-point value that do not
  correspond to	bits in	the frame buffer are masked out.

Notes

  The current index can	be updated at any time.	 In particular,	glIndex	can
  be called between a call to glBegin and the corresponding call to glEnd.

Associated Gets

  glGet	with argument GL_CURRENT_INDEX

See Also

  glColor

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glIndexMask.3gl

Name

  glIndexMask -	control	the writing of individual bits in the color index
  buffers

C Specification

  void glIndexMask( GLuint mask	)

Parameters

  mask	Specifies a bit	mask to	enable and disable the writing of individual
    bits in	the color index	buffers.  Initially, the mask is all ones.

Description

  glIndexMask controls the writing of individual bits in the color index
  buffers.  The	least significant n bits of mask, where	n is the number	of
  bits in a color index	buffer,	specify	a mask.	 Wherever a one	appears	in
  the mask, the	corresponding bit in the color index buffer (or	buffers) is
  made writable.  Where	a zero appears,	the bit	is write-protected.

  This mask is used only in color index	mode, and it affects only the buffers
  currently selected for writing (see glDrawBuffer).  Initially, all bits are
  enabled for writing.

Errors

  GL_INVALID_OPERATION is generated if glIndexMask is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_INDEX_WRITEMASK

See Also

  glColorMask, glDepthMask, glDrawBuffer, glIndex, glStencilMask

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glIndexPointerEXT.3gl

Name

  glIndexPointerEXT - define an	array of color indexes

C Specification

  void glIndexPointerEXT( GLenum type,
              GLsizei stride,
              GLsizei count,
              const	GLvoid *pointer	)

Parameters

  type	   Specifies the data type of each color index in the array.
       Symbolic constants GL_SHORT,	GL_INT,	GL_FLOAT, or GL_DOUBLE_EXT,
       are accepted.

  stride   Specifies the byte offset between consecutive color indexes.	 If
       stride is zero the color indexes are	understood to be tightly
       packed in the array.

  count	   Specifies the number	of indexes, counting from the first, that are
       static.

  pointer  Specifies a pointer to the first index in the array.

Description

  glIndexPointerEXT specifies the location and data format of an array of
  color	indexes	to use when rendering using the vertex array extension. type
  specifies the data type of each color index and stride gives the byte
  stride from one color index to the next allowing vertexes and attributes to
  be packed into a single array or stored in separate arrays. (Single-array
  storage may be more efficient on some implementations.) count indicates
  the number of array elements (counting from the first) that are static.
  Static elements may be modified by the application, but once they are
  modified, the application must explicitly respecify the array before using
  it for any rendering. When a color index array is specified, type, stride,
  count and pointer are saved as client-side state, and static array elements
  may be cached by the implementation.

  The color index array	is enabled and disabled	using glEnable and glDisable
  with the argument GL_INDEX_ARRAY_EXT.	If enabled, the	color index array is
  used when glDrawArraysEXT or glArrayElementEXT is called.

  Use glDrawArraysEXT to define	a sequence of primitives (all of the same
  type)	from pre-specified vertex and vertex attribute arrays.	Use
  glArrayElementEXT to specify primitives by indexing vertexes and vertex
  attributes.

Notes

  Non-static array elements are	not accessed until glArrayElementEXT or
  glDrawArraysEXT is executed.

  By default the color index array is disabled and it won't be accessed	when
  glArrayElementEXT or glDrawArraysEXT is called.

  Although it is not an error to call glIndexPointerEXT between the
  execution of glBegin and the corresponding execution of glEnd, the results
  are undefined.

  glIndexPointerEXT will typically be implemented on the client	side with no
  protocol.

  Since	the color index	array parameters are client side state,	they are not
  saved	or restored by glPushAttrib and	glPopAttrib.

  glIndexPointerEXT commands are not entered into display lists.

  glIndexPointerEXT is part of the EXT_vertex_array extension, not
  part of the core GL command set. If "GL_EXT_vertex_array" is
  included in the string returned by glGetString, when called with argument
  GL_EXTENSIONS, extension EXT_vertex_array is supported.

Errors

  GL_INVALID_ENUM is generated if type is not an accepted value.

  GL_INVALID_VALUE is generated	if stride or count is negative.

Associated Gets

  glIsEnabled with argument GL_INDEX_ARRAY_EXT
  glGet	with argument GL_INDEX_ARRAY_SIZE_EXT
  glGet	with argument GL_INDEX_ARRAY_TYPE_EXT
  glGet	with argument GL_INDEX_ARRAY_STRIDE_EXT
  glGet	with argument GL_INDEX_ARRAY_COUNT_EXT
  glGetPointervEXT with	argument GL_INDEX_ARRAY_POINTER_EXT

See Also

  glArrayElementEXT, glColorPointerEXT,	glDrawArraysEXT,
  glEdgeFlagPointerEXT,	glGetPointervEXT, glNormalPointerEXT,
  glTexCoordPointerEXT,	glVertexPointerEXT, glEnable

Introduction | Alphabetic | Specification

Last Edited: Fri Feb 24, 1995

AFV


glInitNames.3gl

Name

  glInitNames -	initialize the name stack

C Specification

  void glInitNames( void )

Description

  The name stack is used during	selection mode to allow	sets of	rendering
  commands to be uniquely identified.  It consists of an ordered set of
  unsigned integers.  glInitNames causes the name stack	to be initialized to
  its default empty state.

  The name stack is always empty while the render mode is not GL_SELECT.
  Calls	to glInitNames while the render	mode is	not GL_SELECT are ignored.

Errors

  GL_INVALID_OPERATION is generated if glInitNames is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_NAME_STACK_DEPTH
  glGet	with argument GL_MAX_NAME_STACK_DEPTH

See Also

  glLoadName, glPushName, glRenderMode,	glSelectBuffer

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glIsEnabled.3gl

Name

  glIsEnabled -	test whether a capability is enabled

C Specification

  GLboolean glIsEnabled( GLenum	cap )

Parameters

  cap  Specifies a symbolic constant indicating	a GL capability.

Description

  glIsEnabled returns GL_TRUE if cap is	an enabled capability and returns
  GL_FALSE otherwise.  The following capabilities are accepted for cap:

  GL_ALPHA_TEST		       See glAlphaFunc.
  GL_AUTO_NORMAL	       See glEvalCoord.
  GL_BLEND		       See glBlendFunc.
  GL_CLIP_PLANEi	       See glClipPlane.
  GL_COLOR_ARRAY_EXT	       See glColorPointerEXT.
  GL_COLOR_MATERIAL	       See glColorMaterial.
  GL_CULL_FACE		       See glCullFace.
  GL_DEPTH_TEST		       See glDepthFunc and  glDepthRange.
  GL_DITHER		       See glEnable.
  GL_EDGE_FLAG_ARRAY_EXT       See glEdgeFlagPointerEXT.
  GL_FOG		       See glFog.
  GL_INDEX_ARRAY_EXT	       See glIndexPointerEXT.
  GL_LIGHTi		       See glLightModel	and glLight.
  GL_LIGHTING		       See glMaterial, glLightModel, and glLight.
  GL_LINE_SMOOTH	       See glLineWidth.
  GL_LINE_STIPPLE	       See glLineStipple.
  GL_LOGIC_OP		       See glLogicOp.
  GL_MAP1_COLOR_4	       See glMap1.
  GL_MAP1_INDEX		       See glMap1.
  GL_MAP1_NORMAL	       See glMap1.
  GL_MAP1_TEXTURE_COORD_1      See glMap1.
  GL_MAP1_TEXTURE_COORD_2      See glMap1.
  GL_MAP1_TEXTURE_COORD_3      See glMap1.
  GL_MAP1_TEXTURE_COORD_4      See glMap1.
  GL_MAP1_VERTEX_3	       See glMap1.
  GL_MAP1_VERTEX_4	       See glMap1.
  GL_MAP2_COLOR_4	       See glMap2.
  GL_MAP2_INDEX		       See glMap2.
  GL_MAP2_NORMAL	       See glMap2.
  GL_MAP2_TEXTURE_COORD_1      See glMap2.
  GL_MAP2_TEXTURE_COORD_2      See glMap2.
  GL_MAP2_TEXTURE_COORD_3      See glMap2.
  GL_MAP2_TEXTURE_COORD_4      See glMap2.
  GL_MAP2_VERTEX_3	       See glMap2.
  GL_MAP2_VERTEX_4	       See glMap2.
  GL_NORMAL_ARRAY_EXT	       See glNormalPointerEXT.
  GL_NORMALIZE		       See glNormal.
  GL_POINT_SMOOTH	       See glPointSize.
  GL_POLYGON_SMOOTH	       See glPolygonMode.
  GL_POLYGON_STIPPLE	       See glPolygonStipple.
  GL_SCISSOR_TEST	       See glScissor.
  GL_STENCIL_TEST	       See glStencilFunc and glStencilOp.
  GL_TEXTURE_1D		       See glTexImage1D.
  GL_TEXTURE_2D		       See glTexImage2D.
  GL_TEXTURE_COORD_ARRAY_EXT   See glTexCoordPointerEXT.
  GL_TEXTURE_GEN_Q	       See glTexGen.
  GL_TEXTURE_GEN_R	       See glTexGen.
  GL_TEXTURE_GEN_S	       See glTexGen.
  GL_TEXTURE_GEN_T	       See glTexGen.
  GL_VERTEX_ARRAY_EXT	       See glVertexPointerEXT.

Notes

  If an	error is generated, glIsEnabled	returns	zero.

Errors

  GL_INVALID_ENUM is generated if cap is not an	accepted value.

  GL_INVALID_OPERATION is generated if glIsEnabled is executed between the
  execution of glBegin and the corresponding execution of glEnd.

See Also

  glEnable

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glIsList.3gl

Name

  glIsList - test for display-list existence

C Specification

  GLboolean glIsList( GLuint list )

Parameters

  list	Specifies a potential display-list name.

Description

  glIsList returns GL_TRUE if list is the name of a display list and returns
  GL_FALSE otherwise.

Errors

  GL_INVALID_OPERATION is generated if glIsList	is executed between the
  execution of	glBegin	and the	corresponding execution	of glEnd.

See Also

  glCallList, glCallLists, glDeleteLists, glGenLists, glNewList

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glLight.3gl

Name

  glLightf, glLighti, glLightfv, glLightiv - set light source parameters

C Specification

  void glLightf( GLenum	light,
         GLenum	pname,
         GLfloat param )
  void glLighti( GLenum	light,
         GLenum	pname,
         GLint param )

Parameters

  light	  Specifies a light.  The number of lights is depends on the
      implementation, but at least eight lights are	supported.  They are
      identified by	symbolic names of the form GL_LIGHTi where 0 <= i <
      GL_MAX_LIGHTS.

  pname	  Specifies a single-valued light source parameter for light.
      GL_SPOT_EXPONENT, GL_SPOT_CUTOFF, GL_CONSTANT_ATTENUATION,
      GL_LINEAR_ATTENUATION	, and GL_QUADRATIC_ATTENUATION are accepted.

  param	  Specifies the	value that parameter pname of light source light will
      be set to.

C Specification

  void glLightfv( GLenum light,
          GLenum pname,
          const	GLfloat	*params	)
  void glLightiv( GLenum light,
          GLenum pname,
          const	GLint *params )

Parameters

  light
       Specifies a light.  The number of lights	depends	on the
       implementation, but at least eight lights are supported.	 They are
       identified by symbolic names of the form	GL_LIGHTi where	0 <= i <
       GL_MAX_LIGHTS.

  pname
       Specifies a light source	parameter for light.  GL_AMBIENT, GL_DIFFUSE,
       GL_SPECULAR, GL_POSITION, GL_SPOT_DIRECTION, GL_SPOT_EXPONENT,
       GL_SPOT_CUTOFF, GL_CONSTANT_ATTENUATION,	GL_LINEAR_ATTENUATION, and
       GL_QUADRATIC_ATTENUATION	are accepted.

  params
       Specifies a pointer to the value	or values that parameter pname of
       light source light will be set to.

Description

  glLight sets the values of individual	light source parameters.  light	names
  the light and	is a symbolic name of the form GL_LIGHTi, where	0 <= i <
  GL_MAX_LIGHTS.  pname	specifies one of ten light source parameters, again
  by symbolic name.  params is either a	single value or	a pointer to an	array
  that contains	the new	values.

  Lighting calculation is enabled and disabled using glEnable and glDisable
  with argument	GL_LIGHTING.  When lighting is enabled,	light sources that
  are enabled contribute to the	lighting calculation.  Light source i is
  enabled and disabled using glEnable and glDisable with argument GL_LIGHTi.

  The ten light	parameters are as follows:

  GL_AMBIENT	      params contains four integer or floating-point values
              that specify the ambient RGBA intensity of the light.
              Integer values are mapped	linearly such that the most
              positive representable value maps	to 1.0,	and the	most
              negative representable value maps	to -1.0.  Floating-
              point values are mapped directly.	 Neither integer nor
              floating-point values are	clamped.  The default ambient
              light intensity is (0.0, 0.0, 0.0, 1.0).

  GL_DIFFUSE	      params contains four integer or floating-point values
              that specify the diffuse RGBA intensity of the light.
              Integer values are mapped	linearly such that the most
              positive representable value maps	to 1.0,	and the	most
              negative representable value maps	to -1.0.  Floating-
              point values are mapped directly.	 Neither integer nor
              floating-point values are	clamped.  The default diffuse
              intensity	is (0.0, 0.0, 0.0, 1.0)	for all	lights other
              than light zero.	The default diffuse intensity of
              light zero is (1.0, 1.0, 1.0, 1.0).

  GL_SPECULAR	      params contains four integer or floating-point values
              that specify the specular	RGBA intensity of the light.
              Integer values are mapped	linearly such that the most
              positive representable value maps	to 1.0,	and the	most
              negative representable value maps	to -1.0.  Floating-
              point values are mapped directly.	 Neither integer nor
              floating-point values are	clamped.  The default
              specular intensity is (0.0, 0.0, 0.0, 1.0) for all
              lights other than	light zero.  The default specular
              intensity	of light zero is (1.0, 1.0, 1.0, 1.0).

  GL_POSITION	      params contains four integer or floating-point values
              that specify the position	of the light in	homogeneous
              object coordinates.  Both	integer	and floating-point
              values are mapped	directly.  Neither integer nor
              floating-point values are	clamped.

              The position is transformed by the modelview matrix
              when glLight is called (just as if it were a point),
              and it is	stored in eye coordinates.  If the w
              component	of the position	is 0.0,	the light is treated
              as a directional source.	Diffuse	and specular lighting
              calculations take	the light's direction, but not its
              actual position, into account, and attenuation is
              disabled.	 Otherwise, diffuse and	specular lighting
              calculations are based on	the actual location of the
              light in eye coordinates,	and attenuation	is enabled.
              The default position is (0,0,1,0); thus, the default
              light source is directional, parallel to,	and in the
              direction	of the -z axis.

  GL_SPOT_DIRECTION   params contains three integer or floating-point values
              that specify the direction of the	light in homogeneous
              object coordinates.  Both	integer	and floating-point
              values are mapped	directly.  Neither integer nor
              floating-point values are	clamped.

              The spot direction is transformed	by the inverse of the
              modelview	matrix when glLight is called (just as it it
              were a normal), and it is	stored in eye coordinates.
              It is significant	only when GL_SPOT_CUTOFF is not	180,
              which it is by default.  The default direction is
              (0,0,-1).

  GL_SPOT_EXPONENT    params is	a single integer or floating-point value that
              specifies	the intensity distribution of the light.
              Integer and floating-point values	are mapped directly.
              Only values in the range [0,128] are accepted.

              Effective	light intensity	is attenuated by the cosine
              of the angle between the direction of the	light and the
              direction	from the light to the vertex being lighted,
              raised to	the power of the spot exponent.	 Thus, higher
              spot exponents result in a more focused light source,
              regardless of the	spot cutoff angle (see next
              paragraph).  The default spot exponent is	0, resulting
              in uniform light distribution.

  GL_SPOT_CUTOFF      params is	a single integer or floating-point value that
              specifies	the maximum spread angle of a light source.
              Integer and floating-point values	are mapped directly.
              Only values in the range [0,90], and the special value
              180, are accepted.  If the angle between the direction
              of the light and the direction from the light to the
              vertex being lighted is greater than the spot cutoff
              angle, the light is completely masked.  Otherwise, its
              intensity	is controlled by the spot exponent and the
              attenuation factors.  The	default	spot cutoff is 180,
              resulting	in uniform light distribution.

  GL_CONSTANT_ATTENUATION

  GL_LINEAR_ATTENUATION

  GL_QUADRATIC_ATTENUATION
              params is	a single integer or floating-point value that
              specifies	one of the three light attenuation factors.
              Integer and floating-point values	are mapped directly.
              Only nonnegative values are accepted.  If	the light is
              positional, rather than directional, its intensity is
              attenuated by the	reciprocal of the sum of: the
              constant factor, the linear factor times the distance
              between the light	and the	vertex being lighted, and the
               quadratic factor	times the square of the	same
              distance.	 The default attenuation factors are (1,0,0),
              resulting	in no attenuation.

Notes

  It is	always the case	that GL_LIGHTi = GL_LIGHT0 + i.

Errors

  GL_INVALID_ENUM is generated if either light or pname	is not an accepted
  value.

  GL_INVALID_VALUE is generated	if a spot exponent value is specified outside
  the range [0,128], or	if spot	cutoff is specified outside the	range [0,90]
  (except for the special value	180), or if a negative attenuation factor is
  specified.

  GL_INVALID_OPERATION is generated if glLight is executed between the
  execution of	glBegin	and the	corresponding execution	of glEnd.

Associated Gets

  glGetLight
  glIsEnabled with argument GL_LIGHTING

See Also

  glColorMaterial, glLightModel, glMaterial

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glLightModel.3gl

Name

  glLightModelf, glLightModeli,	glLightModelfv,	glLightModeliv - set the
  lighting model parameters

C Specification

  void glLightModelf( GLenum pname,
              GLfloat param )
  void glLightModeli( GLenum pname,
              GLint param )

Parameters

  pname	  Specifies a single-valued lighting model parameter.
      GL_LIGHT_MODEL_LOCAL_VIEWER and GL_LIGHT_MODEL_TWO_SIDE are
      accepted.

  param	  Specifies the	value that param will be set to.

C Specification

  void glLightModelfv( GLenum pname,
               const GLfloat *params )
  void glLightModeliv( GLenum pname,
               const GLint *params )

Parameters

  pname
       Specifies a lighting model parameter.  GL_LIGHT_MODEL_AMBIENT,
       GL_LIGHT_MODEL_LOCAL_VIEWER, and	GL_LIGHT_MODEL_TWO_SIDE	are accepted.

  params
       Specifies a pointer to the value	or values that params will be set to.

Description

  glLightModel sets the	lighting model parameter.  pname names a parameter
  and params gives the new value.  There are three lighting model parameters:

  GL_LIGHT_MODEL_AMBIENT
        params contains four integer or floating-point values that
        specify the	ambient	RGBA intensity of the entire scene.  Integer
        values are mapped linearly such that the most positive
        representable value	maps to	1.0, and the most negative
        representable value	maps to	-1.0.  Floating-point values are
        mapped directly.  Neither integer nor floating-point values	are
        clamped.  The default ambient scene	intensity is (0.2, 0.2,	0.2,
        1.0).

  GL_LIGHT_MODEL_LOCAL_VIEWER
        params is a	single integer or floating-point value that specifies
        how	specular reflection angles are computed.  If params is 0 (or
        0.0), specular reflection angles take the view direction to	be
        parallel to	and in the direction of	the -z axis, regardless	of
        the	location of the	vertex in eye coordinates.  Otherwise
        specular reflections are computed from the origin of the eye
        coordinate system.	The default is 0.

  GL_LIGHT_MODEL_TWO_SIDE
        params is a	single integer or floating-point value that specifies
        whether one- or two-sided lighting calculations are	done for
        polygons.  It has no effect	on the lighting	calculations for
        points, lines, or bitmaps.	If params is 0 (or 0.0), one-sided
        lighting is	specified, and only the	front material parameters are
        used in the	lighting equation.  Otherwise, two-sided lighting is
        specified.	In this	case, vertices of back-facing polygons are
        lighted using the back material parameters,	and have their
        normals reversed before the	lighting equation is evaluated.
        Vertices of	front-facing polygons are always lighted using the
        front material parameters, with no change to their normals.	 The
        default is 0.

  In RGBA mode,	the lighted color of a vertex is the sum of the	material
  emission intensity, the product of the material ambient reflectance and the
  lighting model full-scene ambient intensity, and the contribution of each
  enabled light	source.	 Each light source contributes the sum of three
  terms: ambient, diffuse, and specular.  The ambient light source
  contribution is the product of the material ambient reflectance and the
  light's ambient intensity.  The diffuse light	source contribution is the
  product of the material diffuse reflectance, the light's diffuse intensity,
  and the dot product of the vertex's normal with the normalized vector	from
  the vertex to	the light source.  The specular	light source contribution is
  the product of the material specular reflectance, the	light's	specular
  intensity, and the dot product of the	normalized vertex-to-eye and vertex-
  to-light vectors, raised to the power	of the shininess of the	material.
  All three light source contributions are attenuated equally based on the
  distance from	the vertex to the light	source and on light source direction,
  spread exponent, and spread cutoff angle.  All dot products are replaced
  with zero if they evaluate to	a negative value.

  The alpha component of the resulting lighted color is	set to the alpha
  value	of the material	diffuse	reflectance.

  In color index mode, the value of the	lighted	index of a vertex ranges from
  the ambient to the specular values passed to glMaterial using
  GL_COLOR_INDEXES.  Diffuse and specular coefficients,	computed with a	(.30,
  .59, .11) weighting of the lights' colors, the shininess of the material,
  and the same reflection and attenuation equations as in the RGBA case,
  determine how	much above ambient the resulting index is.

Errors

  GL_INVALID_ENUM is generated if pname	is not an accepted value.

  GL_INVALID_OPERATION is generated if glLightModel is called between a	call
  to glBegin and the corresponding call	to glEnd.

Associated Gets

  glGet	with argument GL_LIGHT_MODEL_AMBIENT
  glGet	with argument GL_LIGHT_MODEL_LOCAL_VIEWER
  glGet	with argument GL_LIGHT_MODEL_TWO_SIDE
  glIsEnabled with argument GL_LIGHTING

See Also

  glLight, glMaterial

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glLineStipple.3gl

Name

  glLineStipple	- specify the line stipple pattern

C Specification

  void glLineStipple( GLint factor,
              GLushort pattern )

Parameters

  factor   Specifies a multiplier for each bit in the line stipple pattern.
       If factor is	3, for example,	each bit in the	pattern	will be	used
       three times before the next bit in the pattern is used.  factor is
       clamped to the range	[1, 256] and defaults to one.

  pattern  Specifies a 16-bit integer whose bit	pattern	determines which
       fragments of	a line will be drawn when the line is rasterized.
       Bit zero is used first, and the default pattern is all ones.

Description

  Line stippling masks out certain fragments produced by rasterization;	those
  fragments will not be	drawn.	The masking is achieved	by using three
  parameters: the 16-bit line stipple pattern pattern, the repeat count
  factor, and an integer stipple counter s.

  Counter s is reset to	zero whenever glBegin is called, and before each line
  segment of a glBegin(GL_LINES)/glEnd sequence	is generated.  It is
  incremented after each fragment of a unit width aliased line segment is
  generated, or	after each i fragments of an i width line segment are
  generated.  The i fragments associated with count s are masked out if

               pattern bit (s /	factor)	mod 16

  is zero, otherwise these fragments are sent to the frame buffer.  Bit	zero
  of pattern is	the least significant bit.

  Antialiased lines are	treated	as a sequence of 1xwidth rectangles for
  purposes of stippling.  Rectangle s is rasterized or not based on the
  fragment rule	described for aliased lines, counting rectangles rather	than
  groups of fragments.

  Line stippling is enabled or disabled	using glEnable and glDisable with
  argument GL_LINE_STIPPLE.  When enabled, the line stipple pattern is
  applied as described above.  When disabled, it is as if the pattern were
  all ones.  Initially,	line stippling is disabled.

Errors

  GL_INVALID_OPERATION is generated if glLineStipple is	executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_LINE_STIPPLE_PATTERN
  glGet	with argument GL_LINE_STIPPLE_REPEAT
  glIsEnabled with argument GL_LINE_STIPPLE

See Also

  glLineWidth, glPolygonStipple

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glLineWidth.3gl

Name

  glLineWidth -	specify	the width of rasterized	lines

C Specification

  void glLineWidth( GLfloat width )

Parameters

  width	 Specifies the width of	rasterized lines.  The default is 1.0.

Description

  glLineWidth specifies	the rasterized width of	both aliased and antialiased
  lines.  Using	a line width other than	1.0 has	different effects, depending
  on whether line antialiasing is enabled.  Line antialiasing is controlled
  by calling glEnable and glDisable with argument GL_LINE_SMOOTH.

  If line antialiasing is disabled, the	actual width is	determined by
  rounding the supplied	width to the nearest integer.  (If the rounding
  results in the value 0, it is	as if the line width were 1.) If |dx| >= |dy|,
  i pixels are filled in each column that is rasterized, where i is the
  rounded value	of width.  Otherwise, i	pixels are filled in each row that is
  rasterized.

  If antialiasing is enabled, line rasterization produces a fragment for each
  pixel	square that intersects the region lying	within the rectangle having
  width	equal to the current line width, length	equal to the actual length of
  the line, and	centered on the	mathematical line segment.  The	coverage
  value	for each fragment is the window	coordinate area	of the intersection
  of the rectangular region with the corresponding pixel square.  This value
  is saved and used in the final rasterization step.

  Not all widths can be	supported when line antialiasing is enabled. If	an
  unsupported width is requested, the nearest supported	width is used.	Only
  width	1.0 is guaranteed to be	supported; others depend on the
  implementation.  The range of	supported widths and the size difference
  between supported widths within the range can	be queried by calling glGet
  with arguments GL_LINE_WIDTH_RANGE and GL_LINE_WIDTH_GRANULARITY.

Notes

  The line width specified by glLineWidth is always returned when
  GL_LINE_WIDTH	is queried.  Clamping and rounding for aliased and
  antialiased lines have no effect on the specified value.

  Non-antialiased line width may be clamped to an implementation-dependent
  maximum.  Although this maximum cannot be queried, it	must be	no less	than
  the maximum value for	antialiased lines, rounded to the nearest integer
  value.

Errors

  GL_INVALID_VALUE is generated	if width is less than or equal to zero.

  GL_INVALID_OPERATION is generated if glLineWidth is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_LINE_WIDTH
  glGet	with argument GL_LINE_WIDTH_RANGE
  glGet	with argument GL_LINE_WIDTH_GRANULARITY
  glIsEnabled with argument GL_LINE_SMOOTH

See Also

  glEnable, glLineSmooth


glListBase.3gl

Name

  glListBase - set the display-list base for glCallLists

C Specification

  void glListBase( GLuint base )

Parameters

  base	Specifies an integer offset that will be added to glCallLists offsets
    to generate display-list names.	 Initial value is zero.

Description

  glCallLists specifies	an array of offsets.  Display-list names are
  generated by adding base to each offset.  Names that reference valid
  display lists	are executed; the others are ignored.

Errors

  GL_INVALID_OPERATION is generated if glListBase is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_LIST_BASE

See Also

  glCallLists

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glLoadIdentity.3gl

Name

  glLoadIdentity - replace the current matrix with the identity	matrix

C Specification

  void glLoadIdentity( void )

Description

  glLoadIdentity replaces the current matrix with the identity matrix.	It is
  semantically equivalent to calling glLoadMatrix with the identity matrix

                  |	1   0	0   0  |
                  |		       |
                  |	0   1	0   0  |
                  |	0   0	1   0  |
                  |		       |
                  |	0   0	0   1  |

  but in some cases it is more efficient.

Errors

  GL_INVALID_OPERATION is generated if glLoadIdentity is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_MATRIX_MODE
  glGet	with argument GL_MODELVIEW_MATRIX
  glGet	with argument GL_PROJECTION_MATRIX
  glGet	with argument GL_TEXTURE_MATRIX

See Also

  glLoadMatrix,	glMatrixMode, glMultMatrix, glPushMatrix

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glLoadMatrix.3gl

Name

  glLoadMatrixd, glLoadMatrixf - replace the current matrix with an arbitrary
  matrix

C Specification

  void glLoadMatrixd( const GLdouble *m	)
  void glLoadMatrixf( const GLfloat *m )

Parameters

  m  Specifies a pointer to a 4x4 matrix stored	in column-major	order as
     sixteen consecutive values.

Description

  glLoadMatrix replaces	the current matrix with	the one	specified in m.	 The
  current matrix is the	projection matrix, modelview matrix, or	texture
  matrix, determined by	the current matrix mode	(see glMatrixMode).

  m points to a	4x4 matrix of single- or double-precision floating-point
  values stored	in column-major	order.	That is, the matrix is stored as
  follows:

                  |a0  a4  a8   a12|
                  |		       |
                  |a1  a5  a9   a13|
                  |		       |
                  |a2  a6  a10  a14|
                  |		       |
                  |a3  a7  a11  a15|
                  |		       |

Errors

  GL_INVALID_OPERATION is generated if glLoadMatrix is called between a	call
  to glBegin and the corresponding call	to glEnd.

Associated Gets

  glGet	with argument GL_MATRIX_MODE
  glGet	with argument GL_MODELVIEW_MATRIX
  glGet	with argument GL_PROJECTION_MATRIX
  glGet	with argument GL_TEXTURE_MATRIX

See Also

  glLoadIdentity, glMatrixMode,	glMultMatrix, glPushMatrix

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glLoadName.3gl

Name

  glLoadName - load a name onto	the name stack

C Specification

  void glLoadName( GLuint name )

Parameters

  name	Specifies a name that will replace the top value on the	name stack.

Description

  The name stack is used during	selection mode to allow	sets of	rendering
  commands to be uniquely identified.  It consists of an ordered set of
  unsigned integers.  glLoadName causes	name to	replace	the value on the top
  of the name stack, which is initially	empty.

  The name stack is always empty while the render mode is not GL_SELECT.
  Calls	to glLoadName while the	render mode is not GL_SELECT are ignored.

Errors

  GL_INVALID_OPERATION is generated if glLoadName is called while the name
  stack	is empty.

  GL_INVALID_OPERATION is generated if glLoadName is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_NAME_STACK_DEPTH
  glGet	with argument GL_MAX_NAME_STACK_DEPTH

See Also

  glInitNames, glPushName, glRenderMode, glSelectBuffer

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glLogicOp.3gl

Name

  glLogicOp - specify a	logical	pixel operation	for color index	rendering

C Specification

  void glLogicOp( GLenum opcode	)

Parameters

  opcode  Specifies a symbolic constant	that selects a logical operation.
      The following	symbols	are accepted: GL_CLEAR,	GL_SET,	GL_COPY,
      GL_COPY_INVERTED, GL_NOOP, GL_INVERT,	GL_AND,	GL_NAND, GL_OR,
      GL_NOR, GL_XOR, GL_EQUIV, GL_AND_REVERSE, GL_AND_INVERTED,
      GL_OR_REVERSE, and GL_OR_INVERTED.

Description

  glLogicOp specifies a	logical	operation that,	when enabled, is applied
  between the incoming color index and the color index at the corresponding
  location in the frame	buffer.	 The logical operation is enabled or disabled
  with glEnable	and glDisable using the	symbolic constant GL_LOGIC_OP.

  opcode is a symbolic constant	chosen from the	list below.  In	the
  explanation of the logical operations, s represents the incoming color
  index	and d represents the index in the frame	buffer.	 Standard C-language
  operators are	used.  As these	bitwise	operators suggest, the logical
  operation is applied independently to	each bit pair of the source and
  destination indices.
             ------------------------------------
             |     opcode      | resulting value |
             ------------------------------------
             |	  GL_CLEAR     |	0	 |
             |	   GL_SET      |	1	 |
             |	  GL_COPY      |	s	 |
             |GL_COPY_INVERTED |       !s	 |
             |	  GL_NOOP      |	d	 |
             |	 GL_INVERT     |       !d	 |
             |	   GL_AND      |      s	& d	 |
             |	  GL_NAND      |    !(s	& d)	 |
             |	   GL_OR       |      s	| d	 |
             |	   GL_NOR      |    !(s	| d)	 |
             |	   GL_XOR      |      s	^ d	 |
             |	  GL_EQUIV     |    !(s	^ d)	 |
             | GL_AND_REVERSE  |     s & !d	 |
             |GL_AND_INVERTED  |     !s	& d	 |
             | GL_OR_REVERSE   |     s | !d	 |
             | GL_OR_INVERTED  |     !s	| d	 |
             ------------------------------------

Notes

  Logical pixel	operations are not applied to RGBA color buffers.

  When more than one color index buffer	is enabled for drawing,	logical
  operations are done separately for each enabled buffer, using	for the
  destination index the	contents of that buffer	(see glDrawBuffer).

  opcode must be one of	the sixteen accepted values.  Other values result in
  an error.

Errors

  GL_INVALID_ENUM is generated if opcode is not	an accepted value.

  GL_INVALID_OPERATION is generated if glLogicOp is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_LOGIC_OP_MODE
  glIsEnabled with argument GL_LOGIC_OP

See Also

  glAlphaFunc, glBlendFunc, glDrawBuffer, glEnable, glStencilOp

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glMap1.3gl

Name

  glMap1d, glMap1f - define a one-dimensional evaluator

C Specification

  void glMap1d(	GLenum target,
        GLdouble u1,
        GLdouble u2,
        GLint stride,
        GLint order,
        const GLdouble *points )
  void glMap1f(	GLenum target,
        GLfloat	u1,
        GLfloat	u2,
        GLint stride,
        GLint order,
        const GLfloat *points )

Parameters

  target  Specifies the	kind of	values that are	generated by the evaluator.
      Symbolic constants GL_MAP1_VERTEX_3, GL_MAP1_VERTEX_4,
      GL_MAP1_INDEX, GL_MAP1_COLOR_4, GL_MAP1_NORMAL,
      GL_MAP1_TEXTURE_COORD_1, GL_MAP1_TEXTURE_COORD_2,
      GL_MAP1_TEXTURE_COORD_3, and GL_MAP1_TEXTURE_COORD_4 are accepted.

  u1, u2  Specify a linear mapping of u, as presented to glEvalCoord1, to u,
      the variable that is evaluated by the	equations specified by this
      command.

  stride  Specifies the	number of floats or doubles between the	beginning of
      one control point and	the beginning of the next one in the data
      structure referenced in points.  This	allows control points to be
      embedded in arbitrary	data structures.  The only constraint is that
      the values for a particular control point must occupy	contiguous
      memory locations.

  order	  Specifies the	number of control points.  Must	be positive.

  points  Specifies a pointer to the array of control points.

Description

  Evaluators provide a way to use polynomial or	rational polynomial mapping
  to produce vertices, normals,	texture	coordinates, and colors.  The values
  produced by an evaluator are sent to further stages of GL processing just
  as if	they had been presented	using glVertex,	glNormal, glTexCoord, and
  glColor commands, except that	the generated values do	not update the
  current normal, texture coordinates, or color.

  All polynomial or rational polynomial	splines	of any degree (up to the
  maximum degree supported by the GL implementation) can be described using
  evaluators.  These include almost all	splines	used in	computer graphics,
  including B-splines, Bezier curves, Hermite splines, and so on.

  Evaluators define curves based on Bernstein polynomials.  Define p(u) as

                 n
                ---
                \   n
               p(u)  =   \ B (u)R
                 /  i    i
                /
                ---
                i=0

                                   n
  where	R  is a	control	point and B (u) is the ith Bernstein polynomial of
         i                         i
  degree n (order = n+1):

                n	     |n|  i      n-i
               B (u)   = | | u  (1-u)
                i	     |i|

  Recall that

                 0	       |n|
                0  = 1 and | |  = 1
                       |0|

  glMap1 is used to define the basis and to specify what kind of values	are
  produced.  Once defined, a map can be	enabled	and disabled by	calling
  glEnable and glDisable with the map name, one	of the nine predefined values
  for target described below.  glEvalCoord1 evaluates the one-dimensional
  maps that are	enabled.  When glEvalCoord1 presents a value u,	the Bernstein
  functions are	evaluated using	u, where

                       u - u1
                  u =  -------
                       u2 - u1

  target is a symbolic constant	that indicates what kind of control points
  are provided in points, and what output is generated when the	map is
  evaluated.  It can assume one	of nine	predefined values:

  GL_MAP1_VERTEX_3	   Each	control	point is three floating-point values
               representing	x, y, and z.  Internal glVertex3
               commands are	generated when the map is evaluated.

  GL_MAP1_VERTEX_4	   Each	control	point is four floating-point values
               representing	x, y, z, and w.	 Internal glVertex4
               commands are	generated when the map is evaluated.

  GL_MAP1_INDEX		   Each	control	point is a single floating-point
               value representing a	color index.  Internal
               glIndex commands are	generated when the map is
               evaluated.  The current index is not	updated	with
               the value of	these glIndex commands,	however.

  GL_MAP1_COLOR_4	   Each	control	point is four floating-point values
               representing	red, green, blue, and alpha.
               Internal glColor4 commands are generated when the
               map is evaluated.  The current color	is not
               updated with	the value of these glColor4 commands,
               however.

  GL_MAP1_NORMAL	   Each	control	point is three floating-point values
               representing	the x, y, and z	components of a
               normal vector.  Internal glNormal commands are
               generated when the map is evaluated.	 The current
               normal is not updated with the value	of these
               glNormal commands, however.

  GL_MAP1_TEXTURE_COORD_1  Each	control	point is a single floating-point
               value representing the s texture coordinate.
               Internal glTexCoord1	commands are generated when
               the map is evaluated.  The current texture
               coordinates are not updated with the	value of
               these glTexCoord commands, however.

  GL_MAP1_TEXTURE_COORD_2  Each	control	point is two floating-point values
               representing	the s and t texture coordinates.
               Internal glTexCoord2	commands are generated when
               the map is evaluated.  The current texture
               coordinates are not updated with the	value of
               these glTexCoord commands, however.

  GL_MAP1_TEXTURE_COORD_3  Each	control	point is three floating-point values
               representing	the s, t, and r	texture	coordinates.
               Internal glTexCoord3	commands are generated when
               the map is evaluated.  The current texture
               coordinates are not updated with the	value of
               these glTexCoord commands, however.

  GL_MAP1_TEXTURE_COORD_4  Each	control	point is four floating-point values
               representing	the s, t, r, and q texture
               coordinates.	 Internal glTexCoord4 commands are
               generated when the map is evaluated.	 The current
               texture coordinates are not updated with the	value
               of these glTexCoord commands, however.

  stride, order, and points define the array addressing	for accessing the
  control points.  points is the location of the first control point, which
  occupies one,	two, three, or four contiguous memory locations, depending on
  which	map is being defined.  order is	the number of control points in	the
  array.  stride tells how many	float or double	locations to advance the
  internal memory pointer to reach the next control point.

Notes

  As is	the case with all GL commands that accept pointers to data, it is as
  if the contents of points were copied	by glMap1 before it returned.
  Changes to the contents of points have no effect after glMap1	is called.

Errors

  GL_INVALID_ENUM is generated if target is not	an accepted value.

  GL_INVALID_VALUE is generated	if u1 is equal to u2.

  GL_INVALID_VALUE is generated	if stride is less than the number of values
  in a control point.

  GL_INVALID_VALUE is generated	if order is less than one or greater than
  GL_MAX_EVAL_ORDER.

  GL_INVALID_OPERATION is generated if glMap1 is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGetMap
  glGet	with argument GL_MAX_EVAL_ORDER
  glIsEnabled with argument GL_MAP1_VERTEX_3
  glIsEnabled with argument GL_MAP1_VERTEX_4
  glIsEnabled with argument GL_MAP1_INDEX
  glIsEnabled with argument GL_MAP1_COLOR_4
  glIsEnabled with argument GL_MAP1_NORMAL
  glIsEnabled with argument GL_MAP1_TEXTURE_COORD_1
  glIsEnabled with argument GL_MAP1_TEXTURE_COORD_2
  glIsEnabled with argument GL_MAP1_TEXTURE_COORD_3
  glIsEnabled with argument GL_MAP1_TEXTURE_COORD_4

See Also

  glBegin, glColor, glEnable, glEvalCoord, glEvalMesh, glEvalPoint, glMap2,
  glMapGrid, glNormal, glTexCoord, glVertex

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glMap2.3gl

Name

  glMap2d, glMap2f - define a two-dimensional evaluator

C Specification

  void glMap2d(	GLenum target,
        GLdouble u1,
        GLdouble u2,
        GLint ustride,
        GLint uorder,
        GLdouble v1,
        GLdouble v2,
        GLint vstride,
        GLint vorder,
        const GLdouble *points )
  void glMap2f(	GLenum target,
        GLfloat	u1,
        GLfloat	u2,
        GLint ustride,
        GLint uorder,
        GLfloat	v1,
        GLfloat	v2,
        GLint vstride,
        GLint vorder,
        const GLfloat *points )

Parameters

  target   Specifies the kind of values	that are generated by the evaluator.
       Symbolic constants GL_MAP2_VERTEX_3,	GL_MAP2_VERTEX_4,
       GL_MAP2_INDEX, GL_MAP2_COLOR_4, GL_MAP2_NORMAL,
       GL_MAP2_TEXTURE_COORD_1, GL_MAP2_TEXTURE_COORD_2,
       GL_MAP2_TEXTURE_COORD_3, and	GL_MAP2_TEXTURE_COORD_4	are accepted.

  u1, u2   Specify a linear mapping of u, as presented to glEvalCoord2, to u,
       one of the two variables that is evaluated by the equations
       specified by	this command.

  ustride  Specifies the number	of floats or doubles between the beginning of
       control point R   and the beginning of control point	R        ,
                          ij                                     (i+1) j
       where i and j are the u and v control point indices,	respectively.
       This	allows control points to be embedded in	arbitrary data
       structures.	The only constraint is that the	values for a
       particular control point must occupy	contiguous memory locations.

  uorder   Specifies the dimension of the control point	array in the u axis.
       Must	be positive.

  v1, v2   Specify a linear mapping of v, as presented to glEvalCoord2,	to v,
       one of the two variables that is evaluated by the equations
       specified by	this command.

  vstride  Specifies the number	of floats or doubles between the beginning of
       control point R   and the beginning of control point	R        ,
              ij                                     (i+1) j
       where i and j are the u and v control point indices,	respectively.
       This	allows control points to be embedded in	arbitrary data
       structures.	The only constraint is that the	values for a
       particular control point must occupy	contiguous memory locations.

  vorder   Specifies the dimension of the control point	array in the v axis.
       Must	be positive.

  points   Specifies a pointer to the array of control points.

Description

  Evaluators provide a way to use polynomial or	rational polynomial mapping
  to produce vertices, normals,	texture	coordinates, and colors.  The values
  produced by an evaluator are sent on to further stages of GL processing
  just as if they had been presented using glVertex, glNormal, glTexCoord,
  and glColor commands, except that the generated values do not	update the
  current normal, texture coordinates, or color.

  All polynomial or rational polynomial	splines	of any degree (up to the
  maximum degree supported by the GL implementation) can be described using
  evaluators.  These include almost all	surfaces used in computer graphics,
  including B-spline surfaces, NURBS surfaces, Bezier surfaces,	and so on.

  Evaluators define surfaces based on bivariate	Bernstein polynomials.
  Define p(u,v) as

                    n   m
           --- ---
              \   \   n    m
        p(u,v)  =   \   \ B (u)B (v) R
            /   /  i    j     ij
           /   /
           --- ---
           i=0 j=0

                                 n
  where	R   is a control point,	B (u) is the ith Bernstein polynomial of
         ij                      i
  degree n (uorder = n+1)

                n	     |n| i     n-i
               B (u)  =  | |u (1-u)
                i	     |i|

       m
  and B (v') is the jth Bernstein polynomial of degree m (vorder = m+1)
       j
                m	     |m| j     m-j
               B (v)  =  | |v (1-v)
                j	     |j|

  Recall that

                 0	       |n|
                0  = 1 and | | = 1
                       |0|

  glMap2 is used to define the basis and to specify what kind of values	are
  produced.  Once defined, a map can be	enabled	and disabled by	calling
  glEnable and glDisable with the map name, one	of the nine predefined values
  for target, described	below.	When glEvalCoord2 presents values u and	v,
  the bivariate Bernstein polynomials are evaluated using u and v, where

                    u - u1
                 u  =  -------
                        u2 - u1

                    v - v1
                 v  =  -------
                        v2 - v1

  target is a symbolic constant	that indicates what kind of control points
  are provided in points, and what output is generated when the	map is
  evaluated.  It can assume one	of nine	predefined values:

  GL_MAP2_VERTEX_3	   Each	control	point is three floating-point values
               representing	x, y, and z.  Internal glVertex3
               commands are	generated when the map is evaluated.

  GL_MAP2_VERTEX_4	   Each	control	point is four floating-point values
               representing	x, y, z, and w.	 Internal glVertex4
               commands are	generated when the map is evaluated.

  GL_MAP2_INDEX		   Each	control	point is a single floating-point
               value representing a	color index.  Internal
               glIndex commands are	generated when the map is
               evaluated.  The current index is not	updated	with
               the value of	these glIndex commands,	however.

  GL_MAP2_COLOR_4	   Each	control	point is four floating-point values
               representing	red, green, blue, and alpha.
               Internal glColor4 commands are generated when the
               map is evaluated.  The current color	is not
               updated with	the value of these glColor4 commands,
               however.

  GL_MAP2_NORMAL	   Each	control	point is three floating-point values
               representing	the x, y, and z	components of a
               normal vector.  Internal glNormal commands are
               generated when the map is evaluated.	 The current
               normal is not updated with the value	of these
               glNormal commands, however.

  GL_MAP2_TEXTURE_COORD_1  Each	control	point is a single floating-point
               value representing the s texture coordinate.
               Internal glTexCoord1	commands are generated when
               the map is evaluated.  The current texture
               coordinates are not updated with the	value of
               these glTexCoord commands, however.

  GL_MAP2_TEXTURE_COORD_2  Each	control	point is two floating-point values
               representing	the s and t texture coordinates.
               Internal glTexCoord2	commands are generated when
               the map is evaluated.  The current texture
               coordinates are not updated with the	value of
               these glTexCoord commands, however.

  GL_MAP2_TEXTURE_COORD_3  Each	control	point is three floating-point values
               representing	the s, t, and r	texture	coordinates.
               Internal glTexCoord3	commands are generated when
               the map is evaluated.  The current texture
               coordinates are not updated with the	value of
               these glTexCoord commands, however.

  GL_MAP2_TEXTURE_COORD_4  Each	control	point is four floating-point values
               representing	the s, t, r, and q texture
               coordinates.	 Internal glTexCoord4 commands are
               generated when the map is evaluated.	 The current
               texture coordinates are not updated with the	value
               of these glTexCoord commands, however.

  ustride, uorder, vstride, vorder, and	points define the array	addressing
  for accessing	the control points.  points is the location of the first
  control point, which occupies	one, two, three, or four contiguous memory
  locations, depending on which	map is being defined.  There are
  uorderxvorder	control	points in the array.  ustride tells how	many float or
  double locations are skipped to advance the internal memory pointer from
  control point	R   to control point R	    .  vstride tells how many float
                 ij                   (i+1)j
  or double locations are skipped to advance the internal memory pointer from
  control point	R   to control point R	    .
         ij		      i(j+1)

Notes

  As is	the case with all GL commands that accept pointers to data, it is as
  if the contents of points were copied	by glMap2 before it returned.
  Changes to the contents of points have no effect after glMap2	is called.

Errors

  GL_INVALID_ENUM is generated if target is not	an accepted value.

  GL_INVALID_VALUE is generated	if u1 is equal to u2, or if v1 is equal	to
  v2.

  GL_INVALID_VALUE is generated	if either ustride or vstride is	less than the
  number of values in a	control	point.

  GL_INVALID_VALUE is generated	if either uorder or vorder is less than	one
  or greater than GL_MAX_EVAL_ORDER.

  GL_INVALID_OPERATION is generated if glMap2 is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGetMap
  glGet	with argument GL_MAX_EVAL_ORDER
  glIsEnabled with argument GL_MAP2_VERTEX_3
  glIsEnabled with argument GL_MAP2_VERTEX_4
  glIsEnabled with argument GL_MAP2_INDEX
  glIsEnabled with argument GL_MAP2_COLOR_4
  glIsEnabled with argument GL_MAP2_NORMAL
  glIsEnabled with argument GL_MAP2_TEXTURE_COORD_1
  glIsEnabled with argument GL_MAP2_TEXTURE_COORD_2
  glIsEnabled with argument GL_MAP2_TEXTURE_COORD_3
  glIsEnabled with argument GL_MAP2_TEXTURE_COORD_4

See Also

  glBegin, glColor, glEnable, glEvalCoord, glEvalMesh, glEvalPoint, glMap1,
  glMapGrid, glNormal, glTexCoord, glVertex

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glMapGrid.3gl

Name

  glMapGrid1d, glMapGrid1f, glMapGrid2d, glMapGrid2f - define a	one- or	two-
  dimensional mesh

C Specification

  void glMapGrid1d( GLint un,
            GLdouble u1,
            GLdouble u2	)
  void glMapGrid1f( GLint un,
            GLfloat u1,
            GLfloat u2 )
  void glMapGrid2d( GLint un,
            GLdouble u1,
            GLdouble u2,
            GLint vn,
            GLdouble v1,
            GLdouble v2	)
  void glMapGrid2f( GLint un,
            GLfloat u1,
            GLfloat u2,
            GLint vn,
            GLfloat v1,
            GLfloat v2 )

Parameters

  un  Specifies	the number of partitions in the	grid range interval [u1, u2].
      Must be positive.

  u1, u2
      Specify the mappings for integer grid domain values i=0 and i=un.

  vn  Specifies	the number of partitions in the	grid range interval [v1, v2]
      (glMapGrid2 only).

  v1, v2
      Specify the mappings for integer grid domain values j=0 and j=vn
      (glMapGrid2 only).

Description

  glMapGrid and	glEvalMesh are used in tandem to efficiently generate and
  evaluate a series of evenly spaced map domain	values.	 glEvalMesh steps
  through the integer domain of	a one- or two-dimensional grid,	whose range
  is the domain	of the evaluation maps specified by glMap1 and glMap2.

  glMapGrid1 and glMapGrid2 specify the	linear grid mappings between the i
  (or i	and j) integer grid coordinates, to the	u (or u	and v) floating-point
  evaluation map coordinates.  See glMap1 and glMap2 for details of how	u and
  v coordinates	are evaluated.

  glMapGrid1 specifies a single	linear mapping such that integer grid
  coordinate 0 maps exactly to u1, and integer grid coordinate un maps
  exactly to u2.  All other integer grid coordinates i are mapped such that

                 u = i(u2-u1)/un + u1

  glMapGrid2 specifies two such	linear mappings.  One maps integer grid
  coordinate i=0 exactly to u1,	and integer grid coordinate i=un exactly to
  u2.  The other maps integer grid coordinate j=0 exactly to v1, and integer
  grid coordinate j=vn exactly to v2.  Other integer grid coordinates i	and j
  are mapped such that

                 u = i(u2-u1)/un + u1

                 v = j(v2-v1)/vn + v1

  The mappings specified by glMapGrid are used identically by glEvalMesh and
  glEvalPoint.

Errors

  GL_INVALID_VALUE is generated	if either un or	vn is not positive.

  GL_INVALID_OPERATION is generated if glMapGrid is called between a call to
  glBegin and the corresponding	call to	glEnd.

Associated Gets

  glGet	with argument GL_MAP1_GRID_DOMAIN
  glGet	with argument GL_MAP2_GRID_DOMAIN
  glGet	with argument GL_MAP1_GRID_SEGMENTS
  glGet	with argument GL_MAP2_GRID_SEGMENTS

See Also

  glEvalCoord, glEvalMesh, glEvalPoint,	glMap1,	glMap2

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glMaterial.3gl

Name

  glMaterialf, glMateriali, glMaterialfv, glMaterialiv - specify material
  parameters for the lighting model

C Specification

  void glMaterialf( GLenum face,
            GLenum pname,
            GLfloat param )
  void glMateriali( GLenum face,
            GLenum pname,
            GLint param	)

Parameters

  face	  Specifies which face or faces	are being updated.  Must be one	of
      GL_FRONT, GL_BACK, or	GL_FRONT_AND_BACK.

  pname	  Specifies the	single-valued material parameter of the	face or	faces
      that is being	updated.  Must be GL_SHININESS.

  param	  Specifies the	value that parameter GL_SHININESS will be set to.

C Specification

  void glMaterialfv( GLenum face,
             GLenum pname,
             const GLfloat *params )
  void glMaterialiv( GLenum face,
             GLenum pname,
             const GLint *params )

Parameters

  face Specifies which face or faces are being updated.	 Must be one of
       GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK.

  pname
       Specifies the material parameter	of the face or faces that is being
       updated.	 Must be one of	GL_AMBIENT, GL_DIFFUSE,	GL_SPECULAR,
       GL_EMISSION, GL_SHININESS, GL_AMBIENT_AND_DIFFUSE,  or
       GL_COLOR_INDEXES.

  params
       Specifies a pointer to the value	or values that pname will be set to.

Description

  glMaterial assigns values to material	parameters.  There are two matched
  sets of material parameters.	One, the front-facing set, is used to shade
  points, lines, bitmaps, and all polygons (when two-sided lighting is
  disabled), or	just front-facing polygons (when two-sided lighting is
  enabled).  The other set, back-facing, is used to shade back-facing
  polygons only	when two-sided lighting	is enabled.  Refer to the
  glLightModel reference page for details concerning one- and two-sided
  lighting calculations.

  glMaterial takes three arguments.  The first,	face, specifies	whether	the
  GL_FRONT materials, the GL_BACK materials, or	both GL_FRONT_AND_BACK
  materials will be modified.  The second, pname, specifies which of several
  parameters in	one or both sets will be modified.  The	third, params,
  specifies what value or values will be assigned to the specified parameter.

  Material parameters are used in the lighting equation	that is	optionally
  applied to each vertex.  The equation	is discussed in	the glLightModel
  reference page.  The parameters that can be specified	using glMaterial, and
  their	interpretations	by the lighting	equation, are as follows:

  GL_AMBIENT	      params contains four integer or floating-point values
              that specify the ambient RGBA reflectance	of the
              material.	 Integer values	are mapped linearly such that
              the most positive	representable value maps to 1.0, and
              the most negative	representable value maps to -1.0.
              Floating-point values are	mapped directly.  Neither
              integer nor floating-point values	are clamped.  The
              default ambient reflectance for both front- and back-
              facing materials is (0.2,	0.2, 0.2, 1.0).

  GL_DIFFUSE	      params contains four integer or floating-point values
              that specify the diffuse RGBA reflectance	of the
              material.	 Integer values	are mapped linearly such that
              the most positive	representable value maps to 1.0, and
              the most negative	representable value maps to -1.0.
              Floating-point values are	mapped directly.  Neither
              integer nor floating-point values	are clamped.  The
              default diffuse reflectance for both front- and back-
              facing materials is (0.8,	0.8, 0.8, 1.0).

  GL_SPECULAR	      params contains four integer or floating-point values
              that specify the specular	RGBA reflectance of the
              material.	 Integer values	are mapped linearly such that
              the most positive	representable value maps to 1.0, and
              the most negative	representable value maps to -1.0.
              Floating-point values are	mapped directly.  Neither
              integer nor floating-point values	are clamped.  The
              default specular reflectance for both front- and back-
              facing materials is (0.0,	0.0, 0.0, 1.0).

  GL_EMISSION	      params contains four integer or floating-point values
              that specify the RGBA emitted light intensity of the
              material.	 Integer values	are mapped linearly such that
              the most positive	representable value maps to 1.0, and
              the most negative	representable value maps to -1.0.
              Floating-point values are	mapped directly.  Neither
              integer nor floating-point values	are clamped.  The
              default emission intensity for both front- and back-
              facing materials is (0.0,	0.0, 0.0, 1.0).

  GL_SHININESS	      params is	a single integer or floating-point value that
              specifies	the RGBA specular exponent of the material.
              Integer and floating-point values	are mapped directly.
              Only values in the range [0,128] are accepted.  The
              default specular exponent	for both front-	and back-
              facing materials is 0.

  GL_AMBIENT_AND_DIFFUSE
              Equivalent to calling glMaterial twice with the same
              parameter	values,	once with GL_AMBIENT and once with
              GL_DIFFUSE.

  GL_COLOR_INDEXES    params contains three integer or floating-point values
              specifying the color indices for ambient,	diffuse, and
              specular lighting.  These	three values, and
              GL_SHININESS, are	the only material values used by the
              color index mode lighting	equation.  Refer to the
              glLightModel reference page for a	discussion of color
              index lighting.

Notes

  The material parameters can be updated at any	time.  In particular,
  glMaterial can be called between a call to glBegin and the corresponding
  call to glEnd.  If only a single material parameter is to be changed per
  vertex, however, glColorMaterial is preferred	over glMaterial	(see
  glColorMaterial).

Errors

  GL_INVALID_ENUM is generated if either face or pname is not an accepted
  value.

  GL_INVALID_VALUE is generated	if a specular exponent outside the range
  [0,128] is specified.

Associated Gets

  glGetMaterial

See Also

  glColorMaterial, glLight, glLightModel

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glMatrixMode.3gl

Name

  glMatrixMode - specify which matrix is the current matrix

C Specification

  void glMatrixMode( GLenum mode )

Parameters

  mode	Specifies which	matrix stack is	the target for subsequent matrix
    operations.  Three values are accepted:	GL_MODELVIEW, GL_PROJECTION,
    and GL_TEXTURE.	 The default value is GL_MODELVIEW.

Description

  glMatrixMode sets the	current	matrix mode.  mode can assume one of three
  values:

  GL_MODELVIEW	      Applies subsequent matrix	operations to the modelview
              matrix stack.

  GL_PROJECTION	      Applies subsequent matrix	operations to the projection
              matrix stack.

  GL_TEXTURE	      Applies subsequent matrix	operations to the texture
              matrix stack.

Errors

  GL_INVALID_ENUM is generated if mode is not an accepted value.

  GL_INVALID_OPERATION is generated if glMatrixMode is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_MATRIX_MODE

See Also

  glLoadMatrix,	glPushMatrix

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glMultMatrix.3gl

Name

  glMultMatrixd, glMultMatrixf - multiply the current matrix by	an arbitrary
  matrix

C Specification

  void glMultMatrixd( const GLdouble *m	)
  void glMultMatrixf( const GLfloat *m )

Parameters

  m  Specifies a pointer a to 4x4 matrix stored	in column-major	order as
     sixteen consecutive values.

Description

  glMultMatrix multiplies the current matrix with the one specified in m.
  That is, if M	is the current matrix and T is the matrix passed to
  glMultMatrix,	then M is replaced with	M · T.

  The current matrix is	the projection matrix, modelview matrix, or texture
  matrix, determined by	the current matrix mode	(see glMatrixMode).

  m points to a	4x4 matrix of single- or double-precision floating-point
  values stored	in column-major	order.	That is, the matrix is stored as

                  |a0  a4  a8   a12|
                  |		       |
                  |a1  a5  a9   a13|
                  |		       |
                  |a2  a6  a10  a14|
                  |		       |
                  |a3  a7  a11  a15|

Errors

  GL_INVALID_OPERATION is generated if glMultMatrix is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_MATRIX_MODE
  glGet	with argument GL_MODELVIEW_MATRIX
  glGet	with argument GL_PROJECTION_MATRIX
  glGet	with argument GL_TEXTURE_MATRIX

See Also

  glMatrixMode,	glLoadIdentity,	glLoadMatrix, glPushMatrix

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glNewList.3gl

Name

  glNewList, glEndList - create	or replace a display list

C Specification

  void glNewList( GLuint list,
          GLenum mode )

Parameters

  list	Specifies the display list name.

  mode	Specifies the compilation mode,	which can be GL_COMPILE	or
    GL_COMPILE_AND_EXECUTE.

C Specification

  void glEndList( void )

Description

  Display lists	are groups of GL commands that have been stored	for
  subsequent execution.	 The display lists are created with glNewList.	All
  subsequent commands are placed in the	display	list, in the order issued,
  until	glEndList is called.

  glNewList has	two arguments.	The first argument, list, is a positive
  integer that becomes the unique name for the display list.  Names can	be
  created and reserved with glGenLists and tested for uniqueness with
  glIsList.  The second	argument, mode,	is a symbolic constant that can
  assume one of	two values:

  GL_COMPILE	      Commands are merely compiled.

  GL_COMPILE_AND_EXECUTE
              Commands are executed as they are	compiled into the
              display list.

  Certain commands are not compiled into the display list, but are executed
  immediately, regardless of the display-list mode.  These commands are
  glIsList, glGenLists,	glDeleteLists, glFeedbackBuffer, glSelectBuffer,
  glRenderMode,	glReadPixels, glPixelStore, glFlush, glFinish, glIsEnabled,
  and all of the glGet routines.

  When glEndList is encountered, the display-list definition is	completed by
  associating the list with the	unique name list (specified in the glNewList
  command). If a display list with name	list already exists, it	is replaced
  only when glEndList is called.

Notes

  glCallList and glCallLists can be entered into display lists.	 The commands
  in the display list or lists executed	by glCallList or glCallLists are not
  included in the display list being created, even if the list creation	mode
  is GL_COMPILE_AND_EXECUTE.

  A display list is just a group of commands and arguments, so errors
  generated by commands	in a display list must be generated when the list is
  executed. If the list	is created in GL_COMPILE mode, errors are not
  generated until the list is executed.

Errors

  GL_INVALID_VALUE is generated	if list	is zero.

  GL_INVALID_ENUM is generated if mode is not an accepted value.

  GL_INVALID_OPERATION is generated if glEndList is called without a
  preceding glNewList, or if glNewList is called while a display list is
  being	defined.

  GL_INVALID_OPERATION is generated if glNewList or glEndList is executed
  between the execution	of glBegin and the corresponding execution of glEnd.

  GL_OUT_OF_MEMORY is generated	if there is insufficient memory	to compile
  the display list.

Associated Gets

  glIsList

See Also

  glCallList, glCallLists, glDeleteLists, glGenLists

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glNormal.3gl

Name

  glNormal3b, glNormal3d, glNormal3f, glNormal3i, glNormal3s, glNormal3bv,
  glNormal3dv, glNormal3fv, glNormal3iv, glNormal3sv - set the current normal
  vector

C Specification

  void glNormal3b( GLbyte nx,
           GLbyte ny,
           GLbyte nz )
  void glNormal3d( GLdouble nx,
           GLdouble ny,
           GLdouble nz )
  void glNormal3f( GLfloat nx,
           GLfloat ny,
           GLfloat nz )
  void glNormal3i( GLint nx,
           GLint ny,
           GLint nz )
  void glNormal3s( GLshort nx,
           GLshort ny,
           GLshort nz )

Parameters

  nx, ny, nz
        Specify the	x, y, and z coordinates	of the new current normal.
        The	initial	value of the current normal is (0,0,1).

C Specification

  void glNormal3bv( const GLbyte *v )
  void glNormal3dv( const GLdouble *v )
  void glNormal3fv( const GLfloat *v )
  void glNormal3iv( const GLint	*v )
  void glNormal3sv( const GLshort *v )

Parameters

  v    Specifies a pointer to an array of three	elements: the x, y, and	z
       coordinates of the new current normal.

Description

  The current normal is	set to the given coordinates whenever glNormal is
  issued.  Byte, short,	or integer arguments are converted to floating-point
  format with a	linear mapping that maps the most positive representable
  integer value	to 1.0,	and the	most negative representable integer value to
  -1.0.

  Normals specified with glNormal need not have	unit length.  If
  normalization	is enabled, then normals specified with	glNormal are
  normalized after transformation.  Normalization is controlled	using
  glEnable and glDisable with the argument GL_NORMALIZE.  By default,
  normalization	is disabled.

Notes

  The current normal can be updated at any time.  In particular, glNormal can
  be called between a call to glBegin and the corresponding call to glEnd.

Associated Gets

  glGet	with argument GL_CURRENT_NORMAL
  glIsEnable with argument GL_NORMALIZE

See Also

  glBegin, glColor, glIndex, glTexCoord, glVertex

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glNormalPointerEXT.3gl

Name

  glNormalPointerEXT - define a	array of normals

C Specification

  void glNormalPointerEXT( GLenum type,
               GLsizei stride,
               GLsizei count,
               const GLvoid	*pointer )

Parameters

  type	   Specifies the the data type of each coordinate in the array.
       Symbolic constants GL_BYTE, GL_SHORT, GL_INT, GL_FLOAT, or
       GL_DOUBLE_EXT are accepted.

  stride   Specifies the byte offset between consecutive normals.

  count	   Specifies the number	of normals, counting from the first, that are
       static.

  pointer  Specifies a pointer to the first coordinate of the first normal in
       the array.

Description

  glNormalPointerEXT specifies the location and data format of an array of
  normals to use when rendering using the vertex array extension. type specifies
  the data type of the normal coordinates and stride gives the byte stride
  from one normal to the next allowing vertexes and attributes to be packed
  into a single array or stored in separate arrays.  (Single-array storage may
  be more efficient on some implementations.) count indicates the number of
  array elements (counting from the first) that are static. Static elements
  may be modified by the application, but once they are modified, the
  application must explicitly respecify the array before using it for any
  rendering. When a normal array is specified, type, stride, count and pointer
  are saved as client-side state, and static array elements may be cached by
  the implementation.

  The normal array is enabled and disabled using glEnable and glDisable	with
  the argument GL_NORMAL_ARRAY_EXT. If enabled,	the normal array is used when
  glDrawArraysEXT or glArrayElementEXT is called.

  Use glDrawArraysEXT to define	a sequence of primitives (all of the same
  type)	from pre-specified vertex and vertex attribute arrays.	Use
  glArrayElementEXT to specify primitives by indexing vertexes and vertex
  attributes.

Notes

  Non-static array elements are	not accessed until glArrayElementEXT or
  glDrawArraysEXT is executed.

  By default the normal	array is disabled and it won't be accessed when
  glArrayElementEXT or glDrawArraysEXT is called.

  Although it is not an error to call glNormalPointerEXT between the
  execution of glBegin and the corresponding execution of glEnd, the results
  are undefined.

  glNormalPointerEXT will typically be implemented on the client side with no
  protocol.

  Since	the normal array parameters are	client side state, they	are not	saved
  or restored by glPushAttrib and glPopAttrib.

  glNormalPointerEXT commands are not entered into display lists.

  glNormalPointerEXT is	part of	the EXT_vertex_array extension, not
  part of the core GL command set. If "GL_EXT_vertex_array" is
  included in the string returned by glGetString, when called with argument
  GL_EXTENSIONS, extension EXT_vertex_array is supported.

Errors

  GL_INVALID_ENUM is generated if type is not an accepted value.

  GL_INVALID_VALUE is generated	if stride or count is negative.

Associated Gets

  glIsEnabled with argument GL_NORMAL_ARRAY_EXT
  glGet	with argument GL_NORMAL_ARRAY_TYPE_EXT
  glGet	with argument GL_NORMAL_ARRAY_STRIDE_EXT
  glGet	with argument GL_NORMAL_ARRAY_COUNT_EXT
  glGetPointervEXT with	argument GL_NORMAL_ARRAY_POINTER_EXT

See Also

  glArrayElementEXT, glColorPointerEXT,	glDrawArraysEXT,
  glEdgeFlagPointerEXT,	glGetPointervEXT, glIndexPointerEXT,
  glTexCoordPointerEXT,	glVertexPointerEXT, glEnable

Introduction | Alphabetic | Specification

Last Edited: Fri Feb 24, 1995

AFV


glOrtho.3gl

Name

  glOrtho - multiply the current matrix	by an orthographic matrix

C Specification

  void glOrtho(	GLdouble left,
        GLdouble right,
        GLdouble bottom,
        GLdouble top,
        GLdouble near,
        GLdouble far )

Parameters

  left,	right Specify the coordinates for the left and right vertical
          clipping planes.

  bottom, top Specify the coordinates for the bottom and top horizontal
          clipping planes.

  near,	far   Specify the distances to the nearer and farther depth clipping
          planes.  These distances are negative if the plane is to be
          behind the viewer.

Description

  glOrtho describes a perspective matrix that produces a parallel projection.
  (left, bottom,  -near) and (right, top,  -near) specify the points on	the
  near clipping	plane that are mapped to the lower left	and upper right
  corners of the window, respectively, assuming	that the eye is	located	at
  (0, 0, 0).  -far specifies the location of the far clipping plane.  Both
  near and far can be either positive or negative.  The	corresponding matrix
  is

            |     2	     		          |
            |----------      0          0      t  |
            |right-left				x |
            |					  |
            |		      2    		  |
            |    0       ----------	0      t  |
            |	  	 top-bottom	        y |
            |                                     |
            |					  |
            |	 0	     0	        -2        |
            |                        --------  t  |
            |			     far-near	z |
            |                                     |
            |	 0	     0		0      1  |

  where
                          right+left
                   t  = - ----------
                x     right-left

                      top+bottom
                   t  = - ----------
                y     top-bottom

                       far+near
                t  = - --------
                 z     far-near

  The current matrix is	multiplied by this matrix with the result replacing
  the current matrix.  That is,	if M is	the current matrix and O is the	ortho
  matrix, then M is replaced with M · O.

  Use glPushMatrix and glPopMatrix to save and restore the current matrix
  stack.

Errors

  GL_INVALID_OPERATION is generated if glOrtho is executed between the
  execution of	glBegin	and the	corresponding execution	of glEnd.

Associated Gets

  glGet	with argument GL_MATRIX_MODE
  glGet	with argument GL_MODELVIEW_MATRIX
  glGet	with argument GL_PROJECTION_MATRIX
  glGet	with argument GL_TEXTURE_MATRIX

See Also

  glFrustum, glMatrixMode, glMultMatrix, glPushMatrix, glViewport

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glPassThrough.3gl

Name

  glPassThrough	- place	a marker in the	feedback buffer

C Specification

  void glPassThrough( GLfloat token )

Parameters

  token	 Specifies a marker value to be	placed in the feedback buffer
     following a GL_PASS_THROUGH_TOKEN.

Description

  Feedback is a	GL render mode.	 The mode is selected by calling glRenderMode
  with GL_FEEDBACK.  When the GL is in feedback	mode, no pixels	are produced
  by rasterization.  Instead, information about	primitives that	would have
  been rasterized is fed back to the application using the GL.	See
  glFeedbackBuffer for a description of	the feedback buffer and	the values in
  it.

  glPassThrough	inserts	a user-defined marker in the feedback buffer when it
  is executed in feedback mode.	token is returned as if	it were	a primitive;
  it is	indicated with its own unique identifying value:
  GL_PASS_THROUGH_TOKEN.  The order of glPassThrough commands with respect to
  the specification of graphics	primitives is maintained.

Notes

  glPassThrough	is ignored if the GL is	not in feedback	mode.

Errors

  GL_INVALID_OPERATION is generated if glPassThrough is	executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_RENDER_MODE

See Also

  glFeedbackBuffer, glRenderMode

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glPixelMap.3gl

Name

  glPixelMapfv,	glPixelMapuiv, glPixelMapusv - set up pixel transfer maps

C Specification

  void glPixelMapfv( GLenum map,
             GLint mapsize,
             const GLfloat *values )
  void glPixelMapuiv( GLenum map,
              GLint mapsize,
              const GLuint *values )
  void glPixelMapusv( GLenum map,
              GLint mapsize,
              const GLushort *values )

Parameters

  map	   Specifies a symbolic	map name.  Must	be one of the following:
       GL_PIXEL_MAP_I_TO_I,	GL_PIXEL_MAP_S_TO_S, GL_PIXEL_MAP_I_TO_R,
       GL_PIXEL_MAP_I_TO_G,	GL_PIXEL_MAP_I_TO_B, GL_PIXEL_MAP_I_TO_A,
       GL_PIXEL_MAP_R_TO_R,	GL_PIXEL_MAP_G_TO_G, GL_PIXEL_MAP_B_TO_B, or
       GL_PIXEL_MAP_A_TO_A.

  mapsize  Specifies the size of the map being defined.

  values   Specifies an	array of mapsize values.

Description

  glPixelMap sets up translation tables, or maps, used by glDrawPixels,
  glReadPixels,	glCopyPixels, glTexImage1D, and	glTexImage2D.  Use of these
  maps is described completely in the glPixelTransfer reference	page, and
  partly in the	reference pages	for the	pixel and texture image	commands.
  Only the specification of the	maps is	described in this reference page.

  map is a symbolic map	name, indicating one of	ten maps to set.  mapsize
  specifies the	number of entries in the map, and values is a pointer to an
  array	of mapsize map values.

  The ten maps are as follows:

  GL_PIXEL_MAP_I_TO_I		Maps color indices to color indices.

  GL_PIXEL_MAP_S_TO_S		Maps stencil indices to	stencil	indices.

  GL_PIXEL_MAP_I_TO_R		Maps color indices to red components.

  GL_PIXEL_MAP_I_TO_G		Maps color indices to green components.

  GL_PIXEL_MAP_I_TO_B		Maps color indices to blue components.

  GL_PIXEL_MAP_I_TO_A		Maps color indices to alpha components.

  GL_PIXEL_MAP_R_TO_R		Maps red components to red components.

  GL_PIXEL_MAP_G_TO_G		Maps green components to green components.

  GL_PIXEL_MAP_B_TO_B		Maps blue components to	blue components.

  GL_PIXEL_MAP_A_TO_A		Maps alpha components to alpha components.

  The entries in a map can be specified	as single-precision floating-point
  numbers, unsigned short integers, or unsigned	long integers.	Maps that
  store	color component	values (all but	GL_PIXEL_MAP_I_TO_I and
  GL_PIXEL_MAP_S_TO_S) retain their values in floating-point format, with
  unspecified mantissa and exponent sizes.  Floating-point values specified
  by glPixelMapfv are converted	directly to the	internal floating-point
  format of these maps,	then clamped to	the range [0,1].  Unsigned integer
  values specified by glPixelMapusv and	glPixelMapuiv are converted linearly
  such that the	largest	representable integer maps to 1.0, and zero maps to
  0.0.

  Maps that store indices, GL_PIXEL_MAP_I_TO_I and GL_PIXEL_MAP_S_TO_S,
  retain their values in fixed-point format, with an unspecified number	of
  bits to the right of the binary point.  Floating-point values	specified by
  glPixelMapfv are converted directly to the internal fixed-point format of
  these	maps.  Unsigned	integer	values specified by glPixelMapusv and
  glPixelMapuiv	specify	integer	values,	with all zeros to the right of the
  binary point.

  The table below shows	the initial sizes and values for each of the maps.
  Maps that are	indexed	by either color	or stencil indices must	have mapsize
  = 2n for some	n or results are undefined.  The maximum allowable size	for
  each map depends on the implementation and can be determined by calling
  glGet	with argument GL_MAX_PIXEL_MAP_TABLE.  The single maximum applies to
  all maps, and	it is at least 32.
--------------------------------------------------------------------------------------
|        map         |  lookup index  |  lookup value  | initial size | initial value |
--------------------------------------------------------------------------------------
|GL_PIXEL_MAP_I_TO_I |	color index   |	 color index   |      1	      |	     0.0      |
|GL_PIXEL_MAP_S_TO_S | stencil index  |	stencil	index  |      1	      |	      0	      |
|GL_PIXEL_MAP_I_TO_R |	color index   |	      R	       |      1	      |	     0.0      |
|GL_PIXEL_MAP_I_TO_G |	color index   |	      G	       |      1	      |	     0.0      |
|GL_PIXEL_MAP_I_TO_B |	color index   |	      B	       |      1	      |	     0.0      |
|GL_PIXEL_MAP_I_TO_A |	color index   |	      A	       |      1	      |	     0.0      |
|GL_PIXEL_MAP_R_TO_R |	     R	      |	      R	       |      1	      |	     0.0      |
|GL_PIXEL_MAP_G_TO_G |	     G	      |	      G	       |      1	      |	     0.0      |
|GL_PIXEL_MAP_B_TO_B |	     B	      |	      B	       |      1	      |	     0.0      |
|GL_PIXEL_MAP_A_TO_A |	     A	      |	      A	       |      1	      |	     0.0      |
--------------------------------------------------------------------------------------

Errors

  GL_INVALID_ENUM is generated if map is not an	accepted value.

  GL_INVALID_VALUE is generated	if mapsize is negative or larger than
  GL_MAX_PIXEL_MAP_TABLE.

  GL_INVALID_VALUE is generated	if map is GL_PIXEL_MAP_I_TO_I,
  GL_PIXEL_MAP_S_TO_S, GL_PIXEL_MAP_I_TO_R, GL_PIXEL_MAP_I_TO_G,
  GL_PIXEL_MAP_I_TO_B, or GL_PIXEL_MAP_I_TO_A, and mapsize is not a power of
  two.

  GL_INVALID_OPERATION is generated if glPixelMap is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGetPixelMap
  glGet	with argument GL_PIXEL_MAP_I_TO_I_SIZE
  glGet	with argument GL_PIXEL_MAP_S_TO_S_SIZE
  glGet	with argument GL_PIXEL_MAP_I_TO_R_SIZE
  glGet	with argument GL_PIXEL_MAP_I_TO_G_SIZE
  glGet	with argument GL_PIXEL_MAP_I_TO_B_SIZE
  glGet	with argument GL_PIXEL_MAP_I_TO_A_SIZE
  glGet	with argument GL_PIXEL_MAP_R_TO_R_SIZE
  glGet	with argument GL_PIXEL_MAP_G_TO_G_SIZE
  glGet	with argument GL_PIXEL_MAP_B_TO_B_SIZE
  glGet	with argument GL_PIXEL_MAP_A_TO_A_SIZE
  glGet	with argument GL_MAX_PIXEL_MAP_TABLE

See Also

  glCopyPixels,	glDrawPixels, glPixelStore, glPixelTransfer, glReadPixels,
  glTexImage1D,	glTexImage2D

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glPixelStore.3gl

Name

  glPixelStoref, glPixelStorei - set pixel storage modes

C Specification

  void glPixelStoref( GLenum pname,
              GLfloat param )
  void glPixelStorei( GLenum pname,
              GLint param )

Parameters

  pname	 Specifies the symbolic	name of	the parameter to be set.  Six values
     affect	the packing of pixel data into memory: GL_PACK_SWAP_BYTES,
     GL_PACK_LSB_FIRST, GL_PACK_ROW_LENGTH,	GL_PACK_SKIP_PIXELS,
     GL_PACK_SKIP_ROWS, and	GL_PACK_ALIGNMENT.  Six	more affect the
     unpacking of pixel data from memory: GL_UNPACK_SWAP_BYTES,
     GL_UNPACK_LSB_FIRST, GL_UNPACK_ROW_LENGTH, GL_UNPACK_SKIP_PIXELS,
     GL_UNPACK_SKIP_ROWS, and GL_UNPACK_ALIGNMENT.

  param	 Specifies the value that pname	is set to.

Description

  glPixelStore sets pixel storage modes	that affect the	operation of
  subsequent glDrawPixels and glReadPixels as well as the unpacking of
  polygon stipple patterns (see	glPolygonStipple), bitmaps (see	glBitmap),
  and texture patterns (see glTexImage1D and glTexImage2D).

  pname	is a symbolic constant indicating the parameter	to be set, and param
  is the new value.  Six of the	twelve storage parameters affect how pixel
  data is returned to client memory, and are therefore significant only	for
  glReadPixels commands.  They are as follows:

  GL_PACK_SWAP_BYTES
        If true, byte ordering for multibyte color components, depth
        components,	color indices, or stencil indices is reversed.	That
        is,	if a four-byte component is made up of bytes b , b , b , b ,
                                                              0   1   2   3
        it is stored in memory as b	, b , b	, b  if	GL_PACK_SWAP_BYTES is
                                       3   2   1   0
        true.  GL_PACK_SWAP_BYTES has no effect on the memory order	of
        components within a	pixel, only on the order of bytes within
        components or indices.  For	example, the three components of a
        GL_RGB format pixel	are always stored with red first, green
        second, and	blue third, regardless of the value of
        GL_PACK_SWAP_BYTES.

  GL_PACK_LSB_FIRST
        If true, bits are ordered within a byte from least significant to
        most  significant; otherwise, the first bit	in each	byte is	the
        most significant one.  This	parameter is significant for bitmap
        data only.

  GL_PACK_ROW_LENGTH
        If greater than zero, GL_PACK_ROW_LENGTH defines the number	of
        pixels in a	row.  If the first pixel of a row is placed at
        location p in memory, then the location of the first pixel of the
        next row is	obtained by skipping

                 k = nl               if s >= a

                           snl
                 k = a/s·ceil( --- )  if s < a
                            a

        components or indices, where n is the number of components or
        indices in a pixel,	l is the number	of pixels in a row
        (GL_PACK_ROW_LENGTH	if it is greater than zero, the	width
        argument to	the pixel routine otherwise), a	is the value of
        GL_PACK_ALIGNMENT, and s is	the size, in bytes, of a single
        component (if a<s, then it is as if a=s).  In the case of 1-bit
        values, the	location of the	next row is obtained by	skipping

                          nl
                 k = 8a·ceil( -- )
                          8a

        components or indices.

        The	word component in this description refers to the nonindex
        values red,	green, blue, alpha, and	depth.	Storage	format
        GL_RGB, for	example, has three components per pixel: first red,
        then green,	and finally blue.

  GL_PACK_SKIP_PIXELS and GL_PACK_SKIP_ROWS
        These values are provided as a convenience to the programmer;
        they provide no functionality that cannot be duplicated simply by
        incrementing the pointer passed to glReadPixels.  Setting
        GL_PACK_SKIP_PIXELS	to i is	equivalent to incrementing the
        pointer by in components or	indices, where n is the	number of
        components or indices in each pixel.  Setting GL_PACK_SKIP_ROWS
        to j is equivalent to incrementing the pointer by jk components
        or indices,	where k	is the number of components or indices per
        row, as computed above in the GL_PACK_ROW_LENGTH section.

  GL_PACK_ALIGNMENT
        Specifies the alignment requirements for the start of each pixel
        row	in memory.  The	allowable values are 1 (byte-alignment), 2
        (rows aligned to even-numbered bytes), 4 (word alignment), and 8
        (rows start	on double-word boundaries).

  The other six	of the twelve storage parameters affect	how pixel data is
  read from client memory.  These values are significant for glDrawPixels,
  glTexImage1D,	glTexImage2D, glBitmap,	and glPolygonStipple.  They are	as
  follows:

  GL_UNPACK_SWAP_BYTES
       If true,	byte ordering for multibyte color components, depth
       components, color indices, or stencil indices is	reversed.  That	is,
       if a four-byte component	is made	up of bytes b ,	b , b ,	b , it is
                                                     0   1   2   3
       taken from memory as b ,	b , b ,	b  if GL_UNPACK_SWAP_BYTES is true.
                             3   2   1   0
       GL_UNPACK_SWAP_BYTES has	no effect on the memory	order of components
       within a	pixel, only on the order of bytes within components or
       indices.	 For example, the three	components of a	GL_RGB format pixel
       are always stored with red first, green second, and blue	third,
       regardless of the value of GL_UNPACK_SWAP_BYTES.

  GL_UNPACK_LSB_FIRST
       If true,	bits are ordered within	a byte from least significant to most
       significant; otherwise, the first bit in	each byte is the most
       significant one.	 This is significant for bitmap	data only.

  GL_UNPACK_ROW_LENGTH
       If greater than zero, GL_UNPACK_ROW_LENGTH defines the number of
       pixels in a row.	 If the	first pixel of a row is	placed at location p
       in memory, then the location of the first pixel of the next row is
       obtained	by skipping

                 k = nl               if s >= a

                           snl
                 k = a/s·ceil( --- )  if s < a
                            a

       components or indices, where n is the number of components or indices
       in a pixel, l is	the number of pixels in	a row (GL_UNPACK_ROW_LENGTH
       if it is	greater	than zero, the width argument to the pixel routine
       otherwise), a is	the value of GL_UNPACK_ALIGNMENT, and s	is the size,
       in bytes, of a single component (if a<s,	then it	is as if a=s).	In
       the case	of 1-bit values, the location of the next row is obtained by
       skipping

                          nl
                 k = 8a·ceil( -- )
                          8a

       components or indices.

       The word	component in this description refers to	the nonindex values
       red, green, blue, alpha,	and depth.  Storage format GL_RGB, for
       example,	has three components per pixel:	first red, then	green, and
       finally blue.

  GL_UNPACK_SKIP_PIXELS	and GL_UNPACK_SKIP_ROWS
       These values are	provided as a convenience to the programmer; they
       provide no functionality	that cannot be duplicated simply by
       incrementing the	pointer	passed to glDrawPixels,	glTexImage1D,
       glTexImage2D, glBitmap, or glPolygonStipple.  Setting
       GL_UNPACK_SKIP_PIXELS to	i is equivalent	to incrementing	the pointer
       by in components	or indices, where n is the number of components	or
       indices in each pixel.  Setting GL_UNPACK_SKIP_ROWS to j	is equivalent
       to incrementing the pointer by jk components or indices,	where k	is
       the number of components	or indices per row, as computed	above in the
       GL_UNPACK_ROW_LENGTH section.

  GL_UNPACK_ALIGNMENT
       Specifies the alignment requirements for	the start of each pixel	row
       in memory.  The allowable values	are 1 (byte-alignment),	2 (rows
       aligned to even-numbered	bytes),	4 (word	alignment), and	8 (rows	start
       on double-word boundaries).

  The following	table gives the	type, initial value, and range of valid
  values for each of the storage parameters that can be	set with
  glPixelStore.

       -----------------------------------------------------------------
       |   parameter name     |  type   | initial value |  valid range  |
       -----------------------------------------------------------------
       | GL_PACK_SWAP_BYTES   |	Boolean	|     false	| true or false	|
       |  GL_PACK_LSB_FIRST   |	Boolean	|     false	| true or false	|
       | GL_PACK_ROW_LENGTH   |	integer	|	0	|    [0,oo)	|
       |  GL_PACK_SKIP_ROWS   |	integer	|	0	|    [0,oo)	|
       | GL_PACK_SKIP_PIXELS  |	integer	|	0	|    [0,oo)	|
       |  GL_PACK_ALIGNMENT   |	integer	|	4	| 1, 2,	4, or 8	|
       |----------------------------------------------------------------|
       |GL_UNPACK_SWAP_BYTES  |	Boolean	|     false	| true or false	|
       | GL_UNPACK_LSB_FIRST  |	Boolean	|     false	| true or false	|
       |GL_UNPACK_ROW_LENGTH  |	integer	|	0	|    [0,oo)	|
       | GL_UNPACK_SKIP_ROWS  |	integer	|	0	|    [0,oo)	|
       |GL_UNPACK_SKIP_PIXELS |	integer	|	0	|    [0,oo)	|
       | GL_UNPACK_ALIGNMENT  | integer |       4       | 1, 2, 4, or 8 |
       -----------------------------------------------------------------

  glPixelStoref	can be used to set any pixel store parameter.  If the
  parameter type is Boolean, then if param is 0.0, the parameter is false;
  otherwise it is set to true.	If pname is a integer type parameter, param
  is rounded to	the nearest integer.

  Likewise, glPixelStorei can also be used to set any of the pixel store
  parameters.  Boolean parameters are set to false if param is 0 and true
  otherwise.  param is converted to floating point before being	assigned to
  real-valued parameters.

Notes

  The pixel storage modes in effect when glDrawPixels, glReadPixels,
  glTexImage1D,	glTexImage2D, glBitmap,	or glPolygonStipple is placed in a
  display list control the interpretation of memory data.  The pixel storage
  modes	in effect when a display list is executed are not significant.

Errors

  GL_INVALID_ENUM is generated if pname	is not an accepted value.

  GL_INVALID_VALUE is generated	if a negative row length, pixel	skip, or row
  skip value is	specified, or if alignment is specified	as other than 1, 2,
  4, or	8.

  GL_INVALID_OPERATION is generated if glPixelStore is called between a	call
  to glBegin and the corresponding call	to glEnd.

Associated Gets

  glGet	with argument GL_PACK_SWAP_BYTES
  glGet	with argument GL_PACK_LSB_FIRST
  glGet	with argument GL_PACK_ROW_LENGTH
  glGet	with argument GL_PACK_SKIP_ROWS
  glGet	with argument GL_PACK_SKIP_PIXELS
  glGet	with argument GL_PACK_ALIGNMENT
  glGet	with argument GL_UNPACK_SWAP_BYTES
  glGet	with argument GL_UNPACK_LSB_FIRST
  glGet	with argument GL_UNPACK_ROW_LENGTH
  glGet	with argument GL_UNPACK_SKIP_ROWS
  glGet	with argument GL_UNPACK_SKIP_PIXELS
  glGet	with argument GL_UNPACK_ALIGNMENT

See Also

  glBitmap, glDrawPixels, glPixelMap, glPixelTransfer, glPixelZoom,
  glPolygonStipple, glReadPixels, glTexImage1D,	glTexImage2D

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glPixelTransfer.3gl

Name

  glPixelTransferf, glPixelTransferi - set pixel transfer modes

C Specification

  void glPixelTransferf( GLenum	pname,
             GLfloat param )
  void glPixelTransferi( GLenum	pname,
             GLint param )

Parameters

  pname	 Specifies the symbolic	name of	the pixel transfer parameter to	be
     set.  Must be one of the following: GL_MAP_COLOR, GL_MAP_STENCIL,
     GL_INDEX_SHIFT, GL_INDEX_OFFSET, GL_RED_SCALE,	GL_RED_BIAS,
     GL_GREEN_SCALE, GL_GREEN_BIAS,	GL_BLUE_SCALE, GL_BLUE_BIAS,
     GL_ALPHA_SCALE, GL_ALPHA_BIAS,	GL_DEPTH_SCALE,	or GL_DEPTH_BIAS.

  param	 Specifies the value that pname	is set to.

Description

  glPixelTransfer sets pixel transfer modes that affect	the operation of
  subsequent glDrawPixels, glReadPixels, glCopyPixels, glTexImage1D, and
  glTexImage2D commands.  The algorithms that are specified by pixel transfer
  modes	operate	on pixels after	they are read from the frame buffer
  (glReadPixels	and glCopyPixels) or unpacked from client memory
  (glDrawPixels, glTexImage1D, and glTexImage2D).  Pixel transfer operations
  happen in the	same order, and	in the same manner, regardless of the command
  that resulted	in the pixel operation.	 Pixel storage modes (see
  glPixelStore)	control	the unpacking of pixels	being read from	client
  memory, and the packing of pixels being written back into client memory.

  Pixel	transfer operations handle four	fundamental pixel types: color,	color
  index, depth,	and stencil.  Color pixels are made up of four floating-point
  values with unspecified mantissa and exponent	sizes, scaled such that	0.0
  represents zero intensity and	1.0 represents full intensity.	Color indices
  comprise a single fixed-point	value, with unspecified	precision to the
  right	of the binary point.  Depth pixels comprise a single floating-point
  value, with unspecified mantissa and exponent	sizes, scaled such that	0.0
  represents the minimum depth buffer value, and 1.0 represents	the maximum
  depth	buffer value.  Finally,	stencil	pixels comprise	a single fixed-point
  value, with unspecified precision to the right of the	binary point.

  The pixel transfer operations	performed on the four basic pixel types	are
  as follows:

  Color	      Each of the four color components	is multiplied by a scale
          factor, then added to a bias factor.  That is, the red
          component	is multiplied by GL_RED_SCALE, then added to
          GL_RED_BIAS; the green component is multiplied by
          GL_GREEN_SCALE, then added to GL_GREEN_BIAS; the blue component
          is multiplied by GL_BLUE_SCALE, then added to GL_BLUE_BIAS; and
          the alpha	component is multiplied	by GL_ALPHA_SCALE, then	added
          to GL_ALPHA_BIAS.	 After all four	color components are scaled
          and biased, each is clamped to the range [0,1].  All color
          scale and	bias values are	specified with glPixelTransfer.

          If GL_MAP_COLOR is true, each color component is scaled by the
          size of the corresponding	color-to-color map, then replaced by
          the contents of that map indexed by the scaled component.	 That
          is, the red component is scaled by GL_PIXEL_MAP_R_TO_R_SIZE,
          then replaced by the contents of GL_PIXEL_MAP_R_TO_R indexed by
          itself.  The green component is scaled by
          GL_PIXEL_MAP_G_TO_G_SIZE,	then replaced by the contents of
          GL_PIXEL_MAP_G_TO_G indexed by itself.  The blue component is
          scaled by	GL_PIXEL_MAP_B_TO_B_SIZE, then replaced	by the
          contents of GL_PIXEL_MAP_B_TO_B indexed by itself.  And the
          alpha component is scaled	by GL_PIXEL_MAP_A_TO_A_SIZE, then
          replaced by the contents of GL_PIXEL_MAP_A_TO_A indexed by
          itself.  All components taken from the maps are then clamped to
          the range	[0,1].	GL_MAP_COLOR is	specified with
          glPixelTransfer.	The contents of	the various maps are
          specified	with glPixelMap.

  Color	index Each color index is shifted left by GL_INDEX_SHIFT bits,
          filling with zeros any bits beyond the number of fraction	bits
          carried by the fixed-point index.	 If GL_INDEX_SHIFT is
          negative,	the shift is to	the right, again zero filled.  Then
          GL_INDEX_OFFSET is added to the index.  GL_INDEX_SHIFT and
          GL_INDEX_OFFSET are specified with glPixelTransfer.

          From this	point, operation diverges depending on the required
          format of	the resulting pixels.  If the resulting	pixels are to
          be written to a color index buffer, or if	they are being read
          back to client memory in GL_COLOR_INDEX format, the pixels
          continue to be treated as	indices.  If GL_MAP_COLOR is true,
          each index is masked by 2n - 1, where n is
          GL_PIXEL_MAP_I_TO_I_SIZE,	then replaced by the contents of
          GL_PIXEL_MAP_I_TO_I indexed by the masked	value.	GL_MAP_COLOR
          is specified with	glPixelTransfer.  The contents of the index
          map are specified	with glPixelMap.

          If the resulting pixels are to be	written	to an RGBA color
          buffer, or if they are being read	back to	client memory in a
          format other than	GL_COLOR_INDEX,	the pixels are converted from
          indices to colors	by referencing the four	maps
          GL_PIXEL_MAP_I_TO_R, GL_PIXEL_MAP_I_TO_G,	GL_PIXEL_MAP_I_TO_B,
          and GL_PIXEL_MAP_I_TO_A.	Before being dereferenced, the index
          is masked	by 2n -	1, where n is GL_PIXEL_MAP_I_TO_R_SIZE for
          the red map, GL_PIXEL_MAP_I_TO_G_SIZE for	the green map,
          GL_PIXEL_MAP_I_TO_B_SIZE for the blue map, and
          GL_PIXEL_MAP_I_TO_A_SIZE for the alpha map.  All components
          taken from the maps are then clamped to the range	[0,1].	The
          contents of the four maps	are specified with glPixelMap.

  Depth	      Each depth value is multiplied by	GL_DEPTH_SCALE,	added to
          GL_DEPTH_BIAS, then clamped to the range [0,1].

  Stencil     Each index is shifted GL_INDEX_SHIFT bits	just as	a color	index
          is, then added to	GL_INDEX_OFFSET.  If GL_MAP_STENCIL is true,
          each index is masked by 2n - 1, where n is
          GL_PIXEL_MAP_S_TO_S_SIZE,	then replaced by the contents of
          GL_PIXEL_MAP_S_TO_S indexed by the masked	value.

  The following	table gives the	type, initial value, and range of valid
  values for each of the pixel transfer	parameters that	are set	with
  glPixelTransfer.

       ---------------------------------------------------------
       |    pname       |  type   | initial value | valid range |
       ---------------------------------------------------------
       | GL_MAP_COLOR   | Boolean |	    false     |	true/false  |
       |GL_MAP_STENCIL  | Boolean |	    false     |	true/false  |
       |GL_INDEX_SHIFT  | integer |	      0	      |	 (-oo,oo)   |
       |GL_INDEX_OFFSET | integer |	      0	      |	 (-oo,oo)   |
       | GL_RED_SCALE   |  float  |	     1.0      |	 (-oo,oo)   |
       |GL_GREEN_SCALE  |  float  |	     1.0      |	 (-oo,oo)   |
       | GL_BLUE_SCALE  |  float  |	     1.0      |	 (-oo,oo)   |
       |GL_ALPHA_SCALE  |  float  |	     1.0      |	 (-oo,oo)   |
       |GL_DEPTH_SCALE  |  float  |	     1.0      |	 (-oo,oo)   |
       |  GL_RED_BIAS   |  float  |	     0.0      |	 (-oo,oo)   |
       | GL_GREEN_BIAS  |  float  |	     0.0      |	 (-oo,oo)   |
       | GL_BLUE_BIAS   |  float  |	     0.0      |	 (-oo,oo)   |
       | GL_ALPHA_BIAS  |  float  |	     0.0      |	 (-oo,oo)   |
       | GL_DEPTH_BIAS  |  float  |	     0.0      |	 (-oo,oo)   |
       ---------------------------------------------------------

  glPixelTransferf can be used to set any pixel	transfer parameter.  If	the
  parameter type is Boolean, 0.0 implies false and any other value implies
  true.	 If pname is an	integer	parameter, param is rounded to the nearest
  integer.

  Likewise, glPixelTransferi can also be used to set any of the	pixel
  transfer parameters.	Boolean	parameters are set to false if param is	0 and
  true otherwise.  param is converted to floating point	before being assigned
  to real-valued parameters.

Notes

  If a glDrawPixels, glReadPixels, glCopyPixels, glTexImage1D, or
  glTexImage2D command is placed in a display list (see	glNewList and
  glCallList), the pixel transfer mode settings	in effect when the display
  list is executed are the ones	that are used.	They may be different from
  the settings when the	command	was compiled into the display list.

Errors

  GL_INVALID_ENUM is generated if pname	is not an accepted value.

  GL_INVALID_OPERATION is generated if glPixelTransfer is called between a
  call to glBegin and the corresponding	call to	glEnd.

Associated Gets

  glGet	with argument GL_MAP_COLOR
  glGet	with argument GL_MAP_STENCIL
  glGet	with argument GL_INDEX_SHIFT
  glGet	with argument GL_INDEX_OFFSET
  glGet	with argument GL_RED_SCALE
  glGet	with argument GL_RED_BIAS
  glGet	with argument GL_GREEN_SCALE
  glGet	with argument GL_GREEN_BIAS
  glGet	with argument GL_BLUE_SCALE
  glGet	with argument GL_BLUE_BIAS
  glGet	with argument GL_ALPHA_SCALE
  glGet	with argument GL_ALPHA_BIAS
  glGet	with argument GL_DEPTH_SCALE
  glGet	with argument GL_DEPTH_BIAS

See Also

  glCallList, glCopyPixels, glDrawPixels, glNewList, glPixelMap,
  glPixelStore,	glPixelZoom, glReadPixels, glTexImage1D, glTexImage2D

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glPixelZoom.3gl

Name

  glPixelZoom -	specify	the pixel zoom factors

C Specification

  void glPixelZoom( GLfloat xfactor,
            GLfloat yfactor )

Parameters

  xfactor, yfactor Specify the x and y zoom factors for	pixel write
           operations.

Description

  glPixelZoom specifies	values for the x and y zoom factors.  During the
  execution of glDrawPixels or glCopyPixels, if	(xr, yr) is the	current
  raster position, and a given element is in the mth row and nth column	of
  the pixel rectangle, then pixels whose centers are in	the rectangle with
  corners at

               (xr + n·xfactor,	yr + m·yfactor)

           (xr + (n+1)·xfactor,	yr + (m+1)·yfactor)

  are candidates for replacement.  Any pixel whose center lies on the bottom
  or left edge of this rectangular region is also modified.

  Pixel	zoom factors are not limited to	positive values.  Negative zoom
  factors reflect the resulting	image about the	current	raster position.

Errors

  GL_INVALID_OPERATION is generated if glPixelZoom is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_ZOOM_X
  glGet	with argument GL_ZOOM_Y

See Also

  glCopyPixels,	glDrawPixels

Introduction | Alphabetic | Specification

Last Edited: Tue May 23, 1995

AFV


glPointSize.3gl

Name

  glPointSize -	specify	the diameter of	rasterized points

C Specification

  void glPointSize( GLfloat size )

Parameters

  size	Specifies the diameter of rasterized points.  The default is 1.0.

Description

  glPointSize specifies	the rasterized diameter	of both	aliased	and
  antialiased points.  Using a point size other	than 1.0 has different
  effects, depending on	whether	point antialiasing is enabled.	Point
  antialiasing is controlled by	calling	glEnable and glDisable with argument
  GL_POINT_SMOOTH.

  If point antialiasing	is disabled, the actual	size is	determined by
  rounding the supplied	size to	the nearest integer.  (If the rounding
  results in the value 0, it is	as if the point	size were 1.) If the rounded
  size is odd, then the	center point (x, y) of the pixel fragment that
  represents the point is computed as

            ( floor(x )+.5, floor(y )+.5 )
                 w	           w

  where	w subscripts indicate window coordinates.  All pixels that lie within
  the square grid of the rounded size centered at (x, y) make up the
  fragment.  If	the size is even, the center point is

            ( floor(x +.5), floor(y +.5) )
                 w	           w

  and the rasterized fragment's	centers	are the	half-integer window
  coordinates within the square	of the rounded size centered at	(x, y).	 All
  pixel	fragments produced in rasterizing a nonantialiased point are assigned
  the same associated data, that of the	vertex corresponding to	the point.

  If antialiasing is enabled, then point rasterization produces	a fragment
  for each pixel square	that intersects	the region lying within	the circle
  having diameter equal	to the current point size and centered at the point's
  (x , y ).  The coverage value	for each fragment is the window	coordinate
    w   w
  area of the intersection of the circular region with the corresponding
  pixel	square.	 This value is saved and used in the final rasterization
  step.	The data associated with each fragment is the data associated with
  the point being rasterized.

  Not all sizes	are supported when point antialiasing is enabled. If an
  unsupported size is requested, the nearest supported size is used.  Only
  size 1.0 is guaranteed to be supported; others depend	on the
  implementation.  The range of	supported sizes	and the	size difference
  between supported sizes within the range can be queried by  calling glGet
  with arguments GL_POINT_SIZE_RANGE and GL_POINT_SIZE_GRANULARITY.

Notes

  The point size specified by glPointSize is always returned when
  GL_POINT_SIZE	is queried.  Clamping and rounding for aliased and
  antialiased points have no effect on the specified value.

  Non-antialiased point	size may be clamped to an implementation-dependent
  maximum.  Although this maximum cannot be queried, it	must be	no less	than
  the maximum value for	antialiased points, rounded to the nearest integer
  value.

Errors

  GL_INVALID_VALUE is generated	if size	is less	than or	equal to zero.

  GL_INVALID_OPERATION is generated if glPointSize is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_POINT_SIZE
  glGet	with argument GL_POINT_SIZE_RANGE
  glGet	with argument GL_POINT_SIZE_GRANULARITY
  glIsEnabled with argument GL_POINT_SMOOTH

See Also

  glEnable, glPointSmooth

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


glPolygonMode.3gl

Name

  glPolygonMode	- select a polygon rasterization mode

C Specification

  void glPolygonMode( GLenum face,
              GLenum mode )

Parameters

  face	Specifies the polygons that mode applies to.  Must be GL_FRONT for
    front-facing polygons, GL_BACK for back-facing polygons, or
    GL_FRONT_AND_BACK for front- and back-facing polygons.

  mode	Specifies the way polygons will	be rasterized.	Accepted values	are
    GL_POINT, GL_LINE, and GL_FILL.	 The default is	GL_FILL	for both
    front- and back-facing polygons.

Description

  glPolygonMode	controls the interpretation of polygons	for rasterization.
  face describes which polygons	mode applies to: front-facing polygons
  (GL_FRONT), back-facing polygons (GL_BACK), or both (GL_FRONT_AND_BACK).
  The polygon mode affects only	the final rasterization	of polygons.  In
  particular, a	polygon's vertices are lit and the polygon is clipped and
  possibly culled before these modes are applied.

  Three	modes are defined and can be specified in mode:

  GL_POINT	Polygon	vertices that are marked as the	start of a boundary
        edge are drawn as points.  Point attributes such as
        GL_POINT_SIZE and GL_POINT_SMOOTH control the rasterization
        of the points.	Polygon	rasterization attributes other than
        GL_POLYGON_MODE	have no	effect.

  GL_LINE	Boundary edges of the polygon are drawn	as line	segments.
        They are treated as connected line segments for	line
        stippling; the line stipple counter and	pattern	are not	reset
        between	segments (see glLineStipple).  Line attributes such
        as GL_LINE_WIDTH and GL_LINE_SMOOTH control the	rasterization
        of the lines.  Polygon rasterization attributes	other than
        GL_POLYGON_MODE	have no	effect.

  GL_FILL	The interior of	the polygon is filled.	Polygon	attributes
        such as	GL_POLYGON_STIPPLE and GL_POLYGON_SMOOTH control the
        rasterization of the polygon.

Examples

  To draw a surface with filled	back-facing polygons and outlined front-
  facing polygons, call	glPolygonMode(GL_FRONT,	GL_LINE);

Notes

  Vertices are marked as boundary or nonboundary with an edge flag.  Edge
  flags	are generated internally by the	GL when	it decomposes polygons,	and
  they can be set explicitly using glEdgeFlag.

Errors

  GL_INVALID_ENUM is generated if either face or mode is not an	accepted
  value.

  GL_INVALID_OPERATION is generated if glPolygonMode is	executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_POLYGON_MODE

See Also

  glBegin, glEdgeFlag, glLineStipple, glLineWidth, glPointSize,
  glPolygonStipple

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glPolygonStipple.3gl

Name

  glPolygonStipple - set the polygon stippling pattern

C Specification

  void glPolygonStipple( const GLubyte *mask )

Parameters

  mask	Specifies a pointer to a 32x32 stipple pattern that will be unpacked
    from memory in the same	way that glDrawPixels unpacks pixels.

Description

  Polygon stippling, like line stippling (see glLineStipple), masks out
  certain fragments produced by	rasterization, creating	a pattern.  Stippling
  is independent of polygon antialiasing.

  mask is a pointer to a 32x32 stipple pattern that is stored in memory	just
  like the pixel data supplied to a glDrawPixels with height and width both
  equal	to 32, a pixel format of GL_COLOR_INDEX, and data type of GL_BITMAP.
  That is, the stipple pattern is represented as a 32x32 array of 1-bit	color
  indices packed in unsigned bytes.  glPixelStore parameters like
  GL_UNPACK_SWAP_BYTES and GL_UNPACK_LSB_FIRST affect the assembling of	the
  bits into a stipple pattern.	Pixel transfer operations (shift, offset,
  pixel	map) are not applied to	the stipple image, however.

  Polygon stippling is enabled and disabled with glEnable and glDisable,
  using	argument GL_POLYGON_STIPPLE.  If enabled, a rasterized polygon
  fragment with	window coordinates x  and y  is	sent to	the next stage of the
                                    w      w
  GL if	and only if the	(x  mod 32)th bit is the (y  mod 32)th row of the
                          w                        w
  stipple pattern is one.  When	polygon	stippling is disabled, it is as	if
  the stipple pattern were all ones.

Errors

  GL_INVALID_OPERATION is generated if glPolygonStipple	is executed between
  the execution	of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGetPolygonStipple
  glIsEnabled with argument GL_POLYGON_STIPPLE

See Also

  glDrawPixels,	glLineStipple, glPixelStore, glPixelTransfer

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


glPushAttrib.3gl

Name

  glPushAttrib,	glPopAttrib - push and pop the attribute stack

C Specification

  void glPushAttrib( GLbitfield	mask )

Parameters

  mask	Specifies a mask that indicates	which attributes to save.  Values for
    mask are listed	in the table below.

C Specification

  void glPopAttrib( void )

Description

  glPushAttrib takes one argument, a mask that indicates which groups of
  state	variables to save on the attribute stack.  Symbolic constants are
  used to set bits in the mask.	 mask is typically constructed by ORing
  several of these constants together.	The special mask GL_ALL_ATTRIB_BITS
  can be used to save all stackable states.

  The symbolic mask constants and their	associated GL state are	as follows
  (the second column lists which attributes are	saved):

  GL_ACCUM_BUFFER_BIT	   Accumulation	buffer clear value

  GL_COLOR_BUFFER_BIT	   GL_ALPHA_TEST enable	bit
               Alpha test function and reference value
               GL_BLEND enable bit
               Blending source and destination functions
               GL_DITHER enable bit
               GL_DRAW_BUFFER setting
               GL_LOGIC_OP enable bit
               Logic op function
               Color mode and index	mode clear values
               Color mode and index	mode writemasks

  GL_CURRENT_BIT	   Current RGBA	color
               Current color index
               Current normal vector
               Current texture coordinates
               Current raster position
               GL_CURRENT_RASTER_POSITION_VALID flag
               RGBA	color associated with current raster position
               Color index associated with current raster position
               Texture coordinates associated with current raster position
               GL_EDGE_FLAG	flag

  GL_DEPTH_BUFFER_BIT	   GL_DEPTH_TEST enable	bit
               Depth buffer	test function
               Depth buffer	clear value
               GL_DEPTH_WRITEMASK enable bit

  GL_ENABLE_BIT		   GL_ALPHA_TEST flag
               GL_AUTO_NORMAL flag
               GL_BLEND flag
               Enable bits for the user-definable clipping planes
               GL_COLOR_MATERIAL
               GL_CULL_FACE	flag
               GL_DEPTH_TEST flag
               GL_DITHER flag
               GL_FOG flag
               GL_LIGHTi where 0 <=	i<GL_MAX_LIGHTS
               GL_LIGHTING flag
               GL_LINE_SMOOTH flag
               GL_LINE_STIPPLE flag
               GL_LOGIC_OP flag
               GL_MAP1_x where x is	a map type
               GL_MAP2_x where x is	a map type
               GL_NORMALIZE	flag
               GL_POINT_SMOOTH flag
               GL_POLYGON_SMOOTH flag
               GL_POLYGON_STIPPLE flag
               GL_SCISSOR_TEST flag
               GL_STENCIL_TEST flag
               GL_TEXTURE_1D flag
               GL_TEXTURE_2D flag
               Flags GL_TEXTURE_GEN_x where	x is S,	T, R, or Q

  GL_EVAL_BIT		   GL_MAP1_x enable bits, where	x is a map type
               GL_MAP2_x enable bits, where	x is a map type
               1-D grid endpoints and divisions
               2-D grid endpoints and divisions
               GL_AUTO_NORMAL enable bit

  GL_FOG_BIT		   GL_FOG enable flag
               Fog color
               Fog density
               Linear fog start
               Linear fog end
               Fog index
               GL_FOG_MODE value

  GL_HINT_BIT		   GL_PERSPECTIVE_CORRECTION_HINT setting
               GL_POINT_SMOOTH_HINT	setting
               GL_LINE_SMOOTH_HINT setting
               GL_POLYGON_SMOOTH_HINT setting
               GL_FOG_HINT setting

  GL_LIGHTING_BIT	   GL_COLOR_MATERIAL enable bit
               GL_COLOR_MATERIAL_FACE value
               Color material parameters that are tracking the current color
               Ambient scene color
               GL_LIGHT_MODEL_LOCAL_VIEWER value
               GL_LIGHT_MODEL_TWO_SIDE setting
               GL_LIGHTING enable bit
               Enable bit for each light
               Ambient, diffuse, and specular intensity for	each light
               Direction, position,	exponent, and cutoff angle for each light
               Constant, linear, and quadratic attenuation factors for each	light
               Ambient, diffuse, specular, and emissive color for each material
               Ambient, diffuse, and specular color	indices	for each material
               Specular exponent for each material
               GL_SHADE_MODEL setting

  GL_LINE_BIT		   GL_LINE_SMOOTH flag
               GL_LINE_STIPPLE enable bit
               Line	stipple	pattern	and repeat counter
               Line	width

  GL_LIST_BIT		   GL_LIST_BASE	setting

  GL_PIXEL_MODE_BIT	   GL_RED_BIAS and GL_RED_SCALE	settings
               GL_GREEN_BIAS and GL_GREEN_SCALE values
               GL_BLUE_BIAS	and GL_BLUE_SCALE
               GL_ALPHA_BIAS and GL_ALPHA_SCALE
               GL_DEPTH_BIAS and GL_DEPTH_SCALE
               GL_INDEX_OFFSET and GL_INDEX_SHIFT values
               GL_MAP_COLOR	and GL_MAP_STENCIL flags
               GL_ZOOM_X and GL_ZOOM_Y factors
               GL_READ_BUFFER setting

  GL_POINT_BIT		   GL_POINT_SMOOTH flag
               Point size

  GL_POLYGON_BIT	   GL_CULL_FACE	enable bit
               GL_CULL_FACE_MODE value
               GL_FRONT_FACE indicator
               GL_POLYGON_MODE setting
               GL_POLYGON_SMOOTH flag
               GL_POLYGON_STIPPLE enable bit

  GL_POLYGON_STIPPLE_BIT   Polygon stipple image

  GL_SCISSOR_BIT	   GL_SCISSOR_TEST flag
               Scissor box

  GL_STENCIL_BUFFER_BIT	   GL_STENCIL_TEST enable bit
               Stencil function and	reference value
               Stencil value mask
               Stencil fail, pass, and depth buffer	pass actions
               Stencil buffer clear	value
               Stencil buffer writemask

  GL_TEXTURE_BIT	   Enable bits for the four texture coordinates
               Border color	for each texture image
               Minification	function for each texture image
               Magnification function for each texture image
               Texture coordinates and wrap	mode for each texture image
               Color and mode for each texture environment
               Enable bits GL_TEXTURE_GEN_x, x is S, T, R, and Q
               GL_TEXTURE_GEN_MODE setting for S, T, R, and	Q
               glTexGen plane equations for	S, T, R, and Q

  GL_TRANSFORM_BIT	   Coefficients	of the six clipping planes
               Enable bits for the user-definable clipping planes
               GL_MATRIX_MODE value
               GL_NORMALIZE	flag

  GL_VIEWPORT_BIT	   Depth range (near and far)
               Viewport origin and extent

  glPopAttrib restores the values of the state variables saved with the	last
  glPushAttrib command.	 Those not saved are left unchanged.

  It is	an error to push attributes onto a full	stack, or to pop attributes
  off an empty stack.  In either case, the error flag is set and no other
  change is made to GL state.

  Initially, the attribute stack is empty.

Notes

  Not all values for GL	state can be saved on the attribute stack.  For
  example, pixel pack and unpack state,	render mode state, and select and
  feedback state cannot	be saved.

  The depth of the attribute stack depends on the implementation, but it must
  be at	least 16.

Errors

  GL_STACK_OVERFLOW is generated if glPushAttrib is called while the
  attribute stack is full.

  GL_STACK_UNDERFLOW is	generated if glPopAttrib is called while the
  attribute stack is empty.

  GL_INVALID_OPERATION is generated if glPushAttrib or glPopAttrib is
  executed between the execution of glBegin and	the corresponding execution
  of glEnd.

Associated Gets

  glGet	with argument GL_ATTRIB_STACK_DEPTH.
  glGet	with argument GL_MAX_ATTRIB_STACK_DEPTH.

See Also

  glGet, glGetClipPlane, glGetError, glGetLight, glGetMap, glGetMaterial,
  glGetPixelMap, glGetPolygonStipple, glGetString, glGetTexEnv,	glGetTexGen,
  glGetTexImage, glGetTexLevelParameter, glGetTexParameter, glIsEnabled

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glPushMatrix.3gl

Name

  glPushMatrix,	glPopMatrix - push and pop the current matrix stack

C Specification

  void glPushMatrix( void )

C Specification

  void glPopMatrix( void )

Description

  There	is a stack of matrices for each	of the matrix modes.  In GL_MODELVIEW
  mode,	the stack depth	is at least 32.	 In the	other two modes,
  GL_PROJECTION	and GL_TEXTURE,	the depth is at	least 2.  The current matrix
  in any mode is the matrix on the top of the stack for	that mode.

  glPushMatrix pushes the current matrix stack down by one, duplicating	the
  current matrix.  That	is, after a glPushMatrix call, the matrix on the top
  of the stack is identical to the one below it.

  glPopMatrix pops the current matrix stack, replacing the current matrix
  with the one below it	on the stack.

  Initially, each of the stacks	contains one matrix, an	identity matrix.

  It is	an error to push a full	matrix stack, or to pop	a matrix stack that
  contains only	a single matrix.  In either case, the error flag is set	and
  no other change is made to GL	state.

Errors

  GL_STACK_OVERFLOW is generated if glPushMatrix is called while the current
  matrix stack is full.

  GL_STACK_UNDERFLOW is	generated if glPopMatrix is called while the current
  matrix stack contains	only a single matrix.

  GL_INVALID_OPERATION is generated if glPushMatrix or glPopMatrix is
  executed between the execution of glBegin and	the corresponding execution
  of glEnd.

Associated Gets

  glGet	with argument GL_MATRIX_MODE
  glGet	with argument GL_MODELVIEW_MATRIX
  glGet	with argument GL_PROJECTION_MATRIX
  glGet	with argument GL_TEXTURE_MATRIX
  glGet	with argument GL_MODELVIEW_STACK_DEPTH
  glGet	with argument GL_PROJECTION_STACK_DEPTH
  glGet	with argument GL_TEXTURE_STACK_DEPTH
  glGet	with argument GL_MAX_MODELVIEW_STACK_DEPTH
  glGet	with argument GL_MAX_PROJECTION_STACK_DEPTH
  glGet	with argument GL_MAX_TEXTURE_STACK_DEPTH

See Also

  glFrustum, glLoadIdentity, glLoadMatrix, glMatrixMode, glMultMatrix,
  glOrtho, glRotate, glScale, glTranslate, glViewport

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glPushName.3gl

Name

  glPushName, glPopName	- push and pop the name	stack

C Specification

  void glPushName( GLuint name )

Parameters

  name	Specifies a name that will be pushed onto the name stack.

C Specification

  void glPopName( void )

Description

  The name stack is used during	selection mode to allow	sets of	rendering
  commands to be uniquely identified.  It consists of an ordered set of
  unsigned integers.  glPushName causes	name to	be pushed onto the name
  stack, which is initially empty.  glPopName pops one name off	the top	of
  the stack.

  It is	an error to push a name	onto a full stack, or to pop a name off	an
  empty	stack.	It is also an error to manipulate the name stack between the
  execution of glBegin and the corresponding execution of glEnd.  In any of
  these	cases, the error flag is set and no other change is made to GL state.

  The name stack is always empty while the render mode is not GL_SELECT.
  Calls	to glPushName or glPopName while the render mode is not	GL_SELECT are
  ignored.

Errors

  GL_STACK_OVERFLOW is generated if glPushName is called while the name	stack
  is full.

  GL_STACK_UNDERFLOW is	generated if glPopName is called while the name	stack
  is empty.

  GL_INVALID_OPERATION is generated if glPushName or glPopName is executed
  between a call to glBegin and	the corresponding call to glEnd.

Associated Gets

  glGet	with argument GL_NAME_STACK_DEPTH
  glGet	with argument GL_MAX_NAME_STACK_DEPTH

See Also

  glInitNames, glLoadName, glRenderMode, glSelectBuffer

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glRasterPos.3gl

Name

  glRasterPos2d, glRasterPos2f,	glRasterPos2i, glRasterPos2s, glRasterPos3d,
  glRasterPos3f, glRasterPos3i,	glRasterPos3s, glRasterPos4d, glRasterPos4f,
  glRasterPos4i, glRasterPos4s,	glRasterPos2dv,	glRasterPos2fv,
  glRasterPos2iv, glRasterPos2sv, glRasterPos3dv, glRasterPos3fv,
  glRasterPos3iv, glRasterPos3sv, glRasterPos4dv, glRasterPos4fv,
  glRasterPos4iv, glRasterPos4sv - specify the raster position for pixel
  operations

C Specification

  void glRasterPos2d( GLdouble x,
              GLdouble y )
  void glRasterPos2f( GLfloat x,
              GLfloat y	)
  void glRasterPos2i( GLint x,
              GLint y )
  void glRasterPos2s( GLshort x,
              GLshort y	)
  void glRasterPos3d( GLdouble x,
              GLdouble y,
              GLdouble z )
  void glRasterPos3f( GLfloat x,
              GLfloat y,
              GLfloat z	)
  void glRasterPos3i( GLint x,
              GLint y,
              GLint z )
  void glRasterPos3s( GLshort x,
              GLshort y,
              GLshort z	)
  void glRasterPos4d( GLdouble x,
              GLdouble y,
              GLdouble z,
              GLdouble w )
  void glRasterPos4f( GLfloat x,
              GLfloat y,
              GLfloat z,
              GLfloat w	)
  void glRasterPos4i( GLint x,
              GLint y,
              GLint z,
              GLint w )
  void glRasterPos4s( GLshort x,
              GLshort y,
              GLshort z,
              GLshort w	)

Parameters

  x, y,	z, w
       Specify the x, y, z,	and w object coordinates (if present) for the
       raster position.

C Specification

  void glRasterPos2dv( const GLdouble *v )
  void glRasterPos2fv( const GLfloat *v	)
  void glRasterPos2iv( const GLint *v )
  void glRasterPos2sv( const GLshort *v	)
  void glRasterPos3dv( const GLdouble *v )
  void glRasterPos3fv( const GLfloat *v	)
  void glRasterPos3iv( const GLint *v )
  void glRasterPos3sv( const GLshort *v	)
  void glRasterPos4dv( const GLdouble *v )
  void glRasterPos4fv( const GLfloat *v	)
  void glRasterPos4iv( const GLint *v )
  void glRasterPos4sv( const GLshort *v	)

  v    Specifies a pointer to an array of two, three, or four elements,
       specifying x, y,	z, and w coordinates, respectively.

Description

  The GL maintains a 3-D position in window coordinates.  This position,
  called the raster position, is maintained with subpixel accuracy.  It	is
  used to position pixel and bitmap write operations.  See glBitmap,
  glDrawPixels,	and glCopyPixels.

  The current raster position consists of three	window coordinates (x, y, z),
  a clip coordinate w value, an	eye coordinate distance, a valid bit, and
  associated color data	and texture coordinates.  The w	coordinate is a	clip
  coordinate, because w	is not projected to window coordinates.	 glRasterPos4
  specifies object coordinates x, y, z,	and w explicitly.  glRasterPos3
  specifies object coordinate x, y, and	z explicitly, while w is implicitly
  set to one.  glRasterPos2 uses the argument values for x and y while
  implicitly setting z and w to	zero and one.

  The object coordinates presented by glRasterPos are treated just like	those
  of a glVertex	command: They are transformed by the current modelview and
  projection matrices and passed to the	clipping stage.	 If the	vertex is not
  culled, then it is projected and scaled to window coordinates, which become
  the new current raster position, and the GL_CURRENT_RASTER_POSITION_VALID
  flag is set.	If the vertex is culled, then the valid	bit is cleared and
  the current raster position and associated color and texture coordinates
  are undefined.

  The current raster position also includes some associated color data and
  texture coordinates.	If lighting is enabled,	then GL_CURRENT_RASTER_COLOR,
  in RGBA mode,	or the GL_CURRENT_RASTER_INDEX,	in color index mode, is	set
  to the color produced	by the lighting	calculation (see glLight,
  glLightModel,	and glShadeModel).  If lighting	is disabled, current color
  (in RGBA mode, state variable	GL_CURRENT_COLOR) or color index (in color
  index	mode, state variable GL_CURRENT_INDEX) is used to update the current
  raster color.

  Likewise, GL_CURRENT_RASTER_TEXTURE_COORDS is	updated	as a function of
  GL_CURRENT_TEXTURE_COORDS, based on the texture matrix and the texture
  generation functions (see glTexGen).	Finally, the distance from the origin
  of the eye coordinate	system to the vertex as	transformed by only the
  modelview matrix replaces GL_CURRENT_RASTER_DISTANCE.

  Initially, the current raster	position is (0,0,0,1), the current raster
  distance is 0, the valid bit is set, the associated RGBA color is
  (1,1,1,1), the associated color index	is 1, and the associated texture
  coordinates are (0, 0, 0, 1).	 In RGBA mode, GL_CURRENT_RASTER_INDEX is
  always 1; in color index mode, the current raster RGBA color always
  maintains its	initial	value.

Notes

  The raster position is modified both by glRasterPos and by glBitmap.

  When the raster position coordinates are invalid, drawing commands that are
  based	on the raster position are ignored (that is, they do not result	in
  changes to GL	state).

Errors

  GL_INVALID_OPERATION is generated if glRasterPos is called between a call
  to glBegin and the corresponding call	to glEnd.

Associated Gets

  glGet	with argument GL_CURRENT_RASTER_POSITION
  glGet	with argument GL_CURRENT_RASTER_POSITION_VALID
  glGet	with argument GL_CURRENT_RASTER_DISTANCE
  glGet	with argument GL_CURRENT_RASTER_COLOR
  glGet	with argument GL_CURRENT_RASTER_INDEX
  glGet	with argument GL_CURRENT_RASTER_TEXTURE_COORDS

See Also

  glBitmap, glCopyPixels, glDrawPixels,	glLight, glLightModel, glShadeModel,
  glTexCoord, glTexGen,	glVertex

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glReadBuffer.3gl

Name

  glReadBuffer - select	a color	buffer source for pixels

C Specification

  void glReadBuffer( GLenum mode )

Parameters

  mode	Specifies a color buffer.  Accepted values are GL_FRONT_LEFT,
    GL_FRONT_RIGHT,	GL_BACK_LEFT, GL_BACK_RIGHT, GL_FRONT, GL_BACK,
    GL_LEFT, GL_RIGHT, and GL_AUXi,	where i	is between 0 and
    GL_AUX_BUFFERS -1.

Description

  glReadBuffer specifies a color buffer	as the source for subsequent
  glReadPixels and glCopyPixels	commands.  mode	accepts	one of twelve or more
  predefined values.  (GL_AUX0 through GL_AUX3 are always defined.) In a
  fully	configured system, GL_FRONT, GL_LEFT, and GL_FRONT_LEFT	all name the
  front	left buffer, GL_FRONT_RIGHT and	GL_RIGHT name the front	right buffer,
  and GL_BACK_LEFT and GL_BACK name the	back left buffer.

  Nonstereo double-buffered configurations have	only a front left and a	back
  left buffer.	Single-buffered	configurations have a front left and a front
  right	buffer if stereo, and only a front left	buffer if nonstereo.  It is
  an error to specify a	nonexistent buffer to glReadBuffer.

  By default, mode is GL_FRONT in single-buffered configurations, and GL_BACK
  in double-buffered configurations.

Errors

  GL_INVALID_ENUM is generated if mode is not one of the twelve	(or more)
  accepted values.

  GL_INVALID_OPERATION is generated if mode specifies a	buffer that does not
  exist.

  GL_INVALID_OPERATION is generated if glReadBuffer is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_READ_BUFFER

See Also

  glCopyPixels,	glDrawBuffer, glReadPixels

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glReadPixels.3gl

Name

  glReadPixels - read a	block of pixels	from the frame buffer

C Specification

  void glReadPixels( GLint x,
             GLint y,
             GLsizei width,
             GLsizei height,
             GLenum format,
             GLenum type,
             GLvoid *pixels )

Parameters

  x, y Specify the window coordinates of the first pixel that is read from
       the frame buffer.  This location	is the lower left corner of a
       rectangular block of pixels.

  width, height
       Specify the dimensions of the pixel rectangle.  width and height	of
       one correspond to a single pixel.

  format
       Specifies the format of the pixel data.	The following symbolic values
       are accepted: GL_COLOR_INDEX, GL_STENCIL_INDEX, GL_DEPTH_COMPONENT,
       GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA, GL_LUMINANCE,
       and GL_LUMINANCE_ALPHA.

  type Specifies the data type of the pixel data.  Must	be one of
       GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT,	GL_SHORT,
       GL_UNSIGNED_INT,	GL_INT,	or GL_FLOAT.

  pixels
       Returns the pixel data.

Description

  glReadPixels returns pixel data from the frame buffer, starting with the
  pixel	whose lower left corner	is at location (x, y), into client memory
  starting at location pixels.	Several	parameters control the processing of
  the pixel data before	it is placed into client memory.  These	parameters
  are set with three commands: glPixelStore, glPixelTransfer, and glPixelMap.
  This reference page describes	the effects on glReadPixels of most, but not
  all of the parameters	specified by these three commands.

  glReadPixels returns values from each	pixel with lower left-hand corner at
  (x + i, y + j) for 0<=i<width and 0<=j<height. This pixel is said to be the
  ith pixel in the jth row. Pixels are returned	in row order from the lowest
  to the highest row, left to right in each row.

  format specifies the format for the returned pixel values.  Accepted values
  for format are as follows:

  GL_COLOR_INDEX
        Color indices are read from	the color buffer selected by
        glReadBuffer.  Each	index is converted to fixed point, shifted
        left or right depending on the value and sign of GL_INDEX_SHIFT,
        and	added to GL_INDEX_OFFSET. If GL_MAP_COLOR is GL_TRUE, indices
        are	replaced by their mappings in the table	GL_PIXEL_MAP_I_TO_I.

  GL_STENCIL_INDEX
        Stencil values are read from the stencil buffer.  Each index is
        converted to fixed point, shifted left or right depending on the
        value and sign of GL_INDEX_SHIFT, and added	to GL_INDEX_OFFSET.
        If GL_MAP_STENCIL is GL_TRUE, indices are replaced by their
        mappings in	the table GL_PIXEL_MAP_S_TO_S.

  GL_DEPTH_COMPONENT
        Depth values are read from the depth buffer.  Each component is
        converted to floating point	such that the minimum depth value
        maps to 0.0	and the	maximum	value maps to 1.0.  Each component is
        then multiplied by GL_DEPTH_SCALE, added to	GL_DEPTH_BIAS, and
        finally clamped to the range [0,1].

  GL_RED

  GL_GREEN

  GL_BLUE

  GL_ALPHA

  GL_RGB

  GL_RGBA

  GL_LUMINANCE

  GL_LUMINANCE_ALPHA
        Processing differs depending on whether color buffers store	color
        indices or RGBA color components.  If color	indices	are stored,
        they are read from the color buffer	selected by glReadBuffer.
        Each index is converted to fixed point, shifted left or right
        depending on the value and sign of GL_INDEX_SHIFT, and added to
        GL_INDEX_OFFSET. Indices are then replaced by the red, green,
        blue, and alpha values obtained by indexing	the
        GL_PIXEL_MAP_I_TO_R, GL_PIXEL_MAP_I_TO_G, GL_PIXEL_MAP_I_TO_B,
        and	GL_PIXEL_MAP_I_TO_A tables.  Each of these tables must be of
                  n
        size 2 , but n may be different for different tables. Before an
                                                                 n
        index is used to look up a value in a table of size 2 it must
                               n
        be masked against 2 -1.

        If RGBA color components are stored	in the color buffers, they
        are	read from the color buffer selected by glReadBuffer.  Each
        color component is converted to floating point such	that zero
        intensity maps to 0.0 and full intensity maps to 1.0.  Each
        component is then multiplied by GL_c_SCALE and added to
        GL_c_BIAS, where c is RED, GREEN, BLUE, or ALPHA.  Finally,	if
        GL_MAP_COLOR is GL_TRUE, Each component is clamped to the range
        [0,1], scaled to the size of its corresponding table, and is then
        replaced by	its mapping in the table GL_PIXEL_MAP_c_TO_c, where c
        is R, G, B,	or A.

        Unneeded data is then discarded.  For example, GL_RED discards
        the	green, blue, and alpha components, while GL_RGB	discards only
        the	alpha component.  GL_LUMINANCE computes	a single component
        value as the sum of	the red, green,	and blue components, and
        GL_LUMINANCE_ALPHA does the	same, while keeping alpha as a second
        value.  The	final values are clamped to the	range [0,1].

  The shift, scale, bias, and lookup factors described above are all
  specified by glPixelTransfer.	 The lookup table contents themselves are
  specified by glPixelMap.

  The final step involves converting the indices or components to the proper
  format, as specified by type.	 If format is GL_COLOR_INDEX or
  GL_STENCIL_INDEX and type is not GL_FLOAT, each index	is masked with the
  mask value given in the following table.  If type is GL_FLOAT, then each
  integer index	is converted to	single-precision floating-point	format.

  If format is GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA,
  GL_LUMINANCE,	or GL_LUMINANCE_ALPHA and type is not GL_FLOAT,	each
  component is multiplied by the multiplier shown in the following table.  If
  type is GL_FLOAT, then each component	is passed as is	(or converted to the
  client's single-precision floating-point format if it	is different from the
  one used by the GL).

        -------------------------------------------------------
        |      type        | index mask | component conversion |
        -------------------------------------------------------
        |GL_UNSIGNED_BYTE  |   2^8-1    |	    (2^8-1)c	   |
        |	  GL_BYTE      |   2^7-1    |	 [(2^8-1)c-1]/2	   |
        |	 GL_BITMAP     |     1	    |	       1	   |
        |GL_UNSIGNED_SHORT |   2^16-1   |	   (2^16-1)c	   |
        |	 GL_SHORT      |   2^15-1   |	[(2^16-1)c-1]/2	   |
        | GL_UNSIGNED_INT  |   2^32-1   |	   (2^32-1)c	   |
        |	  GL_INT       |   2^31-1   |	[(2^32-1)c-1]/2	   |
        |    GL_FLOAT      |    none    |          c           |
        -------------------------------------------------------

  Return values	are placed in memory as	follows.  If format is
  GL_COLOR_INDEX, GL_STENCIL_INDEX, GL_DEPTH_COMPONENT,	GL_RED,	GL_GREEN,
  GL_BLUE, GL_ALPHA, or	GL_LUMINANCE, a	single value is	returned and the data
  for the ith pixel in the jth row is placed in	location (j) width + i.
  GL_RGB returns three values, GL_RGBA returns four values, and
  GL_LUMINANCE_ALPHA returns two values	for each pixel,	with all values
  corresponding	to a single pixel occupying contiguous space in	pixels.
  Storage parameters set by glPixelStore, such as GL_PACK_SWAP_BYTES and
  GL_PACK_LSB_FIRST, affect the	way that data is written into memory. See
  glPixelStore for a description.

Notes

  Values for pixels that lie outside the window	connected to the current GL
  context are undefined.

  If an	error is generated, no change is made to the contents of pixels.

Errors

  GL_INVALID_ENUM is generated if format or type is not an accepted value.

  GL_INVALID_VALUE is generated	if either width	or height is negative.

  GL_INVALID_OPERATION is generated if format is GL_COLOR_INDEX and the	color
  buffers store	RGBA color components.

  GL_INVALID_OPERATION is generated if format is GL_STENCIL_INDEX and there
  is no	stencil	buffer.

  GL_INVALID_OPERATION is generated if format is GL_DEPTH_COMPONENT and	there
  is no	depth buffer.

  GL_INVALID_OPERATION is generated if glReadPixels is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_INDEX_MODE

See Also

  glCopyPixels,	glDrawPixels, glPixelMap, glPixelStore,	glPixelTransfer,
  glReadBuffer

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


glRect.3gl

Name

  glRectd, glRectf, glRecti, glRects, glRectdv,	glRectfv, glRectiv, glRectsv
  - draw a rectangle

C Specification

  void glRectd(	GLdouble x1,
        GLdouble y1,
        GLdouble x2,
        GLdouble y2 )
  void glRectf(	GLfloat	x1,
        GLfloat	y1,
        GLfloat	x2,
        GLfloat	y2 )
  void glRecti(	GLint x1,
        GLint y1,
        GLint x2,
        GLint y2 )
  void glRects(	GLshort	x1,
        GLshort	y1,
        GLshort	x2,
        GLshort	y2 )

Parameters

  x1, y1 Specify one vertex of a rectangle.

  x2, y2 Specify the opposite vertex of	the rectangle.

C Specification

  void glRectdv( const GLdouble	*v1,
         const GLdouble	*v2 )
  void glRectfv( const GLfloat *v1,
         const GLfloat *v2 )
  void glRectiv( const GLint *v1,
         const GLint *v2 )
  void glRectsv( const GLshort *v1,
         const GLshort *v2 )

Parameters

  v1   Specifies a pointer to one vertex of a rectangle.

  v2   Specifies a pointer to the opposite vertex of the rectangle.

Description

  glRect supports efficient specification of rectangles	as two corner points.
  Each	rectangle command takes	four arguments,	organized either as two
  consecutive pairs of (x,y) coordinates, or as	two pointers to	arrays,	each
  containing an	(x,y) pair.  The resulting rectangle is	defined	in the z=0
  plane.

  glRect(x1, y1, x2, y2) is exactly equivalent to the following	sequence:
  glBegin(GL_POLYGON); glVertex2(x1, y1); glVertex2(x2,	y1); glVertex2(x2,
  y2); glVertex2(x1, y2); glEnd(); Note	that if	the second vertex is above
  and to the right of the first	vertex,	the  rectangle is constructed with a
  counterclockwise winding.

Errors

  GL_INVALID_OPERATION is generated if glRect is called	between	a call to
  glBegin and the corresponding	call to	glEnd.

See Also

  glBegin, glVertex

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glRenderMode.3gl

Name

  glRenderMode - set rasterization mode

C Specification

  GLint	glRenderMode( GLenum mode )

Parameters

  mode	Specifies the rasterization mode.  Three values	are accepted:
    GL_RENDER, GL_SELECT, and GL_FEEDBACK.	The default value is
    GL_RENDER.

Description

  glRenderMode sets the	rasterization mode.  It	takes one argument, mode,
  which	can assume one of three	predefined values:

  GL_RENDER	  Render mode. Primitives are rasterized, producing pixel
          fragments, which are written into the	frame buffer.  This
          is the normal	mode and also the default mode.

  GL_SELECT	  Selection mode.  No pixel fragments are produced, and	no
          change to the	frame buffer contents is made.	Instead, a
          record of the	names of primitives that would have been
          drawn	if the render mode was GL_RENDER is returned in	a
          select buffer, which must be created (see glSelectBuffer)
          before selection mode	is entered.

  GL_FEEDBACK	  Feedback mode.  No pixel fragments are produced, and no
          change to the	frame buffer contents is made.	Instead, the
          coordinates and attributes of	vertices that would have been
          drawn	had the	render mode been GL_RENDER is returned in a
          feedback buffer, which must be created (see
          glFeedbackBuffer) before feedback mode is entered.

  The return value of glRenderMode is determined by the	render mode at the
  time glRenderMode is called, rather than by mode.  The values	returned for
  the three render modes are as	follows:

  GL_RENDER	  Zero.

  GL_SELECT	  The number of	hit records transferred	to the select buffer.

  GL_FEEDBACK	  The number of	values (not vertices) transferred to the
          feedback buffer.

  Refer	to the glSelectBuffer and glFeedbackBuffer reference pages for more
  details concerning selection and feedback operation.

Notes

  If an	error is generated, glRenderMode returns zero regardless of the
  current render mode.

Errors

  GL_INVALID_ENUM is generated if mode is not one of the three accepted
  values.

  GL_INVALID_OPERATION is generated if glSelectBuffer is called	while the
  render mode is GL_SELECT, or if glRenderMode is called with argument
  GL_SELECT before glSelectBuffer is called at least once.

  GL_INVALID_OPERATION is generated if glFeedbackBuffer	is called while	the
  render mode is GL_FEEDBACK, or if glRenderMode is called with	argument
  GL_FEEDBACK before glFeedbackBuffer is called	at least once.

  GL_INVALID_OPERATION is generated if glRenderMode is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_RENDER_MODE

See Also

  glFeedbackBuffer, glInitNames, glLoadName, glPassThrough, glPushName,
  glSelectBuffer

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glRotate.3gl

Name

  glRotated, glRotatef - multiply the current matrix by	a rotation matrix

C Specification

  void glRotated( GLdouble angle,
          GLdouble x,
          GLdouble y,
          GLdouble z )
  void glRotatef( GLfloat angle,
          GLfloat x,
          GLfloat y,
          GLfloat z )

Parameters

  angle	 Specifies the angle of	rotation, in degrees.

  x, y,	z
     Specify the x,	y, and z coordinates of	a vector, respectively.

Description

  glRotate computes a matrix that performs a counterclockwise rotation of
  angle	degrees	about the vector from the origin through the point (x, y, z).

  The current matrix (see glMatrixMode)	is multiplied by this rotation
  matrix, with the product replacing the current matrix.  That is, if M	is
  the current matrix and R is the translation matrix, then M is	replaced with
  M · R.

  If the matrix	mode is	either GL_MODELVIEW or GL_PROJECTION, all objects
  drawn	after glRotate is called are rotated.  Use glPushMatrix	and
  glPopMatrix to save and restore the unrotated	coordinate system.

Errors

  GL_INVALID_OPERATION is generated if glRotate is executed between
  the execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_MATRIX_MODE
  glGet	with argument GL_MODELVIEW_MATRIX
  glGet	with argument GL_PROJECTION_MATRIX
  glGet	with argument GL_TEXTURE_MATRIX

See Also

  glMatrixMode,	glMultMatrix, glPushMatrix, glScale, glTranslate

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glScale.3gl

Name

  glScaled, glScalef - multiply	the current matrix by a	general	scaling
  matrix

C Specification

  void glScaled( GLdouble x,
         GLdouble y,
         GLdouble z )
  void glScalef( GLfloat x,
         GLfloat y,
         GLfloat z )

Parameters

  x, y,	z
     Specify scale factors along the x, y, and z axes, respectively.

Description

  glScale produces a general scaling along the x, y, and z axes.  The three
  arguments indicate the desired scale factors along each of the three axes.
  The resulting	matrix is

                  |	 x   0	 0   0	|
                  |			|
                  |	 0   y	 0   0	|
                  |			|
                  |	 0   0	 z   0	|
                  |			|
                  |	 0   0	 0   1	|

  The current matrix (see glMatrixMode)	is multiplied by this scale matrix,
  with the product replacing the current matrix.  That is, if M	is the
  current matrix and S is the scale matrix, then M is replaced with M ·	S.

  If the matrix	mode is	either GL_MODELVIEW or GL_PROJECTION, all objects
  drawn	after glScale is called	are scaled.  Use glPushMatrix and glPopMatrix
  to save and restore the unscaled coordinate system.

Notes

  If scale factors other than 1.0 are applied to the modelview matrix and
  lighting is enabled, automatic normalization of normals should probably
  also be enabled (glEnable and	glDisable with argument	GL_NORMALIZE).

Errors

  GL_INVALID_OPERATION is generated if glScale is executed between
  the execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_MATRIX_MODE
  glGet	with argument GL_MODELVIEW_MATRIX
  glGet	with argument GL_PROJECTION_MATRIX
  glGet	with argument GL_TEXTURE_MATRIX

See Also

  glMatrixMode,	glMultMatrix, glPushMatrix, glRotate, glTranslate

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


glScissor.3gl

Name

  glScissor - define the scissor box

C Specification

  void glScissor( GLint	x,
          GLint	y,
          GLsizei width,
          GLsizei height )

Parameters

  x, y Specify the lower left corner of	the scissor box.  Initially (0,0).

  width, height
       Specify the width and height of the scissor box.	 When a	GL context is
       first attached to a window, width and height are	set to the dimensions
       of that window.

Description

  The glScissor	routine	defines	a rectangle, called the	scissor	box, in
  window coordinates.  The first two arguments,	x and y, specify the lower
  left corner of the box.  width and height specify the	width and height of
  the box.

  The scissor test is enabled and disabled using glEnable and glDisable	with
  argument GL_SCISSOR_TEST.  While the scissor test is enabled,	only pixels
  that lie within the scissor box can be modified by drawing commands.
  Window coordinates have integer values at the	shared corners of frame
  buffer pixels, so  glScissor(0,0,1,1)	allows only the	lower left pixel in
  the window to	be modified,  and glScissor(0,0,0,0) disallows modification
  to all pixels	in the window.

  When the scissor test	is disabled, it	is as though the scissor box includes
  the entire window.

Errors

  GL_INVALID_VALUE is generated	if either width	or height is negative.

  GL_INVALID_OPERATION is generated if glScissor is executed between
  execution of glBegin and the corresponding execution ofglEnd.

Associated Gets

  glGet	with argument GL_SCISSOR_BOX
  glIsEnabled with argument GL_SCISSOR_TEST

See Also

  glEnable, glViewport

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


glSelectBuffer.3gl

Name

  glSelectBuffer - establish a buffer for selection mode values

C Specification

  void glSelectBuffer( GLsizei size,
               GLuint *buffer )

Parameters

  size	  Specifies the	size of	buffer.

  buffer  Returns the selection	data.

Description

  glSelectBuffer has two arguments: buffer is a	pointer	to an array of
  unsigned integers, and size indicates	the size of the	array.	buffer
  returns values from the name stack (see glInitNames, glLoadName,
  glPushName) when the rendering mode is GL_SELECT (see	glRenderMode).
  glSelectBuffer must be issued	before selection mode is enabled, and it must
  not be issued	while the rendering mode is GL_SELECT.

  Selection is used by a programmer to determine which primitives are drawn
  into some region of a	window.	 The region is defined by the current
  modelview and	perspective matrices.

  In selection mode, no	pixel fragments	are produced from rasterization.
  Instead, if a	primitive intersects the clipping volume defined by the
  viewing frustum and the user-defined clipping	planes,	this primitive causes
  a selection hit.  (With polygons, no hit occurs if the polygon is culled.)
  When a change	is made	to the name stack, or when glRenderMode	is called, a
  hit record is	copied to buffer if any	hits have occurred since the last
  such event (name stack change	or glRenderMode	call).	The hit	record
  consists of the number of names in the name stack at the time	of the event,
  followed by the minimum and maximum depth values of all vertices that	hit
  since	the previous event, followed by	the name stack contents, bottom	name
  first.

  Returned depth values	are mapped such	that the largest unsigned integer
  value	corresponds to window coordinate depth 1.0, and	zero corresponds to
  window coordinate depth 0.0.

  An internal index into buffer	is reset to zero whenever selection mode is
  entered.  Each time a	hit record is copied into buffer, the index is
  incremented to point to the cell just	past the end of	the block of names -
  that is, to the next available cell.	If the hit record is larger than the
  number of remaining locations	in buffer, as much data	as can fit is copied,
  and the overflow flag	is set.	 If the	name stack is empty when a hit record
  is copied, that record consists of zero followed by the minimum and maximum
  depth	values.

  Selection mode is exited by calling glRenderMode with	an argument other
  than GL_SELECT.  Whenever glRenderMode is called while the render mode is
  GL_SELECT, it	returns	the number of hit records copied to buffer, resets
  the overflow flag and	the selection buffer pointer, and initializes the
  name stack to	be empty.  If the overflow bit was set when glRenderMode was
  called, a negative hit record	count is returned.

Notes

  The contents of buffer are undefined until glRenderMode is called with an
  argument other than GL_SELECT.

  glBegin/glEnd	primitives and calls to	glRasterPos can	result in hits.

Errors

  GL_INVALID_VALUE is generated	if size	is negative.

  GL_INVALID_OPERATION is generated if glSelectBuffer is called	while the
  render mode is GL_SELECT, or if glRenderMode is called with argument
  GL_SELECT before glSelectBuffer is called at least once.

  GL_INVALID_OPERATION is generated if glSelectBuffer is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_NAME_STACK_DEPTH

See Also

  glFeedbackBuffer, glInitNames, glLoadName, glPushName, glRenderMode

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glShadeModel.3gl

Name

  glShadeModel - select	flat or	smooth shading

C Specification

  void glShadeModel( GLenum mode )

Parameters

  mode	Specifies a symbolic value representing	a shading technique.
    Accepted values	are GL_FLAT and	GL_SMOOTH.  The	default	is GL_SMOOTH.

Description

  GL primitives	can have either	flat or	smooth shading.	 Smooth	shading, the
  default, causes the computed colors of vertices to be	interpolated as	the
  primitive is rasterized, typically assigning different colors	to each
  resulting pixel fragment.  Flat shading selects the computed color of	just
  one vertex and assigns it to all the pixel fragments generated by
  rasterizing a	single primitive.  In either case, the computed	color of a
  vertex is the	result of lighting, if lighting	is enabled, or it is the
  current color	at the time the	vertex was specified, if lighting is
  disabled.

  Flat and smooth shading are indistinguishable	for points.  Counting
  vertices and primitives from one starting when glBegin is issued, each
  flat-shaded line segment i is	given the computed color of vertex i+1,	its
  second vertex.  Counting similarly from one, each flat-shaded	polygon	is
  given	the computed color of the vertex listed	in the following table.	 This
  is the last vertex to	specify	the polygon in all cases except	single
  polygons, where the first vertex specifies the flat-shaded color.

            ---------------------------------------
            |primitive type of polygon   |   i    |
            ---------------------------------------
            |Single polygon (i=1)	 |   1	  |
            |Triangle strip		 |  i+2	  |
            |Triangle fan		 |  i+2	  |
            |Independent triangle	 |   3i	  |
            |Quad strip			 |  2i+2  |
            |Independent quad		 |   4i	  |
            ---------------------------------------

  Flat and smooth shading are specified	by glShadeModel	with mode set to
  GL_FLAT and GL_SMOOTH, respectively.

Errors

  GL_INVALID_ENUM is generated if mode is any value other than GL_FLAT or
  GL_SMOOTH.

  GL_INVALID_OPERATION is generated if glShadeModel is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet with argument GL_SHADE_MODEL

See Also

  glBegin, glColor, glLight, glLightModel

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


glStencilFunc.3gl

Name

  glStencilFunc	- set function and reference value for stencil testing

C Specification

  void glStencilFunc( GLenum func,
              GLint ref,
              GLuint mask )

Parameters

  func	Specifies the test function.  Eight tokens are valid: GL_NEVER,
    GL_LESS, GL_LEQUAL, GL_GREATER,	GL_GEQUAL, GL_EQUAL, GL_NOTEQUAL, and
    GL_ALWAYS.

  ref	Specifies the reference	value for the stencil test.  ref is clamped
                     n
    to the range [0,2 -1], where n is the number of	bitplanes in the
    stencil	buffer.

  mask	Specifies a mask that is ANDed with both the reference value and the
    stored stencil value when the test is done.

Description

  Stenciling, like z-buffering,	enables	and disables drawing on	a per-pixel
  basis.  You draw into	the stencil planes using GL drawing primitives,	then
  render geometry and images, using the	stencil	planes to mask out portions
  of the screen.  Stenciling is	typically used in multipass rendering
  algorithms to	achieve	special	effects, such as decals, outlining, and
  constructive solid geometry rendering.

  The stencil test conditionally eliminates a pixel based on the outcome of a
  comparison between the reference value and the value in the stencil buffer.
  The test is enabled by glEnable and glDisable	with argument
  GL_STENCIL_TEST.  Actions taken based	on the outcome of the stencil test
  are specified	with glStencilOp.

  func is a symbolic constant that determines the stencil comparison
  function.  It	accepts	one of eight values, shown below.  ref is an integer
  reference value that is used in the stencil comparison.  It is clamped to
  the range [0,2n-1], where n is the number of bitplanes in the	stencil
  buffer.  mask	is bitwise ANDed with both the reference value and the stored
  stencil value, with the ANDed	values participating in	the comparison.

  If stencil represents	the value stored in the	corresponding stencil buffer
  location, the	following list shows the effect	of each	comparison function
  that can be specified	by func.  Only if the comparison succeeds is the
  pixel	passed through to the next stage in the	rasterization process (see
  glStencilOp).	 All tests treat stencil values	as unsigned integers in	the
  range	[0,2n-1], where	n is the number	of bitplanes in the stencil buffer.

  Here are the values accepted by func:

  GL_NEVER	    Always fails.

  GL_LESS	    Passes if ( ref & mask ) < ( stencil & mask ).

  GL_LEQUAL	    Passes if ( ref & mask ) <= ( stencil & mask ).

  GL_GREATER	    Passes if ( ref & mask ) > ( stencil & mask ).

  GL_GEQUAL	    Passes if ( ref & mask ) >= ( stencil & mask ).

  GL_EQUAL	    Passes if ( ref & mask ) = ( stencil & mask ).

  GL_NOTEQUAL	    Passes if ( ref & mask ) != ( stencil & mask ).

  GL_ALWAYS	    Always passes.

Notes

  Initially, the stencil test is disabled.  If there is	no stencil buffer, no
  stencil modification can occur and it	is as if the stencil test always
  passes.

Errors

  GL_INVALID_ENUM is generated if func is not one of the eight accepted
  values.

  GL_INVALID_OPERATION is generated if glStencilFunc is	executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_STENCIL_FUNC
  glGet	with argument GL_STENCIL_VALUE_MASK
  glGet	with argument GL_STENCIL_REF
  glGet	with argument GL_STENCIL_BITS
  glIsEnabled with argument GL_STENCIL_TEST

See Also

  glAlphaFunc, glBlendFunc, glDepthFunc, glEnable, glIsEnabled,	glLogicOp,
  glStencilOp

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


glStencilMask.3gl

Name

  glStencilMask	- control the writing of individual bits in the	stencil
  planes

C Specification

  void glStencilMask( GLuint mask )

Parameters

  mask	Specifies a bit	mask to	enable and disable writing of individual bits
    in the stencil planes.	Initially, the mask is all ones.

Description

  glStencilMask	controls the writing of	individual bits	in the stencil
  planes.  The least significant n bits	of mask, where n is the	number of
  bits in the stencil buffer, specify a	mask.  Wherever	a one appears in the
  mask,	the corresponding bit in the stencil buffer is made writable.  Where
  a zero appears, the bit is write-protected.  Initially, all bits are
  enabled for writing.

Errors

  GL_INVALID_OPERATION is generated if glStencilMask is	executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_STENCIL_WRITEMASK
  glGet	with argument GL_STENCIL_BITS

See Also

  glColorMask, glDepthMask, glIndexMask, glStencilFunc,	glStencilOp

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glStencilOp.3gl

Name

  glStencilOp -	set stencil test actions

C Specification

  void glStencilOp( GLenum fail,
            GLenum zfail,
            GLenum zpass )

Parameters

  fail	 Specifies the action to take when the stencil test fails.  Six
     symbolic constants are	accepted: GL_KEEP, GL_ZERO, GL_REPLACE,
     GL_INCR, GL_DECR, and GL_INVERT.

  zfail	 Specifies stencil action when the stencil test	passes,	but the	depth
     test fails.  zfail accepts the	same symbolic constants	as fail.

  zpass	 Specifies stencil action when both the	stencil	test and the depth
     test pass, or when the	stencil	test passes and	either there is	no
     depth buffer or depth testing is not enabled.	zpass accepts the
     same symbolic constants as fail.

Description

  Stenciling, like z-buffering,	enables	and disables drawing on	a per-pixel
  basis.  You draw into	the stencil planes using GL drawing primitives,	then
  render geometry and images, using the	stencil	planes to mask out portions
  of the screen.  Stenciling is	typically used in multipass rendering
  algorithms to	achieve	special	effects, such as decals, outlining, and
  constructive solid geometry rendering.

  The stencil test conditionally eliminates a pixel based on the outcome of a
  comparison between the value in the stencil buffer and a reference value.
  The test is enabled with glEnable and	glDisable calls	with argument
  GL_STENCIL_TEST, and controlled with glStencilFunc.

  glStencilOp takes three arguments that indicate what happens to the stored
  stencil value	while stenciling is enabled.  If the stencil test fails, no
  change is made to the	pixel's	color or depth buffers,	and fail specifies
  what happens to the stencil buffer contents.	The six	possible actions are
  as follows:

  GL_KEEP	  Keeps	the current value.

  GL_ZERO	  Sets the stencil buffer value	to zero.

  GL_REPLACE	  Sets the stencil buffer value	to ref,	as specified by
          glStencilFunc.

  GL_INCR	  Increments the current stencil buffer	value.	Clamps to the
          maximum representable	unsigned value.

  GL_DECR	  Decrements the current stencil buffer	value.	Clamps to
          zero.

  GL_INVERT	  Bitwise inverts the current stencil buffer value.

  Stencil buffer values	are treated as unsigned	integers.  When	incremented
  and decremented, values are clamped to 0 and 2n-1, where n is	the value
  returned by querying GL_STENCIL_BITS.

  The other two	arguments to glStencilOp specify stencil buffer	actions
  should subsequent depth buffer tests succeed (zpass) or fail (zfail).	 (See
  glDepthFunc.)	They are specified using the same six symbolic constants as
  fail.	 Note that zfail is ignored when there is no depth buffer, or when
  the depth buffer is not enabled.  In these cases, fail and zpass specify
  stencil action when the stencil test fails and passes, respectively.

Notes

  Initially the	stencil	test is	disabled.  If there is no stencil buffer, no
  stencil modification can occur and it	is as if the stencil tests always
  pass,	regardless of any call to glStencilOp.

Errors

  GL_INVALID_ENUM is generated if fail,	zfail, or zpass	is any value other
  than the six defined constant	values.

  GL_INVALID_OPERATION is generated if glStencilOp is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_STENCIL_FAIL
  glGet	with argument GL_STENCIL_PASS_DEPTH_PASS
  glGet	with argument GL_STENCIL_PASS_DEPTH_FAIL
  glGet	with argument GL_STENCIL_BITS
  glIsEnabled with argument GL_STENCIL_TEST

See Also

  glAlphaFunc, glBlendFunc, glDepthFunc, glEnable, glLogicOp, glStencilFunc

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


glTexCoord.3gl

Name

  glTexCoord1d,	glTexCoord1f, glTexCoord1i, glTexCoord1s, glTexCoord2d,
  glTexCoord2f,	glTexCoord2i, glTexCoord2s, glTexCoord3d, glTexCoord3f,
  glTexCoord3i,	glTexCoord3s, glTexCoord4d, glTexCoord4f, glTexCoord4i,
  glTexCoord4s,	glTexCoord1dv, glTexCoord1fv, glTexCoord1iv, glTexCoord1sv,
  glTexCoord2dv, glTexCoord2fv,	glTexCoord2iv, glTexCoord2sv, glTexCoord3dv,
  glTexCoord3fv, glTexCoord3iv,	glTexCoord3sv, glTexCoord4dv, glTexCoord4fv,
  glTexCoord4iv, glTexCoord4sv - set the current texture coordinates

C Specification

  void glTexCoord1d( GLdouble s	)
  void glTexCoord1f( GLfloat s )
  void glTexCoord1i( GLint s )
  void glTexCoord1s( GLshort s )
  void glTexCoord2d( GLdouble s,
             GLdouble t	)
  void glTexCoord2f( GLfloat s,
             GLfloat t )
  void glTexCoord2i( GLint s,
             GLint t )
  void glTexCoord2s( GLshort s,
             GLshort t )
  void glTexCoord3d( GLdouble s,
             GLdouble t,
             GLdouble r	)
  void glTexCoord3f( GLfloat s,
             GLfloat t,
             GLfloat r )
  void glTexCoord3i( GLint s,
             GLint t,
             GLint r )
  void glTexCoord3s( GLshort s,
             GLshort t,
             GLshort r )
  void glTexCoord4d( GLdouble s,
             GLdouble t,
             GLdouble r,
             GLdouble q	)
  void glTexCoord4f( GLfloat s,
             GLfloat t,
             GLfloat r,
             GLfloat q )
  void glTexCoord4i( GLint s,
             GLint t,
             GLint r,
             GLint q )
  void glTexCoord4s( GLshort s,
             GLshort t,
             GLshort r,
             GLshort q )

Parameters

  s, t,	r, q
       Specify s, t, r, and	q  texture coordinates.	 Not all parameters
       are present in all forms of the command.

C Specification

  void glTexCoord1dv( const GLdouble *v	)
  void glTexCoord1fv( const GLfloat *v )
  void glTexCoord1iv( const GLint *v )
  void glTexCoord1sv( const GLshort *v )
  void glTexCoord2dv( const GLdouble *v	)
  void glTexCoord2fv( const GLfloat *v )
  void glTexCoord2iv( const GLint *v )
  void glTexCoord2sv( const GLshort *v )
  void glTexCoord3dv( const GLdouble *v	)
  void glTexCoord3fv( const GLfloat *v )
  void glTexCoord3iv( const GLint *v )
  void glTexCoord3sv( const GLshort *v )
  void glTexCoord4dv( const GLdouble *v	)
  void glTexCoord4fv( const GLfloat *v )
  void glTexCoord4iv( const GLint *v )
  void glTexCoord4sv( const GLshort *v )

Parameters

  v    Specifies a pointer to an array of one, two, three, or four elements,
       which in	turn specify the s, t, r, and q	texture	coordinates.

Description

  The current texture coordinates are part of the data that is associated
  with each vertex and with the	current	raster position.  They are set with
  glTexCoord.

  glTexCoord specifies texture coordinates in one, two,	three, or four
  dimensions. glTexCoord1 sets the current texture coordinates to (s, 0, 0,
  1); a	call to	glTexCoord2 sets them to (s, t,	0, 1).	Similarly,
  glTexCoord3 specifies	the texture coordinates	as (s, t, r, 1), and
  glTexCoord4 defines all four components explicitly as	(s, t, r, q).

Notes

  The current texture coordinates can be updated at any	time.  In particular,
  glTexCoord can be called between a call to glBegin and the corresponding
  call to glEnd.

Associated Gets

  glGet	with argument GL_CURRENT_TEXTURE_COORDS

See Also

  glVertex

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glTexCoordPointerEXT.3gl

Name

  glTexCoordPointerEXT - define	an array of texture coordinates

C Specification

  void glTexCoordPointerEXT( GLint size,
                 GLenum type,
                 GLsizei stride,
                 GLsizei count,
                 const GLvoid *pointer )

Parameters

  size	   Specifies the number	of coordinates per array element. It must be
       1, 2, 3 or 4.

  type	   Specifies the data type of each texture coordinate.	Symbolic
       constants GL_SHORT, GL_INT, GL_FLOAT, or GL_DOUBLE_EXT, are
       accepted.

  stride   Specifies the byte offset between consecutive array elements.  If
       stride is zero the array elements are understood to be tightly
       packed.

  count	   Specifies the number	of array elements, counting from the first,
       that	are static.

  pointer  Specifies a pointer to the first coordinate of the first element
       in the array.

Description

  glTexCoordPointerEXT specifies the location and data format of an array of
  texture coordinates to use when rendering using the vertex array extension.
  size specifies the number of coordinates per element, and must be 1, 2, 3,
  or 4.  type specifies the data type of each texture coordinate and stride
  gives the byte stride from one array element to the next allowing vertexes
  and attributes to be packed into a single array or stored in separate arrays.
  (Single-array storage may be more efficient on some implementations.) count
  indicates the number of array elements (counting from the first) that are
  static. Static elements may be modified by the application, but once they
  are modified, the application must explicitly respecify the array before
  using it for any rendering. When a texture coordinate array is specified,
  size, type, stride, count, and pointer are saved  as client-side state, and
  static array elements may be cached by the implementation.

  The texture coordinate array is enabled and disabled using glEnable and
  glDisable with the argument GL_TEXTURE_COORD_ARRAY_EXT. If enabled, the
  texture coordinate array is used when	glDrawArraysEXT	or glArrayElementEXT
  is called.

  Use glDrawArraysEXT to define	a sequence of primitives (all of the same
  type)	from pre-specified vertex and vertex attribute arrays.	Use
  glArrayElementEXT to specify primitives by indexing vertexes and vertex
  attributes.

Notes

  Non-static array elements are	not accessed until glArrayElementEXT or
  glDrawArraysEXT is executed.

  By default the texture coordinate array is disabled and it won't be
  accessed when	glArrayElementEXT or glDrawArraysEXT is	called.

  Although it is not an error to call glTexCoordPointerEXT between the
  execution of glBegin and the corresponding execution of glEnd, the results
  are undefined.

  glTexCoordPointerEXT will typically be implemented on	the client side	with
  no protocol.

  Since	the texture coordinate array parameters	are client side	state, they
  are not saved	or restored by glPushAttrib and	glPopAttrib.

  glTexCoordPointerEXT commands	are not	entered	into display lists.

  glTexCoordPointerEXT is part of the EXT_vertex_array extension,
  not part of the core GL command set. If "GL_EXT_vertex_array" is
  included in the string returned by glGetString, when called with argument
  GL_EXTENSIONS, extension EXT_vertex_array is supported.

Errors

  GL_INVALID_VALUE is generated	if size	is not 1, 2, 3,	or 4.

  GL_INVALID_ENUM is generated if type is not an accepted value.

  GL_INVALID_VALUE is generated	if stride or count  is negative

Associated Gets

  glIsEnabled with argument GL_TEXTURE_COORD_ARRAY_EXT
  glGet	with argument GL_TEXTURE_COORD_ARRAY_SIZE_EXT
  glGet	with argument GL_TEXTURE_COORD_ARRAY_TYPE_EXT
  glGet	with argument GL_TEXTURE_COORD_ARRAY_STRIDE_EXT
  glGet	with argument GL_TEXTURE_COORD_ARRAY_COUNT_EXT
  glGetPointervEXT with	argument GL_TEXTURE_COORD_ARRAY_POINTER_EXT

See Also

  glArrayElementEXT, glColorPointerEXT,	glDrawArraysEXT,
  glEdgeFlagPointerEXT,	glGetPointervEXT, glIndexPointerEXT,
  glNormalPointerEXT, glVertexPointerEXT, glEnable

Introduction | Alphabetic | Specification

Last Edited: Fri Feb 24, 1995

AFV


glTexEnv.3gl

Name

  glTexEnvf, glTexEnvi,	glTexEnvfv, glTexEnviv - set texture environment
  parameters

C Specification

  void glTexEnvf( GLenum target,
          GLenum pname,
          GLfloat param	)
  void glTexEnvi( GLenum target,
          GLenum pname,
          GLint	param )

Parameters

  target  Specifies a texture environment.  Must be GL_TEXTURE_ENV.

  pname	  Specifies the	symbolic name of a single-valued texture environment
      parameter.  Must be GL_TEXTURE_ENV_MODE.

  param	  Specifies a single symbolic constant,	one of GL_MODULATE, GL_DECAL,
      or GL_BLEND.

C Specification

  void glTexEnvfv( GLenum target,
           GLenum pname,
           const GLfloat *params )
  void glTexEnviv( GLenum target,
           GLenum pname,
           const GLint *params )

Parameters

  target
       Specifies a texture environment.	 Must be GL_TEXTURE_ENV.

  pname
       Specifies the symbolic name of a	texture	environment parameter.
       Accepted	values are GL_TEXTURE_ENV_MODE and GL_TEXTURE_ENV_COLOR.

  params
       Specifies a pointer to an array of parameters: either a single
       symbolic	constant or an RGBA color.

Description

  A texture environment	specifies how texture values are interpreted when a
  fragment is textured.	 target	must be	GL_TEXTURE_ENV.	 pname can be either
  GL_TEXTURE_ENV_MODE or GL_TEXTURE_ENV_COLOR.

  If pname is GL_TEXTURE_ENV_MODE, then	params is (or points to) the symbolic
  name of a texture function.  Three texture functions are defined:
  GL_MODULATE, GL_DECAL, and GL_BLEND

  A texture function acts on the fragment to be	textured using the texture
  image	value that applies to the fragment (see	glTexParameter)	and produces
  an RGBA color	for that fragment.  The	following table	shows how the RGBA
  color	is produced for	each of	the three texture functions that can be
  chosen.  C is	a triple of color values (RGB) and A is	the associated alpha
  value.  RGBA values extracted	from a texture image are in the	range [0,1].
  The subscript	f refers to the	incoming fragment, the subscript t to the
  texture image, the subscript c to the	texture	environment color, and
  subscript v indicates	a value	produced by the	texture	function.

  A texture image can have up to four components per texture element (see
  glTexImage1D and glTexImage2D).  In a	one-component image, L	indicates
                                                              t
  that single component.  A two-component image	uses L	and A .	 A three-
                                                      t      t
  component image has only a color value, C .  A four-component	image has
                                           t
  both a color value C	and an alpha value A .
              t			    t

       ----------------------------------------------------------------
       | number of ||		 texture functions		       |
       |components || GL_MODULATE      GL_DECAL           GL_BLEND     |
       ----------------------------------------------------------------
       |     	   ||  C =L C	 |		    | C	=(1-L )C +L C  |
       |    1	   ||	v  t f	 |    undefined	    |  v     t	f  t c |
       |           ||  A =A      |                  | A =A             |
       |           ||   v  f     |                  |  v  f            |
       |-----------||------------|------------------|------------------|
       |     	   ||  C =L C 	 |             	    | C =(1-L )C +L C  |
       |    2	   ||	v  t f	 |    undefined	    |  v     t	f  t c |
       |           ||  A =A A    |                  | A =A A           |
       |	   ||	v  t f	 |             	    |  v  t f          |
       |-----------||------------|------------------|------------------|
       |	   ||  C =C C	 |	C =C	    |		       |
       |    3	   ||	v  t f	 |	 v  t	    |	 undefined     |
       |           ||  A =A      |      A =A        |                  |
       |           ||   v  f     |       v  f       |                  |
       |-----------||------------|------------------|------------------|
       |           ||  C =C C    | C =(1-A )C +A C  |                  |
       |    4	   ||   v  t f	 |  v     t  f  t t |	 undefined     |
       |           ||  A =A A    |      A =A        |                  |
       |           ||   v  t f   |       v  f       |                  |
       ----------------------------------------------------------------

  If pname is GL_TEXTURE_ENV_COLOR, params is a pointer to an array that
  holds	an RGBA	color consisting of four values.  Integer color	components
  are interpreted linearly such	that the most positive integer maps to 1.0,
  and the most negative	integer	maps to	-1.0.  The values are clamped to the
  range	[0,1] when they	are specified.	C  takes these four values.
                     c
  GL_TEXTURE_ENV_MODE defaults to GL_MODULATE and GL_TEXTURE_ENV_COLOR
  defaults to (0,0,0,0).

Errors

  GL_INVALID_ENUM is generated when target or pname is not one of the
  accepted defined values, or when params should have a	defined	constant
  value	(based on the value of pname) and does not.

  GL_INVALID_OPERATION is generated if glTexEnv is executed between
  the execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGetTexEnv

See Also

  glTexImage1D,	glTexImage2D, glTexParameter

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


glTexGen.3gl

Name

  glTexGend, glTexGenf,	glTexGeni, glTexGendv, glTexGenfv, glTexGeniv -
  control the generation of texture coordinates

C Specification

  void glTexGend( GLenum coord,
          GLenum pname,
          GLdouble param )
  void glTexGenf( GLenum coord,
          GLenum pname,
          GLfloat param	)
  void glTexGeni( GLenum coord,
          GLenum pname,
          GLint	param )

Parameters

  coord	  Specifies a texture coordinate.  Must	be one of the following:
      GL_S,	GL_T, GL_R, or GL_Q.

  pname	  Specifies the	symbolic name of the texture-coordinate	generation
      function.  Must be GL_TEXTURE_GEN_MODE.

  param	  Specifies a single-valued texture generation parameter, one of
      GL_OBJECT_LINEAR, GL_EYE_LINEAR, or GL_SPHERE_MAP.

C Specification

  void glTexGendv( GLenum coord,
           GLenum pname,
           const GLdouble *params )
  void glTexGenfv( GLenum coord,
           GLenum pname,
           const GLfloat *params )
  void glTexGeniv( GLenum coord,
           GLenum pname,
           const GLint *params )

Parameters

  coord
       Specifies a texture coordinate.	Must be	one of the following: GL_S,
       GL_T, GL_R, or GL_Q.

  pname
       Specifies the symbolic name of the texture-coordinate generation
       function	or function parameters.	 Must be GL_TEXTURE_GEN_MODE,
       GL_OBJECT_PLANE,	or GL_EYE_PLANE.

  params
       Specifies a pointer to an array of texture generation parameters.  If
       pname is	GL_TEXTURE_GEN_MODE, then the array must contain a single
       symbolic	constant, one of GL_OBJECT_LINEAR, GL_EYE_LINEAR, or
       GL_SPHERE_MAP. Otherwise, params	holds the coefficients for the
       texture-coordinate generation function specified	by pname.

Description

  glTexGen selects a texture-coordinate	generation function or supplies
  coefficients for one of the functions.  coord	names one of the (s,t,r,q)
  texture coordinates, and it must be one of these symbols: GL_S, GL_T,	GL_R,
  or GL_Q.  pname must be one of three symbolic	constants:
  GL_TEXTURE_GEN_MODE, GL_OBJECT_PLANE,	or GL_EYE_PLANE. If pname is
  GL_TEXTURE_GEN_MODE, then params chooses a mode, one of GL_OBJECT_LINEAR,
  GL_EYE_LINEAR, or GL_SPHERE_MAP. If pname is either GL_OBJECT_PLANE or
  GL_EYE_PLANE,	params contains	coefficients for the corresponding texture
  generation function.

  If the texture generation function is	GL_OBJECT_LINEAR, the function

                 g=p x +p y	+p z +p	w
                1 o  2 o  3 o  4 o

  is used, where g is the value	computed for the coordinate named in coord,
  p , p	, p , and p  are the four values supplied in params, and x , y , z ,
   1   2   3       4                                              o   o   o
  and w  are the object	coordinates of the vertex.  This function can be used
       o
  to texture-map terrain using sea level as a reference	plane (defined by p ,
                                                                           1
  p , p	, and p	). The altitude	of a terrain vertex is computed	by the
   2   3       4
  GL_OBJECT_LINEAR coordinate generation function as its distance from sea
  level; that altitude is used to index	the texture image to map white snow
  onto peaks and green grass onto foothills, for example.

  If the texture generation function is	GL_EYE_LINEAR, the function

               g=p'x' + p'y	 + p'z  + p'w
                  1 e    2 e    3 e	   4 e

  is used, where
                                                        -1
           (p'	p'  p'  p') = (p   p   p   p )·M
             1	 2   3   4	1   2	3   4

  and x	, y , z	, and w	 are the eye coordinates of the	vertex,	p , p ,	p ,
       e   e   e       e                                         1   2   3
  and p  are the values supplied in params, and M is the modelview matrix
       4
  when glTexGen	is invoked.  If	M is poorly conditioned	or singular, texture
  coordinates generated	by the resulting function may be inaccurate or
  undefined.

  Note that the	values in params define	a reference plane in eye coordinates.
  The modelview	matrix that is applied to them may not be the same one in
  effect when the polygon vertices are transformed. This function establishes
  a field of texture coordinates that can produce dynamic contour lines	on
  moving objects.

  If pname is GL_SPHERE_MAP and coord is either GL_S or GL_T, s and t
  texture coordinates are generated as follows.	Let u be the unit vector
  pointing from	the origin to the polygon vertex (in eye coordinates). Let n'
  be the current normal, after transformation to eye coordinates. Let
                  T
  f = (f   f   f )  be the reflection vector such that
    x   y	z

                                             T
                f = u - 2n'n' u

              _____________
             | 2  2       2
  Finally, let m = 2\|f +f +(f +1) . Then the values assigned to the s and
                       x  y   z
  t texture coordinates	are

                       f
                        x   1
                  s = --- + -
                       m    2

                       f
                        y   1
                  t = --- + -
                       m    2

  A texture-coordinate generation function is enabled or disabled using
  glEnable or glDisable	with one of the	symbolic texture-coordinate names
  (GL_TEXTURE_GEN_S, GL_TEXTURE_GEN_T, GL_TEXTURE_GEN_R, or GL_TEXTURE_GEN_Q)
  as the argument. When	enabled, the specified texture coordinate is computed
  according to the generating function associated with that coordinate.	When
  disabled, subsequent vertices	take the specified texture coordinate from
  the current set of texture coordinates. Initially, all texture generation
  functions are	set to GL_EYE_LINEAR and are disabled.	Both s plane
  equations are	(1,0,0,0), both	t plane	equations are (0,1,0,0), and all r
  and q	plane equations	are (0,0,0,0).

Errors

  GL_INVALID_ENUM is generated when coord or pname is not an accepted defined
  value, or when pname is GL_TEXTURE_GEN_MODE and params is not	an accepted
  defined value.

  GL_INVALID_ENUM is generated when pname is GL_TEXTURE_GEN_MODE, params is
  GL_SPHERE_MAP, and coord is either GL_R or GL_Q.

  GL_INVALID_OPERATION is generated if glTexGen is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGetTexGen
  glIsEnabled with argument GL_TEXTURE_GEN_S
  glIsEnabled with argument GL_TEXTURE_GEN_T
  glIsEnabled with argument GL_TEXTURE_GEN_R
  glIsEnabled with argument GL_TEXTURE_GEN_Q

See Also

  glTexEnv, glTexImage1D, glTexImage2D,	glTexParameter

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


glTexImage1D.3gl

Name

  glTexImage1D - specify a one-dimensional texture image

C Specification

  void glTexImage1D( GLenum target,
             GLint level,
             GLint components,
             GLsizei width,
             GLint border,
             GLenum format,
             GLenum type,
             const GLvoid *pixels )

Parameters

  target      Specifies	the target texture.  Must be GL_TEXTURE_1D.

  level	      Specifies	the level-of-detail number.  Level 0 is	the base
          image level.  Level n is the nth mipmap reduction	image.

  components  Specifies	the number of color components in the texture.	Must
          be 1, 2, 3, or 4.

                                                                  n
  width	      Specifies	the width of the texture image.	 Must be 2 +2(border)
          for some integer n.  The height of the texture image is 1.

  border      Specifies	the width of the border.  Must be either 0 or 1.

  format      Specifies	the format of the pixel	data.  The following symbolic
          values are accepted: GL_COLOR_INDEX, GL_RED, GL_GREEN, GL_BLUE,
          GL_ALPHA,	GL_RGB,	GL_RGBA, GL_LUMINANCE, and
          GL_LUMINANCE_ALPHA.

  type	      Specifies	the data type of the pixel data.  The following
          symbolic values are accepted: GL_UNSIGNED_BYTE, GL_BYTE,
          GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT,
          GL_INT, and GL_FLOAT.

  pixels      Specifies	a pointer to the image data in memory.

Description

  Texturing maps a portion of a	specified texture image	onto each graphical
  primitive for	which texturing	is enabled.  One-dimensional texturing is
  enabled and disabled using glEnable and glDisable with argument
  GL_TEXTURE_1D.

  Texture images are defined with glTexImage1D.	 The arguments describe	the
  parameters of	the texture image, such	as width, width	of the border,
  level-of-detail number (see glTexParameter), and number of color components
  provided.  The last three arguments describe the way the image is
  represented in memory, and they are identical	to the pixel formats used for
  glDrawPixels.

  Data is read from pixels as a	sequence of signed or unsigned bytes, shorts,
  or longs, or single-precision	floating-point values, depending on type.
  These	values are grouped into	sets of	one, two, three, or four values,
  depending on format, to form elements. If type is GL_BITMAP, the data	is
  considered as	a string of unsigned bytes (and	format must be
  GL_COLOR_INDEX). Each	data byte is treated as	eight 1-bit elements, with
  bit ordering determined by GL_UNPACK_LSB_FIRST (see glPixelStore).

  format determines the	composition of each element in pixels.	It can assume
  one of nine symbolic values:

  GL_COLOR_INDEX
        Each element is a single value, a color index. It is converted to
        fixed point	(with an unspecified number of zero bits to the	right
        of the binary point), shifted left or right	depending on the
        value and sign of GL_INDEX_SHIFT, and added	to GL_INDEX_OFFSET
        (see glPixelTransfer). The resulting index is converted to a set
        of color components	using the GL_PIXEL_MAP_I_TO_R,
        GL_PIXEL_MAP_I_TO_G, GL_PIXEL_MAP_I_TO_B, and GL_PIXEL_MAP_I_TO_A
        tables, and	clamped	to the range [0,1].

  GL_RED    Each element is a single red component. It is converted to
        floating point and assembled into an RGBA element by attaching
        0.0	for green and blue, and	1.0 for	alpha. Each component is then
        multiplied by the signed scale factor GL_c_SCALE, added to the
        signed bias	GL_c_BIAS, and clamped to the range [0,1] (see
        glPixelTransfer).

  GL_GREEN  Each element is a single green component. It is converted to
        floating point and assembled into an RGBA element by attaching
        0.0	for red	and blue, and 1.0 for alpha. Each component is then
        multiplied by the signed scale factor GL_c_SCALE, added to the
        signed bias	GL_c_BIAS, and clamped to the range [0,1] (see
        glPixelTransfer).

  GL_BLUE   Each element is a single blue component. It	is converted to
        floating point and assembled into an RGBA element by attaching
        0.0	for red	and green, and 1.0 for alpha. Each component is	then
        multiplied by the signed scale factor GL_c_SCALE, added to the
        signed bias	GL_c_BIAS, and clamped to the range [0,1] (see
        glPixelTransfer).

  GL_ALPHA  Each element is a single alpha component. It is converted to
        floating point and assembled into an RGBA element by attaching
        0.0	for red, green,	and blue.  Each	component is then multiplied
        by the signed scale	factor GL_c_SCALE, added to the	signed bias
        GL_c_BIAS, and clamped to the range	[0,1] (see glPixelTransfer).

  GL_RGB    Each element is an RGB triple.  It is converted to floating	point
        and	assembled into an RGBA element by attaching 1.0	for alpha.
        Each component is then multiplied by the signed scale factor
        GL_c_SCALE,	added to the signed bias GL_c_BIAS, and	clamped	to
        the	range [0,1] (see  glPixelTransfer).

  GL_RGBA   Each element is a complete RGBA element.  It is converted to
        floating point.  Each component is then multiplied by the signed
        scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and
        clamped to the range [0,1] (see glPixelTransfer).

  GL_LUMINANCE
        Each element is a single luminance value.  It is converted to
        floating point, then assembled into	an RGBA	element	by
        replicating	the luminance value three times	for red, green,	and
        blue and attaching 1.0 for alpha. Each component is	then
        multiplied by the signed scale factor GL_c_SCALE, added to the
        signed bias	GL_c_BIAS, and clamped to the range [0,1] (see
        glPixelTransfer).

  GL_LUMINANCE_ALPHA
        Each element is a luminance/alpha pair.  It	is converted to
        floating point, then assembled into	an RGBA	element	by
        replicating	the luminance value three times	for red, green,	and
        blue.  Each	component is then multiplied by	the signed scale
        factor GL_c_SCALE, added to	the signed bias	GL_c_BIAS, and
        clamped to the range [0,1] (see glPixelTransfer).

  A texture image can have up to four components per texture element,
  depending on components.  A one-component texture image uses only the	red
  component of the RGBA	color extracted	from pixels. A two-component image
  uses the R and A values.  A three-component image uses the R,	G, and B
  values.  A four-component image uses all of the RGBA components.

Notes

  Texturing has	no effect in color index mode.

  The texture image can	be represented by the same data	formats	as the pixels
  in a glDrawPixels command, except that GL_STENCIL_INDEX and
  GL_DEPTH_COMPONENT cannot be used.  glPixelStore and glPixelTransfer modes
  affect texture images	in exactly the way they	affect glDrawPixels.

  A texture image with zero width indicates the	null texture.  If the null
  texture is specified for level-of-detail 0, it is as if texturing were
  disabled.

Errors

  GL_INVALID_ENUM is generated when target is not GL_TEXTURE_1D.

  GL_INVALID_ENUM is generated when format is not an accepted format
  constant.  Format constants other than GL_STENCIL_INDEX and
  GL_DEPTH_COMPONENT are accepted.

  GL_INVALID_ENUM is generated when type is not	a type constant.

  GL_INVALID_ENUM is generated if type is GL_BITMAP and	format is not
  GL_COLOR_INDEX.

  GL_INVALID_VALUE is generated	if level is less than zero or greater than
  log max, where max is	the returned value of GL_MAX_TEXTURE_SIZE.
     2
  GL_INVALID_VALUE is generated	if components is not 1, 2, 3, or 4.

  GL_INVALID_VALUE is generated	if width is less than zero or greater than
                                                              n
  2 + GL_MAX_TEXTURE_SIZE, or if it cannot be represented as 2 +2(border) for
  some integer value of	n.

  GL_INVALID_VALUE is generated	if border is not 0 or 1.

  GL_INVALID_OPERATION is generated if glTexImage1D is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGetTexImage
  glIsEnabled with argument GL_TEXTURE_1D

See Also

  glDrawPixels,	glFog, glPixelStore, glPixelTransfer, glTexEnv,	glTexGen,
  glTexImage2D,	glTexParameter

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


glTexImage2D.3gl

Name

  glTexImage2D - specify a two-dimensional texture image

C Specification

  void glTexImage2D( GLenum target,
             GLint level,
             GLint components,
             GLsizei width,
             GLsizei height,
             GLint border,
             GLenum format,
             GLenum type,
             const GLvoid *pixels )

Parameters

  target      Specifies	the target texture.  Must be GL_TEXTURE_2D.

  level	      Specifies	the level-of-detail number.  Level 0 is	the base
          image level.  Level n is the nth mipmap reduction	image.

  components  Specifies	the number of color components in the texture.	Must
          be 1, 2, 3, or 4.

                                                                  n
  width	      Specifies the width of the texture image.  Must be 2 +2(border)
              for some integer n.

  height      Specifies	the height of the texture image.  Must be
           m
          2 +2(border) for some integer m.

  border      Specifies	the width of the border.  Must be either 0 or 1.

  format      Specifies	the format of the pixel	data.  The following symbolic
          values are accepted: GL_COLOR_INDEX, GL_RED, GL_GREEN, GL_BLUE,
          GL_ALPHA,	GL_RGB,	GL_RGBA, GL_LUMINANCE, and
          GL_LUMINANCE_ALPHA.

  type	      Specifies	the data type of the pixel data.  The following
          symbolic values are accepted: GL_UNSIGNED_BYTE, GL_BYTE,
          GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT,
          GL_INT, and GL_FLOAT.

  pixels      Specifies	a pointer to the image data in memory.

Description

  Texturing maps a portion of a	specified texture image	onto each graphical
  primitive for	which texturing	is enabled.  Two-dimensional texturing is
  enabled and disabled using glEnable and glDisable with argument
  GL_TEXTURE_2D.

  Texture images are defined with glTexImage2D.	The arguments describe the
  parameters of	the texture image, such	as height, width, width	of the
  border, level-of-detail number (see glTexParameter), and number of color
  components provided.	The last three arguments describe the way the image
  is represented in memory, and	they are identical to the pixel	formats	used
  for glDrawPixels.

  Data is read from pixels as a	sequence of signed or unsigned bytes, shorts,
  or longs, or single-precision	floating-point values, depending on type.
  These	values are grouped into	sets of	one, two, three, or four values,
  depending on format, to form elements. If type is GL_BITMAP, the data	is
  considered as	a string of unsigned bytes (and	format must be
  GL_COLOR_INDEX). Each	data byte is treated as	eight 1-bit elements, with
  bit ordering determined by GL_UNPACK_LSB_FIRST (see glPixelStore).

  format determines the	composition of each element in pixels.	It can assume
  one of nine symbolic values:

  GL_COLOR_INDEX
        Each element is a single value, a color index. It is converted to
        fixed point	(with an unspecified number of zero bits to the	right
        of the binary point), shifted left or right	depending on the
        value and sign of GL_INDEX_SHIFT, and added	to GL_INDEX_OFFSET
        (see glPixelTransfer). The resulting index is converted to a set
        of color components	using the GL_PIXEL_MAP_I_TO_R,
        GL_PIXEL_MAP_I_TO_G, GL_PIXEL_MAP_I_TO_B, and GL_PIXEL_MAP_I_TO_A
        tables, and	clamped	to the range [0,1].

  GL_RED    Each element is a single red component. It is converted to
        floating point and assembled into an RGBA element by attaching
        0.0	for green and blue, and	1.0 for	alpha. Each component is then
        multiplied by the signed scale factor GL_c_SCALE, added to the
        signed bias	GL_c_BIAS, and clamped to the range [0,1] (see
        glPixelTransfer).

  GL_GREEN  Each element is a single green component. It is converted to
        floating point and assembled into an RGBA element by attaching
        0.0	for red	and blue, and 1.0 for alpha. Each component is then
        multiplied by the signed scale factor GL_c_SCALE, added to the
        signed bias	GL_c_BIAS, and clamped to the range [0,1] (see
        glPixelTransfer).

  GL_BLUE   Each element is a single blue component. It	is converted to
        floating point and assembled into an RGBA element by attaching
        0.0	for red	and green, and 1.0 for alpha. Each component is	then
        multiplied by the signed scale factor GL_c_SCALE, added to the
        signed bias	GL_c_BIAS, and clamped to the range [0,1] (see
        glPixelTransfer).

  GL_ALPHA  Each element is a single alpha component. It is converted to
        floating point and assembled into an RGBA element by attaching
        0.0	for red, green,	and blue.  Each	component is then multiplied
        by the signed scale	factor GL_c_SCALE, added to the	signed bias
        GL_c_BIAS, and clamped to the range	[0,1] (see glPixelTransfer).

  GL_RGB    Each element is an RGB triple.  It is converted to floating	point
        and	assembled into an RGBA element by attaching 1.0	for alpha.
        Each component is then multiplied by the signed scale factor
        GL_c_SCALE,	added to the signed bias GL_c_BIAS, and	clamped	to
        the	range [0,1] (see  glPixelTransfer).

  GL_RGBA   Each element is a complete RGBA element.  It is converted to
        floating point.  Each component is then multiplied by the signed
        scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and
        clamped to the range [0,1] (see glPixelTransfer).

  GL_LUMINANCE
        Each element is a single luminance value.  It is converted to
        floating point, then assembled into	an RGBA	element	by
        replicating	the luminance value three times	for red, green,	and
        blue and attaching 1.0 for alpha. Each component is	then
        multiplied by the signed scale factor GL_c_SCALE, added to the
        signed bias	GL_c_BIAS, and clamped to the range [0,1] (see
        glPixelTransfer).

  GL_LUMINANCE_ALPHA
        Each element is a luminance/alpha pair.  It	is converted to
        floating point, then assembled into	an RGBA	element	by
        replicating	the luminance value three times	for red, green,	and
        blue.  Each	component is then multiplied by	the signed scale
        factor GL_c_SCALE, added to	the signed bias	GL_c_BIAS, and
        clamped to the range [0,1] (see glPixelTransfer).

  Please refer to the glDrawPixels reference page for a	description of the
  acceptable values for	the type parameter.  A texture image can have up to
  four components per texture element, depending on components.	 A one-
  component texture image uses only the	red component of the RGBA color
  extracted from pixels. A two-component image uses the	R and A	values.	 A
  three-component image	uses the R, G, and B values.  A	four-component image
  uses all of the RGBA components.

Notes

  Texturing has	no effect in color index mode.

  The texture image can	be represented by the same data	formats	as the pixels
  in a glDrawPixels command, except that GL_STENCIL_INDEX and
  GL_DEPTH_COMPONENT cannot be used.  glPixelStore and glPixelTransfer modes
  affect texture images	in exactly the way they	affect glDrawPixels.

  A texture image with zero height or width indicates the null texture.	 If
  the null texture is specified	for level-of-detail 0, it is as	if texturing
  were disabled.

Errors

  GL_INVALID_ENUM is generated when target is not GL_TEXTURE_2D.

  GL_INVALID_ENUM is generated when format is not an accepted format
  constant.  Format constants other than GL_STENCIL_INDEX and
  GL_DEPTH_COMPONENT are accepted.

  GL_INVALID_ENUM is generated when type is not	a type constant.

  GL_INVALID_ENUM is generated if type is GL_BITMAP and	format is not
  GL_COLOR_INDEX.

  GL_INVALID_VALUE is generated	if level is less than zero or greater than
  log max, where max is	the returned value of GL_MAX_TEXTURE_SIZE.
     2
  GL_INVALID_VALUE is generated	if components is not 1,	2, 3, or 4.

  GL_INVALID_VALUE is generated	if width or height is less than	zero or
  greater than 2 + GL_MAX_TEXTURE_SIZE,	or if either cannot be represented as
   k
  2 +2(border) for some	integer	value of k.

  GL_INVALID_VALUE is generated	if border is not 0 or 1.

  GL_INVALID_OPERATION is generated if glTexImage2D is executed	between	the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGetTexImage
  glIsEnabled with argument GL_TEXTURE_2D

See Also

  glDrawPixels,	glFog, glPixelStore, glPixelTransfer, glTexEnv,	glTexGen,
  glTexImage1D,	glTexParameter

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


glTexParameter.3gl

Name

  glTexParameterf, glTexParameteri, glTexParameterfv, glTexParameteriv - set
  texture parameters

C Specification

  void glTexParameterf(	GLenum target,
            GLenum pname,
            GLfloat	param )
  void glTexParameteri(	GLenum target,
            GLenum pname,
            GLint param )

Parameters

  target  Specifies the	target texture,	which must be either GL_TEXTURE_1D or
      GL_TEXTURE_2D.

  pname	  Specifies the	symbolic name of a single-valued texture parameter.
      pname	can be one of the following: GL_TEXTURE_MIN_FILTER,
      GL_TEXTURE_MAG_FILTER, GL_TEXTURE_WRAP_S, or GL_TEXTURE_WRAP_T.

  param	  Specifies the	value of pname.

C Specification

  void glTexParameterfv( GLenum	target,
             GLenum	pname,
             const GLfloat *params )
  void glTexParameteriv( GLenum	target,
             GLenum	pname,
             const GLint *params )

Parameters

  target
       Specifies the target texture, which must	be either GL_TEXTURE_1D	or
       GL_TEXTURE_2D.

  pname
       Specifies the symbolic name of a	texture	parameter.  pname can be one
       of the following: GL_TEXTURE_MIN_FILTER,	GL_TEXTURE_MAG_FILTER,
       GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T, or	GL_TEXTURE_BORDER_COLOR.

  params
       Specifies a pointer to an array where the value or values of pname are
       stored.

Description

  Texture mapping is a technique that applies an image onto an object's
  surface as if	the image were a decal or cellophane shrink-wrap. The image
  is created in	texture	space, with an (s, t) coordinate system. A texture is
  a one- or two-dimensional image and a	set of parameters that determine how
  samples are derived from the image.

  glTexParameter assigns the value or values in	params to the texture
  parameter specified as pname.	target defines the target texture, either
  GL_TEXTURE_1D	or GL_TEXTURE_2D.  The following symbols are accepted in
  pname:

  GL_TEXTURE_MIN_FILTER
        The	texture	minifying function is used whenever the	pixel being
        textured maps to an	area greater than one texture element. There
        are	six defined minifying functions.  Two of them use the nearest
        one	or nearest four	texture	elements to compute the	texture
        value. The other four use mipmaps.

        A mipmap is	an ordered set of arrays representing the same image
        at progressively lower resolutions.	If the texture has dimensions
        2nx2m there	are max(n,m)+1 mipmaps.	The first mipmap is the
        original texture, with dimensions 2nx2m. Each subsequent mipmap
        has	dimensions 2k-1x2l-1 where 2kx2l are the dimensions of the
        previous mipmap, until either k=0 or l=0.  At that point,
        subsequent mipmaps have dimension 1x2l-1 or	2k-1x1 until the
        final mipmap, which	has dimension 1x1. Mipmaps are defined using
        glTexImage1D or glTexImage2D with the level-of-detail argument
        indicating the order of the	mipmaps.  Level	0 is the original
        texture; level max(n,m) is the final 1x1 mipmap.

        params supplies a function for minifying the texture as one	of
        the	following:

        GL_NEAREST
              Returns the value	of the texture element that is
              nearest (in Manhattan distance) to the center of the
              pixel being textured.

        GL_LINEAR Returns the weighted average of the four texture
              elements that are	closest	to the center of the pixel
              being textured.  These can include border	texture
              elements,	depending on the values	of GL_TEXTURE_WRAP_S
              and GL_TEXTURE_WRAP_T, and on the	exact mapping.

        GL_NEAREST_MIPMAP_NEAREST
              Chooses the mipmap that most closely matches the size
              of the pixel being textured and uses the GL_NEAREST
              criterion	(the texture element nearest to	the center of
              the pixel) to produce a texture value.

        GL_LINEAR_MIPMAP_NEAREST
              Chooses the mipmap that most closely matches the size
              of the pixel being textured and uses the GL_LINEAR
              criterion	(a weighted average of the four	texture
              elements that are	closest	to the center of the pixel)
              to produce a texture value.

        GL_NEAREST_MIPMAP_LINEAR
              Chooses the two mipmaps that most	closely	match the
              size of the pixel	being textured and uses	the
              GL_NEAREST criterion (the	texture	element	nearest	to
              the center of the	pixel) to produce a texture value
              from each	mipmap.	The final texture value	is a weighted
              average of those two values.

        GL_LINEAR_MIPMAP_LINEAR
              Chooses the two mipmaps that most	closely	match the
              size of the pixel	being textured and uses	the GL_LINEAR
              criterion	(a weighted average of the four	texture
              elements that are	closest	to the center of the pixel)
              to produce a texture value from each mipmap.  The	final
              texture value is a weighted average of those two
              values.

        As more texture elements are sampled in the	minification process,
        fewer aliasing artifacts will be apparent. While the GL_NEAREST
        and	GL_LINEAR minification functions can be	faster than the	other
        four, they sample only one or four texture elements	to determine
        the	texture	value of the pixel being rendered and can produce
        moire patterns or ragged transitions. The default value of
        GL_TEXTURE_MIN_FILTER is GL_NEAREST_MIPMAP_LINEAR.

  GL_TEXTURE_MAG_FILTER
        The	texture	magnification function is used when the	pixel being
        textured maps to an	area less than or equal	to one texture
        element.  It sets the texture magnification	function to either of
        the	following:

        GL_NEAREST
              Returns the value	of the texture element that is
              nearest (in Manhattan distance) to the center of the
              pixel being textured.

              GL_LINEAR	Returns	the weighted average of	the four
              texture elements that are	closest	to the center of the
              pixel being textured.  These can include border texture
              elements,	depending on the values	of GL_TEXTURE_WRAP_S
              and GL_TEXTURE_WRAP_T, and on the	exact mapping.

        GL_NEAREST is generally faster than	GL_LINEAR, but it can produce
        textured images with sharper edges because the transition between
        texture elements is	not as smooth. The default value of
        GL_TEXTURE_MAG_FILTER is GL_LINEAR.

  GL_TEXTURE_WRAP_S
        Sets the wrap parameter for	texture	coordinate s to	either
        GL_CLAMP or	GL_REPEAT.  GL_CLAMP causes s coordinates to be
        clamped to the range [0,1] and is useful for preventing wrapping
        artifacts when mapping a single image onto an object. GL_REPEAT
        causes the integer part of the s coordinate	to be ignored; the GL
        uses only the fractional part, thereby creating a repeating
        pattern. Border texture elements are accessed only if wrapping is
        set	to GL_CLAMP.  Initially, GL_TEXTURE_WRAP_S is set to
        GL_REPEAT.

  GL_TEXTURE_WRAP_T
        Sets the wrap parameter for	texture	coordinate t to	either
        GL_CLAMP or	GL_REPEAT.  See	the discussion under
        GL_TEXTURE_WRAP_S. Initially, GL_TEXTURE_WRAP_T is set to
        GL_REPEAT.

  GL_TEXTURE_BORDER_COLOR
        Sets a border color.  params contains four values that comprise
        the	RGBA color of the texture border. Integer color	components
        are	interpreted linearly such that the most	positive integer maps
        to 1.0, and	the most negative integer maps to -1.0.	 The values
        are	clamped	to the range [0,1] when	they are specified.
        Initially, the border color	is (0, 0, 0, 0).

Notes

  Suppose texturing is enabled (by calling glEnable with argument
  GL_TEXTURE_1D	or GL_TEXTURE_2D) and GL_TEXTURE_MIN_FILTER is set to one of
  the functions	that requires a	mipmap.	 If either the dimensions of the
  texture images currently defined (with previous calls	to glTexImage1D	or
  glTexImage2D)	do not follow the proper sequence for mipmaps (described
  above), or there are fewer texture images defined than are needed, or	the
  set of texture images	have differing numbers of texture components, then it
  is as	if texture mapping were	disabled.

  Linear filtering accesses the	four nearest texture elements only in 2-D
  textures.  In	1-D textures, linear filtering accesses	the two	nearest
  texture elements.

Errors

  GL_INVALID_ENUM is generated when target or pname is not one of the
  accepted defined values, or when params should have a	defined	constant
  value	(based on the value of pname) and does not.

  GL_INVALID_OPERATION is generated if glTexParameter is called	between	a
  call to glBegin and the corresponding	call to	glEnd.

Associated Gets

  glGetTexParameter
  glGetTexLevelParameter

See Also

  glTexEnv, glTexImage1D, glTexImage2D,	glTexGen

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glTranslate.3gl

Name

  glTranslated,	glTranslatef - multiply	the current matrix by a	translation
  matrix

C Specification

  void glTranslated( GLdouble x,
             GLdouble y,
             GLdouble z	)
  void glTranslatef( GLfloat x,
             GLfloat y,
             GLfloat z )

Parameters

  x, y,	z
     Specify the x,	y, and z coordinates of	a translation vector.

Description

  glTranslate moves the	coordinate system origin to the	point specified	by
  (x,y,z).  The translation vector is used to compute a	4x4 translation
  matrix:

                | 1	   0	0    x	 |
                |			 |
                | 0	   1	0    y	 |
                |			 |
                | 0	   0	1    z	 |
                |			 |
                | 0	   0	0    1	 |

  The current matrix (see glMatrixMode)	is multiplied by this translation
  matrix, with the product replacing the current matrix.  That is, if M	is
  the current matrix and T is the translation matrix, then M is	replaced with
  M · T.

  If the matrix	mode is	either GL_MODELVIEW or GL_PROJECTION, all objects
  drawn	after glTranslate is called are	translated.  Use glPushMatrix and
  glPopMatrix to save and restore the untranslated coordinate system.

Errors

  GL_INVALID_OPERATION is generated if glTranslate is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_MATRIX_MODE
  glGet	with argument GL_MODELVIEW_MATRIX
  glGet	with argument GL_PROJECTION_MATRIX
  glGet	with argument GL_TEXTURE_MATRIX

See Also

  glMatrixMode,	glMultMatrix, glPushMatrix, glRotate, glScale

Introduction | Alphabetic | Specification

Last Edited: Tue, May 23, 1995

AFV


gluBeginCurve.3gl

Name

  gluBeginCurve, gluEndCurve - delimit a NURBS curve definition

C Specification

  void gluBeginCurve( GLUnurbsObj *nobj	)

  void gluEndCurve( GLUnurbsObj	*nobj )

Parameters

  nobj	Specifies the NURBS object (created with gluNewNurbsRenderer).

Description

  Use gluBeginCurve to mark the	beginning of a NURBS curve definition.	After
  calling gluBeginCurve, make one or more calls	to gluNurbsCurve to define
  the attributes of the	curve.	Exactly	one of the calls to gluNurbsCurve
  must have a curve type of GL_MAP1_VERTEX_3 or	GL_MAP1_VERTEX_4.  To mark
  the end of the NURBS curve definition, call gluEndCurve.

  OpenGL evaluators are	used to	render the NURBS curve as a series of line
  segments.  Evaluator state is	preserved during rendering with
  glPushAttrib(GL_EVAL_BIT) and	glPopAttrib(). See the glPushAttrib reference
  page for details on exactly what state these calls preserve.

Example

  The following	commands render	a textured NURBS curve with normals; texture
  coordinates and normals are also specified as	NURBS curves:

  gluBeginCurve(nobj);
     gluNurbsCurve(nobj, ..., GL_MAP1_TEXTURE_COORD_2);
     gluNurbsCurve(nobj, ..., GL_MAP1_NORMAL);
     gluNurbsCurve(nobj, ..., GL_MAP1_VERTEX_4); gluEndCurve(nobj);

See Also

  gluBeginSurface, gluBeginTrim, gluNewNurbsRenderer, gluNurbsCurve,
  glPopAttrib, glPushAttrib

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluBeginPolygon.3gl

gluBeginPolygon.3gl (Obsolete in GLU version 1.2 and later)

Name

  gluBeginPolygon, gluEndPolygon - delimit a polygon description

C Specification

  void gluBeginPolygon(	GLUtesselator *tess )

  void gluEndPolygon( GLUtesselator *tess )

Parameters

  tess	Specifies the tessellation object (created with	gluNewTess).

Description

  gluBeginPolygon and gluEndPolygon delimit the	definition of a	nonconvex
  polygon.  To define such a polygon, first call gluBeginPolygon.  Then
  define the contours of the polygon by	calling	gluTessVertex for each vertex
  and gluNextContour to	start each new contour.	 Finally, call gluEndPolygon
  to signal the	end of the definition.	See the	gluTessVertex and
  gluNextContour reference pages for more details.

  Once gluEndPolygon is	called,	the polygon is tessellated, and	the resulting
  triangles are	described through callbacks.  See gluTessCallback for
  descriptions of the callback functions.

Notes

  As of GLU version 1.2, this command is obsolete and is provided for backwards
  compatibility only. GLU version 1.2 (and later) can be distinguished during
  compilation by checking if the pre-processor symbol GLU_VERSION_1_2 is defined.

  Calls to gluBeginPolygon are mapped to gluTessBeginPolygon followed by
  gluTessBeginContour. Calls to gluEndPolygon are mapped to gluTessEndContour
  followed by gluTessEndPolygon.

Example

  A quadrilateral with a triangular hole in it can be described	like this:

  gluBeginPolygon(tobj);
     gluTessVertex(tobj, v1, v1);
     gluTessVertex(tobj, v2, v2);
     gluTessVertex(tobj, v3, v3);
     gluTessVertex(tobj, v4, v4);
  gluNextContour(tobj, GLU_INTERIOR);
     gluTessVertex(tobj, v5, v5);
     gluTessVertex(tobj, v6, v6);
     gluTessVertex(tobj, v7, v7);
  gluEndPolygon(tobj);

See Also

  gluNewTess, gluNextContour, gluTessCallback, gluTessVertex,
  gluTessBeginPolygon, gluTessBeginContour

Introduction | Alphabetic | Specification

Last Edited: Thu, May 25, 1995

AFV


gluBeginPolygon.3gl

gluBeginPolygon.3gl (GLU versions 1.0 and 1.1)

Name

  gluBeginPolygon, gluEndPolygon - delimit a polygon description

C Specification

  void gluBeginPolygon(	GLUtriangulatorObj *tobj )

  void gluEndPolygon( GLUtriangulatorObj *tobj )

Parameters

  tobj	Specifies the tessellation object (created with	gluNewTess).

Description

  gluBeginPolygon and gluEndPolygon delimit the	definition of a	nonconvex
  polygon.  To define such a polygon, first call gluBeginPolygon.  Then
  define the contours of the polygon by	calling	gluTessVertex for each vertex
  and gluNextContour to	start each new contour.	 Finally, call gluEndPolygon
  to signal the	end of the definition.	See the	gluTessVertex and
  gluNextContour reference pages for more details.

  Once gluEndPolygon is	called,	the polygon is tessellated, and	the resulting
  triangles are	described through callbacks.  See gluTessCallback for
  descriptions of the callback functions.

Example

  A quadrilateral with a triangular hole in it can be described	like this:

  gluBeginPolygon(tobj);
     gluTessVertex(tobj, v1, v1);
     gluTessVertex(tobj, v2, v2);
     gluTessVertex(tobj, v3, v3);
     gluTessVertex(tobj, v4, v4);
  gluNextContour(tobj, GLU_INTERIOR);
     gluTessVertex(tobj, v5, v5);
     gluTessVertex(tobj, v6, v6);
     gluTessVertex(tobj, v7, v7);
  gluEndPolygon(tobj);

See Also

  gluNewTess, gluNextContour, gluTessCallback, gluTessVertex

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


gluBeginSurface.3gl

Name

  gluBeginSurface, gluEndSurface - delimit a NURBS surface definition

C Specification

  void gluBeginSurface(	GLUnurbsObj *nobj )

  void gluEndSurface( GLUnurbsObj *nobj	)

Parameters

  nobj	Specifies the NURBS object (created with gluNewNurbsRenderer).

Description

  Use gluBeginSurface to mark the beginning of a NURBS surface definition.
  After	calling	gluBeginSurface, make one or more calls	to gluNurbsSurface to
  define the attributes	of the surface.	 Exactly one of	these calls to
  gluNurbsSurface must have a surface type of GL_MAP2_VERTEX_3 or
  GL_MAP2_VERTEX_4.  To	mark the end of	the NURBS surface definition, call
  gluEndSurface.

  Trimming of NURBS surfaces is	supported with gluBeginTrim, gluPwlCurve,
  gluNurbsCurve, and gluEndTrim.  Refer	to the gluBeginTrim reference page
  for details.

  OpenGL evaluators are	used to	render the NURBS surface as a set of
  polygons.  Evaluator state is	preserved during rendering with
  glPushAttrib(GL_EVAL_BIT) and	glPopAttrib(). See the glPushAttrib reference
  page for details on exactly what state these calls preserve.

Example

  The following	commands render	a textured NURBS surface with normals; the
  texture coordinates and normals are also described as	NURBS surfaces:

  gluBeginSurface(nobj);
     gluNurbsSurface(nobj, ...,	GL_MAP2_TEXTURE_COORD_2);
     gluNurbsSurface(nobj, ...,	GL_MAP2_NORMAL);
     gluNurbsSurface(nobj, ...,	GL_MAP2_VERTEX_4); gluEndSurface(nobj);

See Also

  gluBeginCurve, gluBeginTrim, gluNewNurbsRenderer, gluNurbsCurve,
  gluNurbsSurface, gluPwlCurve

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluBeginTrim.3gl

Name

  gluBeginTrim,	gluEndTrim - delimit a NURBS trimming loop definition

C Specification

  void gluBeginTrim( GLUnurbsObj *nobj )

  void gluEndTrim( GLUnurbsObj *nobj )

Parameters

  nobj	Specifies the NURBS object (created with gluNewNurbsRenderer).

Description

  Use gluBeginTrim to mark the beginning of a trimming loop, and gluEndTrim
  to mark the end of a trimming	loop.  A trimming loop is a set	of oriented
  curve	segments (forming a closed curve) that define boundaries of a NURBS
  surface.  You	include	these trimming loops in	the definition of a NURBS
  surface, between calls to gluBeginSurface and	gluEndSurface.

  The definition for a NURBS surface can contain many trimming loops.  For
  example, if you wrote	a definition for a NURBS surface that resembled	a
  rectangle with a hole	punched	out, the definition would contain two
  trimming loops.  One loop would define the outer edge	of the rectangle;
  the other would define the hole punched out of the rectangle.	 The
  definitions of each of these trimming	loops would be bracketed by a
  gluBeginTrim/gluEndTrim pair.

  The definition of a single closed trimming loop can consist of multiple
  curve	segments, each described as a piecewise	linear curve (see
  gluPwlCurve) or as a single NURBS curve (see gluNurbsCurve), or as a
  combination of both in any order.  The only library calls that can appear
  in a trimming	loop definition	(between the calls to gluBeginTrim and
  gluEndTrim) are gluPwlCurve and gluNurbsCurve.

  The area of the NURBS	surface	that is	displayed is the region	in the domain
  to the left of the trimming curve as the curve parameter increases.  Thus,
  the retained region of the NURBS surface is inside a counterclockwise
  trimming loop	and outside a clockwise	trimming loop.	For the	rectangle
  mentioned earlier, the trimming loop for the outer edge of the rectangle
  runs counterclockwise, while the trimming loop for the punched-out hole
  runs clockwise.

  If you use more than one curve to define a single trimming loop, the curve
  segments must	form a closed loop (that is, the endpoint of each curve	must
  be the starting point	of the next curve, and the endpoint of the final
  curve	must be	the starting point of the first	curve).	 If the	endpoints of
  the curve are	sufficiently close together but	not exactly coincident,	they
  will be coerced to match.  If	the endpoints are not sufficiently close, an
  error	results	(see gluNurbsCallback).

  If a trimming	loop definition	contains multiple curves, the direction	of
  the curves must be consistent	(that is, the inside must be to	the left of
  all of the curves).  Nested trimming loops are legal as long as the curve
  orientations alternate correctly.  Trimming curves cannot be self-
  intersecting,	nor can	they intersect one another (or an error	results).

  If no	trimming information is	given for a NURBS surface, the entire surface
  is drawn.

Example

  This code fragment defines a trimming	loop that consists of one piecewise
  linear curve,	and two	NURBS curves:

  gluBeginTrim(nobj);
     gluPwlCurve(..., GLU_MAP1_TRIM_2);
     gluNurbsCurve(...,	GLU_MAP1_TRIM_2);
     gluNurbsCurve(...,	GLU_MAP1_TRIM_3); gluEndTrim(nobj);

See Also

  gluBeginSurface, gluNewNurbsRenderer,	gluNurbsCallback, gluNurbsCurve,
  gluPwlCurve

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluBuild1DMipmaps.3gl

Name

  gluBuild1DMipmaps - create 1-D mipmaps

C Specification

  int gluBuild1DMipmaps( GLenum	target,
             GLint components,
             GLint width,
             GLenum	format,
             GLenum	type,
             const void *data )

Parameters

  target      Specifies	the target texture. Must be GL_TEXTURE_1D.

  components  Specifies	the number of color components in the texture.	Must
          be 1, 2, 3, or 4.

  width	      Specifies	the width of the texture image.

  format      Specifies	the format of the pixel	data.  Must be one of
          GL_COLOR_INDEX, GL_RED, GL_GREEN,	GL_BLUE, GL_ALPHA, GL_RGB,
          GL_RGBA, GL_LUMINANCE, and GL_LUMINANCE_ALPHA.

  type	      Specifies	the data type for data.	 Must be one of
          GL_UNSIGNED_BYTE,	GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT,
          GL_SHORT,	GL_UNSIGNED_INT, GL_INT, or GL_FLOAT.

  data	      Specifies	a pointer to the image data in memory.

Description

  gluBuild1DMipmaps obtains the	input image and	generates all mipmap images
  (using gluScaleImage)	so that	the input image	can be used as a mipmapped
  texture image.  glTexImage1D is then called to load each of the images.  If
  the width of the input image is not a	power of two, then the image is
  scaled to the	nearest	power of two before the	mipmaps	are generated.

  A return value of zero indicates success. Otherwise, a GLU error code	is
  returned (see	gluErrorString).

  Please refer to the glTexImage1D reference page for a	description of the
  acceptable values for	the format parameter. See the glDrawPixels reference
  page for a description of the	acceptable values for the type parameter.

See Also

  glTexImage1D,	gluBuild2DMipmaps, gluErrorString, gluScaleImage

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluBuild2DMipmaps.3gl

Name

  gluBuild2DMipmaps - create 2-D mipmaps

C Specification

  int gluBuild2DMipmaps( GLenum	target,
             GLint components,
             GLint width,
             GLint height,
             GLenum	format,
             GLenum	type,
             const void *data )

Parameters

  target      Specifies	the target texture.  Must be GL_TEXTURE_2D.

  components  Specifies	the number of color components in the texture.	Must
          be 1, 2, 3, or 4.

  width, height
          Specifies	the width and height, respectively, of the texture
          image.

  format      Specifies	the format of the pixel	data.  Must be one of:
          GL_COLOR_INDEX, GL_RED, GL_GREEN,	GL_BLUE, GL_ALPHA, GL_RGB,
          GL_RGBA, GL_LUMINANCE, and GL_LUMINANCE_ALPHA.

  type	      Specifies	the data type for data.	 Must be one of:
          GL_UNSIGNED_BYTE,	GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT,
          GL_SHORT,	GL_UNSIGNED_INT, GL_INT, or GL_FLOAT.

  data	      Specifies	a pointer to the image data in memory.

Description

  gluBuild2DMipmaps obtains the	input image and	generates all mipmap images
  (using gluScaleImage)	so that	the input image	can be used as a mipmapped
  texture image.  glTexImage2D is then called to load each of the images.  If
  the dimensions of the	input image are	not powers of two, then	the image is
  scaled so that both the width	and height are powers of two before the
  mipmaps are generated.

  A return value of 0 indicates	success. Otherwise, a GLU error	code is
  returned (see	gluErrorString).

  Please refer to the glTexImage1D reference page for a	description of the
  acceptable values for	the format parameter. See the glDrawPixels reference
  page for a description of the	acceptable values for the type parameter.

See Also

  glDrawPixels,	glTexImage1D, glTexImage2D, gluBuild1DMipmaps,
  gluErrorString, gluScaleImage

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluCylinder.3gl

Name

  gluCylinder -	draw a cylinder

C Specification

  void gluCylinder( GLUquadricObj *qobj,
            GLdouble baseRadius,
            GLdouble topRadius,
            GLdouble height,
            GLint slices,
            GLint stacks )

Parameters

  qobj	      Specifies	the quadrics object (created with gluNewQuadric).

  baseRadius  Specifies	the radius of the cylinder at z	= 0.

  topRadius   Specifies	the radius of the cylinder at z	= height.

  height      Specifies	the height of the cylinder.

  slices      Specifies	the number of subdivisions around the z	axis.

  stacks      Specifies	the number of subdivisions along the z axis.

Description

  gluCylinder draws a cylinder oriented	along the z axis.  The base of the
  cylinder is placed at	 z = 0,	and the	top at z=height.  Like a sphere, a
  cylinder is subdivided around	the z axis into	slices,	and along the z	axis
  into stacks.

  Note that if topRadius is set	to zero, then this routine will	generate a
  cone.

  If the orientation is	set to GLU_OUTSIDE (with gluQuadricOrientation), then
  any generated	normals	point away from	the z axis.  Otherwise,	they point
  toward the z axis.

  If texturing is turned on (with gluQuadricTexture), then texture
  coordinates are generated so that t ranges linearly from 0.0 at z = 0	to
  1.0 at z = height, and s ranges from 0.0 at the +y axis, to 0.25 at the +x
  axis,	to 0.5 at the -y axis, to 0.75 at the -x axis, and back	to 1.0 at the
  +y axis.

See Also

  gluDisk, gluNewQuadric, gluPartialDisk, gluQuadricTexture, gluSphere

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluDeleteNurbsRenderer.3gl

Name

  gluDeleteNurbsRenderer - destroy a NURBS object

C Specification

  void gluDeleteNurbsRenderer( GLUnurbsObj *nobj )

Parameters

  nobj	Specifies the NURBS object to be destroyed (created with
    gluNewNurbsRenderer).

Description

  gluDeleteNurbsRenderer destroys the NURBS object and frees any memory	used
  by it.  Once gluDeleteNurbsRenderer has been called, nobj cannot be used
  again.

See Also

  gluNewNurbsRenderer

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluDeleteQuadric.3gl

Name

  gluDeleteQuadric - destroy a quadrics	object

C Specification

  void gluDeleteQuadric( GLUquadricObj *state )

Parameters

  state	 Specifies the quadrics	object to be destroyed (created	with
     gluNewQuadric).

Description

  gluDeleteQuadric destroys the	quadrics object	and frees any memory used by
  it.  Once gluDeleteQuadric has been called, state cannot be used again.

See Also

  gluNewQuadric

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluDeleteTess.3gl

gluDeleteTess.3gl (GLU version 1.2 and later)

Name

  gluDeleteTess	- destroy a tessellation object

C Specification

  void gluDeleteTess( GLUtesselator *tess )

Parameters

  tess	Specifies the tessellation object to destroy (created with
    gluNewTess).

Description

  gluDeleteTess	destroys the indicated tessellation object and frees any
  memory that it used.

See Also

  gluBeginPolygon, gluNewTess, gluTessCallback

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluDeleteTess.3gl

gluDeleteTess.3gl (GLU version 1.0 and 1.1)

Name

  gluDeleteTess	- destroy a tessellation object

C Specification

  void gluDeleteTess( GLUtriangulatorObj *tobj )

Parameters

  tobj	Specifies the tessellation object to destroy (created with
    gluNewTess).

Description

  gluDeleteTess	destroys the indicated tessellation object and frees any
  memory that it used.

See Also

  gluBeginPolygon, gluNewTess, gluTessCallback

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


gluDisk.3gl

Name

  gluDisk - draw a disk

C Specification

  void gluDisk(	GLUquadricObj *qobj,
        GLdouble innerRadius,
        GLdouble outerRadius,
        GLint slices,
        GLint loops )

Parameters

  qobj	       Specifies the quadrics object (created with gluNewQuadric).

  innerRadius  Specifies the inner radius of the disk (may be 0).

  outerRadius  Specifies the outer radius of the disk.

  slices       Specifies the number of subdivisions around the z axis.

  loops	       Specifies the number of concentric rings	about the origin into
           which the disk is subdivided.

Description

  gluDisk renders a disk on the	z = 0  plane.  The disk	has a radius of
  outerRadius, and contains a concentric circular hole with a radius of
  innerRadius.	If innerRadius is 0, then no hole is generated.	 The disk is
  subdivided around the	z axis into slices (like pizza slices),	and also
  about	the z axis into	rings (as specified by slices and loops,
  respectively).

  With respect to orientation, the +z side of the disk is considered to	be
  "outside" (see gluQuadricOrientation).  This means that if the orientation
  is set to GLU_OUTSIDE, then any normals generated point along	the +z axis.
  Otherwise, they point	along the -z axis.

  If texturing is turned on (with gluQuadricTexture), texture coordinates are
  generated linearly such that where r=outerRadius, the	value at (r, 0,	0) is
  (1, 0.5), at (0, r, 0) it is (0.5, 1), at (-r, 0, 0) it is (0, 0.5), and at
  (0, -r, 0) it	is (0.5, 0).

See Also

  gluCylinder, gluNewQuadric, gluPartialDisk, gluQuadricOrientation,
  gluQuadricTexture, gluSphere

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluErrorString.3gl

Name

  gluErrorString - produce an error string from	an OpenGL or GLU error code

C Specification

  const	GLubyte* gluErrorString( GLenum	errorCode )

Parameters

  errorCode  Specifies an OpenGL or GLU	error code.

Description

  gluErrorString produces an error string from an OpenGL or GLU	error code.
  The string is	in an ISO Latin	1 format. For example,
  gluErrorString(GL_OUT_OF_MEMORY) returns the string out of memory.

  The standard GLU error codes are GLU_INVALID_ENUM, GLU_INVALID_VALUE,	and
  GLU_OUT_OF_MEMORY.  Certain other GLU	functions can return specialized
  error	codes through callbacks.  Refer	to the glGetError reference page for
  the list of OpenGL error codes.

See Also

  glGetError, gluNurbsCallback,	gluQuadricCallback, gluTessCallback

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluGetNurbsProperty.3gl

Name

  gluGetNurbsProperty -	get a NURBS property

C Specification

  void gluGetNurbsProperty( GLUnurbsObj	*nobj,
                GLenum property,
                GLfloat *value )

Parameters

  nobj	    Specifies the NURBS	object (created	with gluNewNurbsRenderer).

  property  Specifies the property whose value is to be	fetched.  Valid
        values are GLU_CULLING, GLU_SAMPLING_TOLERANCE, GLU_DISPLAY_MODE,
        GLU_AUTO_LOAD_MATRIX, GLU_PARAMETRIC_TOLERANCE,
        GLU_SAMPLING_METHOD, GLU_U_STEP, and GLU_V_STEP.

  value	    Specifies a	pointer	to the location	into which the value of	the
        named  property is written.

Description

  gluGetNurbsProperty is used to retrieve properties stored in a NURBS
  object.  These properties affect the way that	NURBS curves and surfaces are
  rendered.  Please refer to the gluNurbsProperty reference page for
  information about what the properties	are and	what they do.

See Also

  gluNewNurbsRenderer, gluNurbsProperty

Notes

  GLU_PARAMETRIC_TOLERANCE, GLU_SAMPLING_METHOD, GLU_U_STEP, and GLU_V_STEP are
  additions to GLU starting with GLU version 1.1. This version can be distinguished
  during compilation by checking if the pre-processor symbol GLU_VERSION_1_1 is
  defined.

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluGetTessProperty.3gl

gluGetTessProperty.3gl (GLU version 1.2 and later)

Name

  gluGetTessProperty - get a tessellation object property

C Specification

  void gluGetTessProperty( GLUtesselator *tess,
               GLenum which,
               GLdouble *value )

Parameters

  tess	 Specifies the tessellation object (created with gluNewTess).

  which	 Specifies the property	whose value is to be fetched.  Valid values
     are GLU_TESS_WINDING_RULE, GLU_TESS_BOUNDARY_ONLY, and
     GLU_TESS_TOLERANCE.

  value	 Specifies a pointer to	the location into which	the value of the
     named	property is written.

Description

  gluGetTessProperty is	used to	retrieve properties stored in a	tessellation
  object.  These properties affect the way that	tessellation objects are
  interpreted and rendered.  Please refer to the gluTessProperty reference
  page for information about what the properties are and what they do.

See Also

  gluNewTess, gluTessProperty

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluLoadSamplingMatrices.3gl

Name

  gluLoadSamplingMatrices - load NURBS sampling	and culling matrices

C Specification

  void gluLoadSamplingMatrices(	GLUnurbsObj *nobj,
                const GLfloat modelMatrix[16],
                const GLfloat projMatrix[16],
                const GLint viewport[4]); )

Parameters

  nobj	       Specifies the NURBS object (created with	gluNewNurbsRenderer).

  modelMatrix  Specifies a modelview matrix (as	from a glGetFloatv call).

  projMatrix   Specifies a projection matrix (as from a	glGetFloatv call).

  viewport;    Specifies a viewport (as	from a glGetIntegerv call).

Description

  gluLoadSamplingMatrices uses modelMatrix, projMatrix,	and viewport; to
  recompute the	sampling and culling matrices stored in	nobj.  The sampling
  matrix determines how	finely a NURBS curve or	surface	must be	tessellated
  to satisfy the sampling tolerance (as	determined by the
  GLU_SAMPLING_TOLERANCE property).  The  culling matrix is used in deciding
  if a NURBS curve or surface should be	culled before rendering	(when the
  GLU_CULLING property is turned on).

  gluLoadSamplingMatrices is necessary only if the GLU_AUTO_LOAD_MATRIX
  property is turned off (see gluNurbsProperty).  Although it can be
  convenient to	leave the GLU_AUTO_LOAD_MATRIX property	turned on, there can
  be a performance penalty for doing so.  (A round trip	to the OpenGL server
  is needed to fetch the current values	of the modelview matrix, projection
  matrix, and viewport.)

See Also

  gluGetNurbsProperty, gluNewNurbsRenderer, gluNurbsProperty

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluLookAt.3gl

Name

  gluLookAt - define a viewing transformation

C Specification

  void gluLookAt( GLdouble eyex,
          GLdouble eyey,
          GLdouble eyez,
          GLdouble centerx,
          GLdouble centery,
          GLdouble centerz,
          GLdouble upx,
          GLdouble upy,
          GLdouble upz )

Parameters

  eyex,	eyey, eyez
          Specifies the	position of the	eye point.

  centerx, centery, centerz
          Specifies the	position of the	reference point.

  upx, upy, upz	  Specifies the	direction of the up vector.

Description

  gluLookAt creates a viewing matrix derived from an eye point,	a reference
  point	indicating the center of the scene, and	an up vector.  The matrix
  maps the reference point to the negative z axis and the eye point to the
  origin, so that, when	a typical projection matrix is used, the center	of
  the scene maps to the	center of the viewport.	 Similarly, the	direction
  described by the up vector projected onto the	viewing	plane is mapped	to
  the positive y axis so that it points	upward in the viewport.	 The up
  vector must not be parallel to the line of sight from	the eye	to the
  reference point.

  The matrix generated by gluLookAt postmultiplies the current matrix.

See Also

  glFrustum, gluPerspective

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluNewNurbsRenderer.3gl

Name

  gluNewNurbsRenderer -	create a NURBS object

C Specification

  GLUnurbsObj* gluNewNurbsRenderer( void )

Description

  gluNewNurbsRenderer creates and returns a pointer to a new NURBS object.
  This object must be referred to when calling NURBS rendering and control
  functions.  A	return value of	zero means that	there is not enough memory to
  allocate the object.

See Also

  gluBeginCurve, gluBeginSurface, gluBeginTrim,	gluDeleteNurbsRenderer,
  gluNurbsCallback, gluNurbsProperty

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluNewQuadric.3gl

Name

  gluNewQuadric	- create a quadrics object

C Specification

  GLUquadricObj* gluNewQuadric(	void )

Description

  gluNewQuadric	creates	and returns a pointer to a new quadrics	object.	 This
  object must be referred to when calling quadrics rendering and control
  functions.  A	return value of	zero means that	there is not enough memory to
  allocate the object.

See Also

  gluCylinder, gluDeleteQuadric, gluDisk, gluPartialDisk, gluQuadricCallback,
  gluQuadricDrawStyle, gluQuadricNormals, gluQuadricOrientation,
  gluQuadricTexture, gluSphere

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluNewTess.3gl

gluNewTess.3gl (GLU version 1.2 and later)

Name

  gluNewTess - create a	tessellation object

C Specification

  GLUtesselator* gluNewTess( void )

Description

  gluNewTess creates and returns a pointer to a	new tessellation object.
  This object must be referred to when calling tessellation functions.	A
  return value of zero means that there	is not enough memory to	allocate the
  object.

See Also

  gluTessBeginPolygon, gluDeleteTess, gluTessCallback

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluNewTess.3gl

gluNewTess.3gl (GLU versions 1.0 and 1.1)

Name

  gluNewTess - create a	tessellation object

C Specification

  GLUtriangulatorObj* gluNewTess( void )

Description

  gluNewTess creates and returns a pointer to a	new tessellation object.
  This object must be referred to when calling tessellation functions.	A
  return value of zero means that there	is not enough memory to	allocate the
  object.

See Also

  gluBeginPolygon, gluDeleteTess, gluTessCallback

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


gluNextContour.3gl

gluNextContour.3gl (Obsolete in GLU version 1.2 and later)

Name

  gluNextContour - mark	the beginning of another contour

C Specification

  void gluNextContour( GLUtesselator *tess,
               GLenum type )

Parameters

  tess	Specifies the tessellation object (created with	gluNewTess).

  type	Specifies the type of the contour being	defined.  Valid	values are
    GLU_EXTERIOR, GLU_INTERIOR, GLU_UNKNOWN, GLU_CCW, and GLU_CW.

Description

  gluNextContour is used in describing polygons	with multiple contours.
  After	the first contour has been described through a series of
  gluTessVertex	calls, a gluNextContour	call indicates that the	previous
  contour is complete and that the next	contour	is about to begin.  Another
  series of gluTessVertex calls	is then	used to	describe the new contour.
  This process can be repeated until all contours have been described.

  type defines what type of contour follows.  The legal	contour	types are as
  follows:

  GLU_EXTERIOR	 An exterior contour defines an	exterior boundary of the
         polygon.

  GLU_INTERIOR	 An interior contour defines an	interior boundary of the
         polygon (such as a hole).

  GLU_UNKNOWN	 An unknown contour is analyzed	by the library to determine
         if it is interior or exterior.

  GLU_CCW, GLU_CW
         The first GLU_CCW or GLU_CW contour defined is	considered to
         be exterior.  All other contours are considered to be
         exterior if they are oriented in the same direction
         (clockwise or counterclockwise) as the	first contour, and
         interior if they are not.

         If one	contour	is of type GLU_CCW or GLU_CW, then all
         contours must be of the same type (if they are	not, then all
         GLU_CCW and GLU_CW contours will be changed to	GLU_UNKNOWN).

         Note that there is no real difference between the GLU_CCW
         and GLU_CW contour types.

  gluNextContour can be	called before the first	contour	is described to
  define the type of the first contour.	 If gluNextContour is not called
  before the first contour, then the first contour is marked GLU_EXTERIOR.

Notes

  As of GLU version 1.2, this command is obsolete and is provided for backwards
  compatibility only. GLU version 1.2 (and later) can be distinguished during
  compilation by checking if the pre-processor symbol GLU_VERSION_1_2 is defined.

  Calls	to gluNextContour are mapped to	gluTessEndContour followed by
  gluTessBeginContour.

Example

  A quadrilateral with a triangular hole in it can be described	as follows:

  gluBeginPolygon(tobj);
     gluTessVertex(tobj, v1, v1);
     gluTessVertex(tobj, v2, v2);
     gluTessVertex(tobj, v3, v3);
     gluTessVertex(tobj, v4, v4);
  gluNextContour(tobj, GLU_INTERIOR);
     gluTessVertex(tobj, v5, v5);
     gluTessVertex(tobj, v6, v6);
     gluTessVertex(tobj, v7, v7);
  gluEndPolygon(tobj);

See Also

   gluBeginPolygon, gluNewTess,	gluTessCallback, gluTessVertex,
  gluTessBeginContour

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluNextContour.3gl

gluNextContour.3gl (GLU versions 1.0 and 1.1)

Name

  gluNextContour - mark	the beginning of another contour

C Specification

  void gluNextContour( GLUtriangulatorObj *tobj,
               GLenum type )

Parameters

  tobj	Specifies the tessellation object (created with	gluNewTess).

  type	Specifies the type of the contour being	defined.  Valid	values are
    GLU_EXTERIOR, GLU_INTERIOR, GLU_UNKNOWN, GLU_CCW, and GLU_CW.

Description

  gluNextContour is used in describing polygons	with multiple contours.
  After	the first contour has been described through a series of
  gluTessVertex	calls, a gluNextContour	call indicates that the	previous
  contour is complete and that the next	contour	is about to begin.  Another
  series of gluTessVertex calls	is then	used to	describe the new contour.
  This process can be repeated until all contours have been described.

  type defines what type of contour follows.  The legal	contour	types are as
  follows:

  GLU_EXTERIOR	 An exterior contour defines an	exterior boundary of the
         polygon.

  GLU_INTERIOR	 An interior contour defines an	interior boundary of the
         polygon (such as a hole).

  GLU_UNKNOWN	 An unknown contour is analyzed	by the library to determine
         if it is interior or exterior.

  GLU_CCW, GLU_CW
         The first GLU_CCW or GLU_CW contour defined is	considered to
         be exterior.  All other contours are considered to be
         exterior if they are oriented in the same direction
         (clockwise or counterclockwise) as the	first contour, and
         interior if they are not.

         If one	contour	is of type GLU_CCW or GLU_CW, then all
         contours must be of the same type (if they are	not, then all
         GLU_CCW and GLU_CW contours will be changed to	GLU_UNKNOWN).

         Note that there is no real difference between the GLU_CCW
         and GLU_CW contour types.

  gluNextContour can be	called before the first	contour	is described to
  define the type of the first contour.	 If gluNextContour is not called
  before the first contour, then the first contour is marked GLU_EXTERIOR.

Example

  A quadrilateral with a triangular hole in it can be described	as follows:

  gluBeginPolygon(tobj);
     gluTessVertex(tobj, v1, v1);
     gluTessVertex(tobj, v2, v2);
     gluTessVertex(tobj, v3, v3);
     gluTessVertex(tobj, v4, v4);
  gluNextContour(tobj, GLU_INTERIOR);
     gluTessVertex(tobj, v5, v5);
     gluTessVertex(tobj, v6, v6);
     gluTessVertex(tobj, v7, v7);
  gluEndPolygon(tobj);

See Also

   gluBeginPolygon, gluNewTess,	gluTessCallback, gluTessVertex

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


gluNurbsCallback.3gl

Name

  gluNurbsCallback - define a callback for a NURBS object

C Specification

  void gluNurbsCallback( GLUnurbsObj *nobj,
             GLenum	which,
             void (*fn)( )

Parameters

  nobj	 Specifies the NURBS object (created with gluNewNurbsRenderer).

  which	 Specifies the callback	being defined.	The only valid value is
     GLU_ERROR.

  fn	 Specifies the function	that the callback calls.

Description

  gluNurbsCallback is used to define a callback	to be used by a	NURBS object.
  If the specified callback is already defined,	then it	is replaced.  If fn
  is NULL, then	any existing callback is erased.

  The one legal	callback is GLU_ERROR:

  GLU_ERROR	 The error function is called when an error is encountered.
         Its single argument is	of type	GLenum,	and it indicates the
         specific error	that occurred.	There are 37 errors unique to
         NURBS named GLU_NURBS_ERROR1 through GLU_NURBS_ERROR37.
         Character strings describing these errors can be retrieved
         with gluErrorString.

See Also

  gluErrorString, gluNewNurbsRenderer

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluNurbsCurve.3gl

Name

  gluNurbsCurve	- define the shape of a	NURBS curve

C Specification

  void gluNurbsCurve( GLUnurbsObj *nobj,
              GLint nknots,
              GLfloat *knot,
              GLint stride,
              GLfloat *ctlarray,
              GLint order,
              GLenum type )

Parameters

  nobj	    Specifies the NURBS	object (created	with gluNewNurbsRenderer).

  nknots    Specifies the number of knots in knot.  nknots equals the number
        of control points plus the order.

  knot	    Specifies an array of nknots nondecreasing knot values.

  stride    Specifies the offset (as a number of single-precision floating-
        point values) between successive curve control points.

  ctlarray  Specifies a	pointer	to an array of control points.	The
        coordinates	must agree with	type, specified	below.

  order	    Specifies the order	of the NURBS curve.  order equals degree + 1,
        hence a cubic curve	has an order of	4.

  type	    Specifies the type of the curve.  If this curve is defined within
        a gluBeginCurve/gluEndCurve	pair, then the type can	be any of the
        valid one-dimensional evaluator types (such	as GL_MAP1_VERTEX_3
        or GL_MAP1_COLOR_4).  Between a gluBeginTrim/gluEndTrim pair, the
        only valid types are GLU_MAP1_TRIM_2 and GLU_MAP1_TRIM_3.

Description

  Use gluNurbsCurve to describe	a NURBS	curve.

  When gluNurbsCurve appears between a gluBeginCurve/gluEndCurve pair, it is
  used to describe a curve to be rendered.  Positional,	texture, and color
  coordinates are associated by	presenting each	as a separate gluNurbsCurve
  between a gluBeginCurve/gluEndCurve pair.  No	more than one call to
  gluNurbsCurve	for each of color, position, and texture data can be made
  within a single gluBeginCurve/gluEndCurve pair.  Exactly one call must be
  made to describe the position	of the curve (a	type of	GL_MAP1_VERTEX_3 or
  GL_MAP1_VERTEX_4).

  When gluNurbsCurve appears between a gluBeginTrim/gluEndTrim pair, it	is
  used to describe a trimming curve on a NURBS surface.	 If type is
  GLU_MAP1_TRIM_2, then	it describes a curve in	two-dimensional	(u and v)
  parameter space.  If it is GLU_MAP1_TRIM_3, then it describes	a curve	in
  two-dimensional homogeneous (u, v, and w) parameter space.  See the
  gluBeginTrim reference page for more discussion about	trimming curves.

Example

  The following	commands render	a textured NURBS curve with normals:

  gluBeginCurve(nobj);
     gluNurbsCurve(nobj, ..., GL_MAP1_TEXTURE_COORD_2);
     gluNurbsCurve(nobj, ..., GL_MAP1_NORMAL);
     gluNurbsCurve(nobj, ..., GL_MAP1_VERTEX_4); gluEndCurve(nobj);

Notes

  To define trim curves	which stitch well use gluPwlCurve.

See Also

  gluBeginCurve, gluBeginTrim, gluNewNurbsRenderer, gluPwlCurve

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluNurbsProperty.3gl

Name

  gluNurbsProperty - set a NURBS property

C Specification

  void gluNurbsProperty( GLUnurbsObj *nobj,
             GLenum	property,
             GLfloat value )

Parameters

  nobj	    Specifies the NURBS	object (created	with gluNewNurbsRenderer).

  property  Specifies the property to be set.  Valid values are
        GLU_SAMPLING_TOLERANCE, GLU_DISPLAY_MODE, GLU_CULLING,
        GLU_AUTO_LOAD_MATRIX, GLU_PARAMETRIC_TOLERANCE,
        GLU_SAMPLING_METHOD, GLU_U_STEP, or	GLU_V_STEP.

  value	    Specifies the value	of the indicated property.  It may be a
        numeric value, or one of GLU_PATH_LENGTH, GLU_PARAMETRIC_ERROR,
        or GLU_DOMAIN_DISTANCE.

Description

  gluNurbsProperty is used to control properties stored	in a NURBS object.
  These	properties affect the way that a NURBS curve is	rendered.  The legal
  values for property are as follows:

  GLU_SAMPLING_METHOD	   specifies how a NURBS surface should	be
               tessellated.	value may be set to one	of
               GLU_PATH_LENGTH, GLU_PARAMETRIC_ERROR, or
               GLU_DOMAIN_DISTANCE.	When set to GLU_PATH_LENGTH,
               the surface is rendered so that the maximum
               length, in pixels, of the edges of the
               tessellation	polygons is no greater than what is
               specified by	GLU_SAMPLING_TOLERANCE.

               GLU_PARAMETRIC_ERROR	specifies that the surface is
               rendered in such a way that the value specified by
               GLU_PARAMETRIC_TOLERANCE describes the maximum
               distance, in	pixels,	between	the tessellation
               polygons and	the surfaces they approximate.

               GLU_DOMAIN_DISTANCE allows users to specify,	in
               parametric coordinates, how many sample points per
               unit	length are taken in u, v direction. The
               default value of GLU_SAMPLING_METHOD	is
               GLU_PATH_LENGTH.

  GLU_SAMPLING_TOLERANCE   specifies the maximum length, in pixels to use
               when	the sampling method is set to
               GLU_PATH_LENGTH. The	NURBS code is conservative
               when	rendering a curve or surface, so the actual
               length can be somewhat shorter. The default value
               is 50.0 pixels.

  GLU_PARAMETRIC_TOLERANCE specifies the maximum distance, in pixels, to use
               when	the sampling method is set to
               GLU_PARAMETRIC_ERROR.  The default value for
               GLU_PARAMETRIC_TOLERANCE is 0.5.

  GLU_U_STEP		   specifies the number	of sample points per unit
               length taken	along the u axis in parametric
               coordinates.	It is needed when GLU_SAMPLING_METHOD
               is set to GLU_DOMAIN_DISTANCE. The default value
               is 100.

  GLU_V_STEP		   specifies the number	of sample points per unit
               length taken	along the v axis in parametric
               coordinate. It is needed when GLU_SAMPLING_METHOD
               is set to GLU_DOMAIN_DISTANCE.  The default value
               is 100.

  GLU_DISPLAY_MODE	   value defines how a NURBS surface should be
               rendered.  value can	be set to GLU_FILL,
               GLU_OUTLINE_POLYGON,	or GLU_OUTLINE_PATCH.  When
               set to GLU_FILL, the	surface	is rendered as a set
               of polygons.	 GLU_OUTLINE_POLYGON instructs the
               NURBS library to draw only the outlines of the
               polygons created by tessellation.
               GLU_OUTLINE_PATCH causes just the outlines of
               patches and trim curves defined by the user to be
               drawn.  The default value is	GLU_FILL.

  GLU_CULLING		   value is a Boolean value that, when set to
               GL_TRUE, indicates that a NURBS curve should	be
               discarded prior to tessellation if its control
               points lie outside the current viewport.  The
               default is GL_FALSE (because	a NURBS	curve cannot
               fall	entirely within	the convex hull	of its
               control points).

  GLU_AUTO_LOAD_MATRIX	   value is a Boolean value.  When set to GL_TRUE,
               the NURBS code downloads the	projection matrix,
               the modelview matrix, and the viewport from the
               OpenGL server to compute sampling and culling
               matrices for	each NURBS curve that is rendered.
               Sampling and	culling	matrices are required to
               determine the tesselation of	a NURBS	surface	into
               line	segments or polygons and to cull a NURBS
               surface if it lies outside of the viewport.	If
               this	mode is	set to GL_FALSE, then the user needs
               to provide a	projection matrix, a modelview
               matrix, and a viewport for the NURBS	renderer to
               use to construct sampling and culling matrices.
               This	can be done with the gluLoadSamplingMatrices
               function.  The default for this mode	is GL_TRUE.
               Changing this mode from GL_TRUE to GL_FALSE does
               not affect the sampling and culling matrices	until
               gluLoadSamplingMatrices is called.

Notes

  A property of	GLU_PARAMETRIC_TOLERANCE, GLU_SAMPLING_METHOD, GLU_U_STEP, or
  GLU_V_STEP, or a value of GLU_PATH_LENGTH, GLU_PARAMETRIC_ERROR,
  GLU_DOMAIN_DISTANCE will only	be supported in	GLU version number 1.1.	They
  are not valid	parameters in GLU 1.0.

  gluGetString can be used to determine	the GLU	version.

See Also

  gluGetNurbsProperty, gluLoadSamplingMatrices,	gluNewNurbsRenderer,
  gluGetString.

Introduction | Alphabetic | Specification

Last Edited: Thu Mar 16, 1995

AFV


gluNurbsSurface.3gl

Name

  gluNurbsSurface - define the shape of	a NURBS	surface

C Specification

  void gluNurbsSurface(	GLUnurbsObj *nobj,
            GLint uknot_count,
            GLfloat	*uknot,
            GLint vknot_count,
            GLfloat	*vknot,
            GLint u_stride,
            GLint v_stride,
            GLfloat	*ctlarray,
            GLint uorder,
            GLint vorder,
            GLenum type )

Parameters

  nobj	       Specifies the NURBS object (created with	gluNewNurbsRenderer).

  uknot_count  Specifies the number of knots in	the parametric u direction.

  uknot	       Specifies an array of uknot_count nondecreasing knot values in
           the parametric u	direction.

  vknot_count  Specifies the number of knots in	the parametric v direction.

  vknot	       Specifies an array of vknot_count nondecreasing knot values in
           the parametric v	direction.

  u_stride     Specifies the offset (as	a number of  single-precision
           floating	point values) between successive control points	in
           the parametric u	direction in ctlarray.

  v_stride     Specifies the offset (in	single-precision floating-point
           values) between successive control points in the	parametric v
           direction in ctlarray.

  ctlarray     Specifies an array containing control points for	the NURBS
           surface.	 The offsets between successive	control	points in the
           parametric u and	v directions are given by u_stride and
           v_stride.

  uorder       Specifies the order of the NURBS	surface	in the parametric u
           direction.  The order is	one more than the degree, hence	a
           surface that is cubic in	u has a	u order	of 4.

  vorder       Specifies the order of the NURBS	surface	in the parametric v
           direction.  The order is	one more than the degree, hence	a
           surface that is cubic in	v has a	v order	of 4.

  type	       Specifies type of the surface. type can be any of the valid
           two-dimensional evaluator types (such as	GL_MAP2_VERTEX_3 or
           GL_MAP2_COLOR_4).

Description

  Use gluNurbsSurface within a NURBS (Non-Uniform Rational B-Spline) surface
  definition to	describe the shape of a	NURBS surface (before any trimming).
  To mark the beginning	of a NURBS surface definition, use the
  gluBeginSurface command.  To mark the	end of a NURBS surface definition,
  use the gluEndSurface	command.  Call gluNurbsSurface within a	NURBS surface
  definition only.

  Positional, texture, and color coordinates are associated with a surface by
  presenting each as a separate	gluNurbsSurface	between	a
  gluBeginSurface/gluEndSurface	pair.  No more than one	call to
  gluNurbsSurface for each of color, position, and texture data	can be made
  within a single gluBeginSurface/gluEndSurface	pair.  Exactly one call	must
  be made to describe the position of the surface (a type of GL_MAP2_VERTEX_3
  or GL_MAP2_VERTEX_4).

  A NURBS surface can be trimmed by using the commands gluNurbsCurve and
  gluPwlCurve between calls to gluBeginTrim and	gluEndTrim.

  Note that a gluNurbsSurface with uknot_count knots in	the u direction	and
  vknot_count knots in the v direction with orders uorder and vorder must
  have (uknot_count - uorder) x	(vknot_count - vorder) control points.

Example

  The following	commands render	a textured NURBS surface with normals; the
  texture coordinates and normals are also NURBS surfaces:

  gluBeginSurface(nobj);
     gluNurbsSurface(nobj, ...,	GL_MAP2_TEXTURE_COORD_2);
     gluNurbsSurface(nobj, ...,	GL_MAP2_NORMAL);
     gluNurbsSurface(nobj, ...,	GL_MAP2_VERTEX_4); gluEndSurface(nobj);

See Also

  gluBeginSurface, gluBeginTrim, gluNewNurbsRenderer, gluNurbsCurve,
  gluPwlCurve

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluOrtho2D.3gl

Name

  gluOrtho2D - define a	2-D orthographic projection matrix

C Specification

  void gluOrtho2D( GLdouble left,
           GLdouble right,
           GLdouble bottom,
           GLdouble top	)

Parameters

  left,	right Specify the coordinates for the left and right vertical
          clipping planes.

  bottom, top Specify the coordinates for the bottom and top horizontal
          clipping planes.

Description

  gluOrtho2D sets up a two-dimensional orthographic viewing region. This is
  equivalent to	calling	glOrtho	with near=-1 and far=1.

See Also

  glOrtho, gluPerspective

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluPartialDisk.3gl

Name

  gluPartialDisk - draw	an arc of a disk

C Specification

  void gluPartialDisk( GLUquadricObj *qobj,
               GLdouble	innerRadius,
               GLdouble	outerRadius,
               GLint slices,
               GLint loops,
               GLdouble	startAngle,
               GLdouble	sweepAngle )

Parameters

  qobj	       Specifies a quadrics object (created with gluNewQuadric).

  innerRadius  Specifies the inner radius of the partial disk (can be zero).

  outerRadius  Specifies the outer radius of the partial disk.

  slices       Specfies	the number of subdivisions around the z	axis.

  loops	       Specifies the number of concentric rings	about the origin into
           which the partial disk is subdivided.

  startAngle   Specifies the starting angle, in	degrees, of the	disk portion.

  sweepAngle   Specifies the sweep angle, in degrees, of the disk portion.

Description

  gluPartialDisk renders a partial disk	on the z=0 plane.  A partial disk is
  similar to a full disk, except that only the subset of the disk from
  startAngle through startAngle	+ sweepAngle is	included (where	0 degrees is
  along	the +y axis, 90	degrees	along the +x axis, 180 along the -y axis, and
  270 along the	-x axis).

  The partial disk has a radius	of outerRadius,	and contains a concentric
  circular hole	with a radius of innerRadius.  If innerRadius is zero, then
  no hole is generated.	 The partial disk is subdivided	around the z axis
  into slices (like pizza slices), and also about the z	axis into rings	(as
  specified by slices and loops, respectively).

  With respect to orientation, the +z side of the partial disk is considered
  to be	outside	(see gluQuadricOrientation). This means	that if	the
  orientation is set to	GLU_OUTSIDE, then any normals generated	point along
  the +z axis.	Otherwise, they	point along the	-z axis.

  If texturing is turned on (with gluQuadricTexture), texture coordinates are
  generated linearly such that where r=outerRadius, the	value at (r, 0,	0) is
  (1, 0.5), at (0, r, 0) it is (0.5, 1), at (-r, 0, 0) it is (0, 0.5), and at
  (0, -r, 0) it	is (0.5, 0).

See Also

  gluCylinder, gluDisk,	gluNewQuadric, gluQuadricOrientation,
  gluQuadricTexture, gluSphere

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluPerspective.3gl

Name

  gluPerspective - set up a perspective	projection matrix

C Specification

  void gluPerspective( GLdouble	fovy,
               GLdouble	aspect,
               GLdouble	zNear,
               GLdouble	zFar )

Parameters

  fovy	  Specifies the	field of view angle, in	degrees, in the	y direction.

  aspect  Specifies the	aspect ratio that determines the field of view in the
      x direction.	The aspect ratio is the	ratio of x (width) to y
      (height).

  zNear	  Specifies the	distance from the viewer to the	near clipping plane
      (always positive).

  zFar	  Specifies the	distance from the viewer to the	far clipping plane
      (always positive).

Description

  gluPerspective specifies a viewing frustum into the world coordinate
  system.  In general, the aspect ratio	in gluPerspective should match the
  aspect ratio of the associated viewport.  For	example, aspect=2.0 means the
  viewer's angle of view is twice as wide in x as it is	in y. If the viewport
  is twice as wide as it is tall, it displays the image	without	distortion.

  The matrix generated by gluPerspective is multipled by the current matrix,
  just as if glMultMatrix were called with the generated matrix.  To load the
  perspective matrix onto the current matrix stack instead, precede the	call
  to gluPerspective with a call	to glLoadIdentity.

  Given	f defined as follows:
                                            fovy
                  f	= cotangent(----)
                          2
  The generated	matrix is

          |    f				   |
                  |  ------  0      0            0         |
          |  aspect   	    		 	   |
          |					   |
          |    0     f	    0		 0	   |
          |					   |
          |		zFar+zNear  2*zFar*zNear   |
                  |    0     0  ----------  ------------   |
          |         	zNear-zFar   zNear-zFar	   |
          |					   |
          |    0     0	    -1		 0         |

Notes

  Depth	buffer precision is affected by	the values specified for zNear and
  zFar.	 The greater the ratio of zFar to zNear	is, the	less effective the
  depth	buffer will be at distinguishing between surfaces that are near	each
  other.  If
                                       zFar
                   r = -----
                       zNear

  roughly log r	bits of	depth buffer precision are lost.  Because r
             2
  approaches infinity as zNear approaches zero,	zNear must never be set	to
  zero.

See Also

  glFrustum, glLoadIdentity, glMultMatrix, gluOrtho2D

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluPickMatrix.3gl

Name

  gluPickMatrix	- define a picking region

C Specification

  void gluPickMatrix( GLdouble x,
              GLdouble y,
              GLdouble width,
              GLdouble height,
              GLint viewport[4]	)

Parameters

  x, y Specify the center of a picking region in window	coordinates.

  width, height
       Specify the width and height, respectively, of the picking region in
       window coordinates.

  viewport
       Specifies the current viewport (as from a glGetIntegerv call).

Description

  gluPickMatrix	creates	a projection matrix that can be	used to	restrict
  drawing to a small region of the viewport.  This is typically	useful to
  determine what objects are being drawn near the cursor.  Use gluPickMatrix
  to restrict drawing to a small region	around the cursor.  Then, enter
  selection mode (with glRenderMode and	rerender the scene.  All primitives
  that would have been drawn near the cursor are identified and	stored in the
  selection buffer.

  The matrix created by	gluPickMatrix is multiplied by the current matrix
  just as if glMultMatrix is called with the generated matrix.	To
  effectively use the generated	pick matrix for	picking, first call
  glLoadIdentity to load an identity matrix onto the perspective matrix
  stack.  Then call gluPickMatrix, and finally,	call a command (such as
  gluPerspective) to multiply the perspective matrix by	the pick matrix.

  When using gluPickMatrix to pick NURBS, be careful to	turn off the NURBS
  property GLU_AUTO_LOAD_MATRIX.  If GLU_AUTO_LOAD_MATRIX is not turned	off,
  then any NURBS surface rendered is subdivided	differently with the pick
  matrix than the way it was subdivided	without	the pick matrix.

Example

  When rendering a scene as follows:

  glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(...);
  glMatrixMode(GL_MODELVIEW); /* Draw the scene	*/

  a portion of the viewport can	be selected as a pick region like this:

  glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPickMatrix(x, y, width,
  height, viewport); gluPerspective(...); glMatrixMode(GL_MODELVIEW); /* Draw
  the scene */

See Also

  glGet, glLoadIndentity, glMultMatrix,	glRenderMode, gluPerspective

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluProject.3gl

Name

  gluProject - map object coordinates to window	coordinates

C Specification

  int gluProject( GLdouble objx,
          GLdouble objy,
          GLdouble objz,
          const	GLdouble modelMatrix[16],
          const	GLdouble projMatrix[16],
          const	GLint viewport[4],
          GLdouble *winx,
          GLdouble *winy,
          GLdouble *winz )

Parameters

  objx,	objy, objz
          Specify the object coordinates.

  modelMatrix	  Specifies the	current	modelview matrix (as from a
          glGetDoublev call).

  projMatrix	  Specifies the	current	projection matrix (as from a
          glGetDoublev call).

  viewport	  Specifies the	current	viewport (as from a glGetIntegerv
          call).

  winx,	winy, winz
          Return the computed window coordinates.

Description

  gluProject transforms	the specified object coordinates into window
  coordinates using modelMatrix, projMatrix, and viewport.  The	result is
  stored in winx, winy,	and winz.  A return value of GL_TRUE indicates
  success, and GL_FALSE	indicates failure.

See Also

  glGet, gluUnProject

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluPwlCurve.3gl

Name

  gluPwlCurve -	describe a piecewise linear NURBS trimming curve

C Specification

  void gluPwlCurve( GLUnurbsObj	*nobj,
            GLint count,
            GLfloat *array,
            GLint stride,
            GLenum type	)

Parameters

  nobj	  Specifies the	NURBS object (created with gluNewNurbsRenderer).

  count	  Specifies the	number of points on the	curve.

  array	  Specifies an array containing	the curve points.

  stride  Specifies the	offset (a number of single-precision floating-point
      values) between points on the	curve.

  type	  Specifies the	type of	curve.	Must be	either GLU_MAP1_TRIM_2 or
      GLU_MAP1_TRIM_3.

Description

  gluPwlCurve describes	a piecewise linear trimming curve for a	NURBS
  surface.  A piecewise	linear curve consists of a list	of coordinates of
  points in the	parameter space	for the	NURBS surface to be trimmed.  These
  points are connected with line segments to form a curve.  If the curve is
  an approximation to a	real curve, the	points should be close enough that
  the resulting	path appears curved at the resolution used in the
  application.

  If type is GLU_MAP1_TRIM_2, then it describes	a curve	in two-dimensional (u
  and v) parameter space.  If it is GLU_MAP1_TRIM_3, then it describes a
  curve	in two-dimensional homogeneous (u, v, and w) parameter space.  Please
  refer	to the	gluBeginTrim reference page for	more information about
  trimming curves.

Notes

  To describe a	trim curve which closely follows the contours of a Nurbs
  surface use gluNurbsCurve.

See Also

  gluBeginCurve, gluBeginTrim, gluNewNurbsRenderer, gluNurbsCurve

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluQuadricCallback.3gl

Name

  gluQuadricCallback - define a	callback for a quadrics	object

C Specification

  void gluQuadricCallback( GLUquadricObj *qobj,
               GLenum which,
               void	(*fn)( )

Parameters

  qobj	 Specifies the quadrics	object (created	with gluNewQuadric).

  which	 Specifies the callback	being defined.	The only valid value is
     GLU_ERROR.

  fn	 Specifies the function	to be called.

Description

  gluQuadricCallback is	used to	define a new callback to be used by a
  quadrics object.  If the specified callback is already defined, then it is
  replaced.  If	fn is NULL, then any existing callback is erased.

  The one legal	callback is GLU_ERROR:

  GLU_ERROR	 The function is called	when an	error is encountered.  Its
         single	argument is of type GLenum, and	it indicates the
         specific error	that occurred.	Character strings describing
         these errors can be retrieved with the	gluErrorString call.

See Also

  gluErrorString, gluNewQuadric

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluQuadricDrawStyle.3gl

Name

  gluQuadricDrawStyle -	specify	the draw style desired for quadrics

C Specification

  void gluQuadricDrawStyle( GLUquadricObj *qobj,
                GLenum drawStyle )

Parameters

  qobj	     Specifies the quadrics object (created with gluNewQuadric).

  drawStyle  Specifies the desired draw	style.	Valid values are GLU_FILL,
         GLU_LINE, GLU_SILHOUETTE, and GLU_POINT.

Description

  gluQuadricDrawStyle specifies	the draw style for quadrics rendered with
  qobj.	 The legal values are as follows:

  GLU_FILL	 Quadrics are rendered with polygon primitives.	 The polygons
         are drawn in a	counterclockwise fashion with respect to
         their normals (as defined with	gluQuadricOrientation).

  GLU_LINE	 Quadrics are rendered as a set	of lines.

  GLU_SILHOUETTE Quadrics are rendered as a set	of lines, except that edges
         separating coplanar faces will	not be drawn.

  GLU_POINT	 Quadrics are rendered as a set	of points.

See Also

  gluNewQuadric, gluQuadricNormals, gluQuadricOrientation,  gluQuadricTexture

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluQuadricNormals.3gl

Name

  gluQuadricNormals - specify what kind	of normals are desired for quadrics

C Specification

  void gluQuadricNormals( GLUquadricObj	*qobj,
              GLenum normals )

Parameters

  qobj	   Specifes the	quadrics object	(created with gluNewQuadric).

  normals  Specifies the desired type of normals.  Valid values	are GLU_NONE,
       GLU_FLAT, and GLU_SMOOTH.

Description

  gluQuadricNormals specifies what kind	of normals are desired for quadrics
  rendered with	qobj.  The legal values	are as follows:

  GLU_NONE     No normals are generated.

  GLU_FLAT     One normal is generated for every facet of a quadric.

  GLU_SMOOTH   One normal is generated for every vertex	of a quadric.  This
           is the default.

See Also

  gluNewQuadric, gluQuadricDrawStyle, gluQuadricOrientation,
  gluQuadricTexture

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluQuadricOrientation.3gl

Name

  gluQuadricOrientation	- specify inside/outside orientation for quadrics

C Specification

  void gluQuadricOrientation( GLUquadricObj *qobj,
                  GLenum orientation )

Parameters

  qobj	       Specifies the quadrics object (created with gluNewQuadric).

  orientation  Specifies the desired orientation.  Valid values	are
           GLU_OUTSIDE and GLU_INSIDE.

Description

  gluQuadricOrientation	specifies what kind of orientation is desired for
  quadrics rendered with qobj.	The orientation	values are as follows:

  GLU_OUTSIDE	 Quadrics are drawn with normals pointing outward.

  GLU_INSIDE	 Normals point inward.	The default is GLU_OUTSIDE.

  Note that the	interpretation of outward and inward depends on	the quadric
  being	drawn.

See Also

  gluNewQuadric, gluQuadricDrawStyle, gluQuadricNormals, gluQuadricTexture

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluQuadricTexture.3gl

Name

  gluQuadricTexture - specify if texturing is desired for quadrics

C Specification

  void gluQuadricTexture( GLUquadricObj	*qobj,
              GLboolean textureCoords )

Parameters

  qobj		 Specifies the quadrics	object (created	with gluNewQuadric).

  textureCoords	 Specifies a flag indicating if	texture	coordinates should be
         generated.

Description

  gluQuadricTexture specifies if texture coordinates should be generated for
  quadrics rendered with qobj.	If the value of	textureCoords is GL_TRUE,
  then texture coordinates are generated, and if textureCoords is GL_FALSE,
  they are not.	 The default is	GL_FALSE.

  The manner in	which texture coordinates are generated	depends	upon the
  specific quadric rendered.

See Also

  gluNewQuadric, gluQuadricDrawStyle, gluQuadricNormals,
  gluQuadricOrientation

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluScaleImage.3gl

Name

  gluScaleImage	- scale	an image to an arbitrary size

C Specification

  int gluScaleImage( GLenum format,
             GLint widthin,
             GLint heightin,
             GLenum typein,
             const void	*datain,
             GLint widthout,
             GLint heightout,
             GLenum typeout,
             void *dataout )

Parameters

  format     Specifies the format of the pixel data.  The following symbolic
         values are	valid: GL_COLOR_INDEX, GL_STENCIL_INDEX,
         GL_DEPTH_COMPONENT, GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB,
         GL_RGBA, GL_LUMINANCE, and	GL_LUMINANCE_ALPHA.

  widthin, heightin
         Specify the width and height, respectively, of the	source image
         that is scaled.

  typein     Specifies the data	type for datain. Must be one of
         GL_UNSIGNED_BYTE, GL_BYTE,	GL_BITMAP, GL_UNSIGNED_SHORT,
         GL_SHORT, GL_UNSIGNED_INT,	GL_INT,	or GL_FLOAT.

  datain     Specifies a pointer to the	source image.

  widthout, heightout
         Specify the width and height, respectively, of the	destination
         image.

  typeout    Specifies the data	type for dataout. Must be one of
         GL_UNSIGNED_BYTE, GL_BYTE,	GL_BITMAP, GL_UNSIGNED_SHORT,
         GL_SHORT, GL_UNSIGNED_INT,	GL_INT,	or GL_FLOAT.

  dataout    Specifies a pointer to the	destination image.

Description

  gluScaleImage	scales a pixel image using the appropriate pixel store modes
  to unpack data from the source image and pack	data into the destination
  image.

  When shrinking an image, gluScaleImage uses a	box filter to sample the
  source image and create pixels for the destination image.  When magnifying
  an image, the	pixels from the	source image are linearly interpolated to
  create the destination image.

  A return value of zero indicates success, otherwise a	GLU error code is
  returned indicating what the problem was (see	gluErrorString).

  Please refer to the glReadPixels reference page for a	description of the
  acceptable values for	the format, typein, and	typeout	parameters.

See Also

  glDrawPixels,	glReadPixels, gluBuild1DMipmaps, gluBuild2DMipmaps,
  gluErrorString

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluSphere.3gl

Name

  gluSphere - draw a sphere

C Specification

  void gluSphere( GLUquadricObj	*qobj,
          GLdouble radius,
          GLint	slices,
          GLint	stacks )

Parameters

  qobj	  Specifies the	quadrics object	(created with gluNewQuadric).

  radius  Specifies the	radius of the sphere.

  slices  Specifies the	number of subdivisions around the z axis (similar to
      lines	of longitude).

  stacks  Specifies the	number of subdivisions along the z axis	(similar to
      lines	of latitude).

Description

  gluSphere draws a sphere of the given	radius centered	around the origin.
  The sphere is	subdivided around the z	axis into slices and along the z axis
  into stacks (similar to lines	of longitude and latitude).

  If the orientation is	set to GLU_OUTSIDE (with gluQuadricOrientation), then
  any normals generated	point away from	the center of the sphere. Otherwise,
  they point toward the	center of the sphere.

  If texturing is turned on (with gluQuadricTexture), then texture
  coordinates are generated so that t ranges from 0.0 at z=-radius to 1.0 at
  z=radius (t increases	linearly along longitudinal lines), and	s ranges from
  0.0 at the +y	axis, to 0.25 at the +x	axis, to 0.5 at	the -y axis, to	0.75
  at the -x axis, and back to 1.0 at the +y axis.

See Also

  gluCylinder, gluDisk,	gluNewQuadric, gluPartialDisk, gluQuadricOrientation
  gluQuadricTexture

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluTessBeginContour.3gl

gluTessBeginContour.3gl (GLU version 1.2 and later)

Name

  gluTessBeginContour, gluTessEndContour - delimit a contour description

C Specification

  void gluTessBeginContour( GLUtesselator *tess	)

  void gluTessEndContour( GLUtesselator	*tess )

Parameters

  tess	Specifies the tessellation object (created with	gluNewTess).

Description

  gluTessBeginContour and gluTessEndContour delimit the	definition of a
  polygon contour. Within each gluTessBeginContour/gluTessEndContour pair,
  there	can be zero or more calls to gluTessVertex. The	vertices specify a
  closed contour (the last vertex of each contour is automatically linked to
  the first).  See the gluTessVertex reference page for	more details.
  gluTessBeginContour can only be called between gluTessBeginPolygon and
  gluTessEndPolygon.

See Also

  gluNewTess, gluTessBeginPolygon, gluTessVertex, gluTessCallback,
  gluTessProperty, gluTessNormal, gluTessEndPolygon

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


gluTessBeginPolygon.3gl

gluTessBeginPolygon.3gl (GLU version 1.2 and later)

Name

  gluTessBeginPolygon -	delimit	a polygon description

C Specification

  void gluTessBeginPolygon( GLUtesselator *tess,
                void *polygon_data )

Parameters

  tess		Specifies the tessellation object (created with	gluNewTess).

  polygon_data	Specifies a pointer to user polygon data.

Description

  gluTessBeginPolygon and gluTessEndPolygon delimit the	definition of a
  nonconvex polygon. Within each gluTessBeginPolygon/gluTessEndPolygon pair,
  there	must be	one or more calls to gluTessBeginContour/gluTessEndContour.
  Within each contour, there are zero or more calls to gluTessVertex. The
  vertices specify a closed contour (the last vertex of	each contour is
  automatically	linked to the first).  See the gluTessVertex,
  gluTessBeginContour and gluTessEndContour reference pages for	more details.

  polygon_data is a pointer to a user-defined data structure. If the
  appropriate callback(s) are specified	(see gluTessCallback), then this
  pointer is returned to the callback function(s). Thus, it is a convenient
  way to store per-polygon information.

  Once gluTessEndPolygon is called, the	polygon	is tessellated,	and the
  resulting triangles are described through callbacks.	See gluTessCallback
  for descriptions of the callback functions.

Example

  A quadrilateral with a triangular hole in it can be described	like this:

  gluTessBeginPolygon(tobj, NULL);
   gluTessBeginContour(tobj);
     gluTessVertex(tobj, v1, v1);
     gluTessVertex(tobj, v2, v2);
     gluTessVertex(tobj, v3, v3);
     gluTessVertex(tobj, v4, v4);
   gluTessEndContour(tobj);
   gluTessBeginContour(tobj);
     gluTessVertex(tobj, v5, v5);
     gluTessVertex(tobj, v6, v6);
     gluTessVertex(tobj, v7, v7);
   gluTessEndContour(tobj);
  gluTessEndPolygon(tobj);

See Also

  gluNewTess, gluTessBeginContour, gluTessVertex, gluTessCallback,
  gluTessProperty, gluTessNormal gluTessEndPolygon

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


gluTessCallback.3gl

gluTessCallback.3gl (GLU version 1.2 and later)

Name

  gluTessCallback - define a callback for a tessellation object

C Specification

  void gluTessCallback(	GLUtesselator *tess,
            GLenum which,
            void (*fn)() )

Parameters

  tess	 Specifies the tessellation object (created with gluNewTess).

  which	 Specifies the callback	being defined.	The following values are
     valid:	GLU_TESS_BEGIN,	GLU_TESS_BEGIN_DATA, GLU_TESS_EDGE_FLAG,
     GLU_TESS_EDGE_FLAG_DATA, GLU_TESS_VERTEX, GLU_TESS_VERTEX_DATA,
     GLU_TESS_END, GLU_TESS_END_DATA, GLU_TESS_COMBINE,
     GLU_TESS_COMBINE_DATA,	GLU_TESS_ERROR,	and GLU_TESS_ERROR_DATA.

  fn	 Specifies the function	to be called.

Description

  gluTessCallback is used to indicate a	callback to be used by a tessellation
  object.  If the specified callback is	already	defined, then it is replaced.
  If fn	is NULL, then the existing callback becomes undefined.

  These	callbacks are used by the tessellation object to describe how a
  polygon specified by the user	is broken into triangles. Note that there are
  two versions of each callback: one with user-specified polygon data and one
  without. If both versions of a particular callback are specified then	the
  callback with	user-specified polygon data will be used. Note that
  "polygon_data" is a copy of the pointer that was specified when
  gluTessBeginPolygon was called.

  The legal callbacks are as follows:

  GLU_TESS_BEGIN
        The	begin callback is invoked like glBegin to indicate the start
        of a (triangle) primitive. The function takes a single argument
        of type GLenum.  If	the GLU_TESS_BOUNDARY_ONLY property is set to
        GL_FALSE then the argument is set to either	GL_TRIANGLE_FAN,
        GL_TRIANGLE_STRIP, or GL_TRIANGLES.	If the GLU_TESS_BOUNDARY_ONLY
        property is	set to GL_TRUE then the	argument will be set to
        GL_LINE_LOOP. The function prototype for this callback looks
        like:

        void begin ( GLenum	type );

  GLU_TESS_BEGIN_DATA
        The	same as	the GLU_TESS_BEGIN callback except that	it takes an
        additional pointer argument. This pointer is identical to the
        opaque pointer provided when gluTessBeginPolygon was called. The
        function prototype for this	callback looks like:

        void beginData ( GLenum type, void *polygon_data );

  GLU_TESS_EDGE_FLAG
        The	edge flag callback is similar to glEdgeFlag.  The function
        takes a single Boolean flag	that indicates which edges lie on the
        polygon boundary. If the flag is GL_TRUE, then each	vertex that
        follows begins an edge which lies on the polygon boundary -- that
        is,	an edge	which separates	an interior region from	an exterior
        one.  If the flag is GL_FALSE, then	each vertex that follows
        begins an edge which lies in the polygon interior. The edge	flag
        callback (if defined) is invoked before the	first vertex callback
        is made.

        Since triangle fans	and triangle strips do not support edge
        flags, the begin callback is not called with GL_TRIANGLE_FAN or
        GL_TRIANGLE_STRIP if an edge flag callback is provided.  Instead,
        the	fans and strips	are converted to independent triangles.	The
        function prototype for this	callback looks like:

        void edgeFlag ( GLboolean flag );

  GLU_TESS_EDGE_FLAG_DATA
        The	same as	the GLU_TESS_EDGE_FLAG callback	except that it takes
        an additional pointer argument. This pointer is identical to the
        opaque pointer provided when gluTessBeginPolygon was called. The
        function prototype for this	callback looks like:

        void edgeFlagData (	GLboolean flag,	void *polygon_data );

  GLU_TESS_VERTEX
        The	vertex callback	is invoked between the begin and end
        callbacks.	It is similar to glVertex, and it defines the
        vertices of	the triangles created by the tessellation process.
        The	function takes a pointer as its	only argument.	This pointer
        is identical to the	opaque pointer provided	by the user when the
        vertex was described (see gluTessVertex). The function prototype
        for	this callback looks like:

        void vertex	( void *vertex_data );

  GLU_TESS_VERTEX_DATA
        The	same as	the GLU_TESS_VERTEX callback except that it takes an
        additional pointer argument. This pointer is identical to the
        opaque pointer provided when gluTessBeginPolygon was called. The
        function prototype for this	callback looks like:

        void vertexData ( void *vertex_data, void *polygon_data );

  GLU_TESS_END
        The	end callback serves the	same purpose as	glEnd. It indicates
        the	end of a primitive and it takes	no arguments. The function
        prototype for this callback	looks like:

        void end ( void );

  GLU_TESS_END_DATA
        The	same as	the GLU_TESS_END callback except that it takes an
        additional pointer argument. This pointer is identical to the
        opaque pointer provided when gluTessBeginPolygon was called. The
        function prototype for this	callback looks like:
        void endData ( void	*polygon_data);

  GLU_TESS_COMBINE
        The	combine	callback is called to create a new vertex when the
        tessellation detects an intersection, or wishes to merge
        features. The function takes four arguments: an array of three
        elements each of type GLdouble, an array of	four pointers, an
        array of four elements each	of type	GLfloat, and a pointer to a
        pointer. The prototype looks like:
        void combine( GLdouble coords[3], void *vertex_data[4],
              GLfloat weight[4], void **outData );

        The	vertex is defined as a linear combination of up	to 4 existing
        vertices, stored in	vertex_data. The coefficients of the linear
        combination	are given by weight; these weights always sum to 1.0.
        All	vertex pointers	are valid even when some of the	weights	are
        zero.  coords gives	the location of	the new	vertex.

        The	user must allocate another vertex, interpolate parameters
        using vertex_data and weight, and return the new vertex pointer
        in outData.	 This handle is	supplied during	rendering callbacks.
        The	user is	responsible for	freeing	the memory sometime after
        gluTessEndPolygon is called.

        For	example, if the	polygon	lies in	an arbitrary plane in 3-space,
        and we associate a color with each vertex, the GLU_TESS_COMBINE
        callback might look like this:

        void myCombine( GLdouble coords[3],	VERTEX *d[4],
                GLfloat w[4], VERTEX **dataOut )
            {
           VERTEX *new = new_vertex();

           new->x = coords[0];
           new->y = coords[1];
           new->z = coords[2];
           new->r = w[0]*d[0]->r + w[1]*d[1]->r + w[2]*d[2]->r + w[3]*d[3]->r;
           new->g = w[0]*d[0]->g + w[1]*d[1]->g + w[2]*d[2]->g + w[3]*d[3]->g;
           new->b = w[0]*d[0]->b + w[1]*d[1]->b + w[2]*d[2]->b + w[3]*d[3]->b;
           new->a = w[0]*d[0]->a + w[1]*d[1]->a + w[2]*d[2]->a + w[3]*d[3]->a;
           *dataOut	= new;
            }

        If the tessellation	detects	an intersection, then the
        GLU_TESS_COMBINE or	GLU_TESS_COMBINE_DATA callback (see below)
        must be defined, and it must write a non-NULL pointer into
        dataOut. Otherwise the GLU_TESS_NEED_COMBINE_CALLBACK error
        occurs, and	no output is generated.	 (This is the only error that
        can	occur during tessellation and rendering.)

  GLU_TESS_COMBINE_DATA
        The	same as	the GLU_TESS_COMBINE callback except that it takes an
        additional pointer argument. This pointer is identical to the
        opaque pointer provided when gluTessBeginPolygon was called. The
        function prototype for this	callback looks like:

        void combineData ( GLdouble	coords[3], void	*vertex_data[4],
                   GLfloat weight[4], void **outData,
                   void *polygon_data );

  GLU_TESS_ERROR
        The	error callback is called when an error is encountered.	The
        one	argument is of type GLenum; it indicates the specific error
        that occurred and will be set to one of
        GLU_TESS_MISSING_BEGIN_POLYGON, GLU_TESS_MISSING_END_POLYGON,
        GLU_TESS_MISSING_BEGIN_CONTOUR, GLU_TESS_MISSING_END_CONTOUR,
        GLU_TESS_COORD_TOO_LARGE, GLU_TESS_NEED_COMBINE_CALLBACK.
        Character strings describing these errors can be retrieved with
        the	gluErrorString call. The function prototype for	this callback
        looks like:

        void error ( GLenum	errno );

        The	GLU library will recover from the first	four errors by
        inserting the missing call(s).  GLU_TESS_COORD_TOO_LARGE says
        that some vertex coordinate	exceeded the predefined	constant
        GLU_TESS_MAX_COORD in absolute value, and that the value has been
        clamped.  (Coordinate values must be small enough so that two can
        be multiplied together without overflow.)
        GLU_TESS_NEED_COMBINE_CALLBACK says	that the tessellation
        detected an	intersection between two edges in the input data, and
        the	GLU_TESS_COMBINE or GLU_TESS_COMBINE_DATA callback was not
        provided.  No output will be generated.

  GLU_TESS_ERROR_DATA
        The	same as	the GLU_TESS_ERROR callback except that	it takes an
        additional pointer argument. This pointer is identical to the
        opaque pointer provided when gluTessBeginPolygon was called. The
        function prototype for this	callback looks like:

        void errorData ( GLenum errno, void	*polygon_data );

Example

  Polygons tessellated can be rendered directly	like this:

  gluTessCallback(tobj, GLU_TESS_BEGIN, glBegin);
  gluTessCallback(tobj, GLU_TESS_VERTEX, glVertex3dv);
  gluTessCallback(tobj, GLU_TESS_END, glEnd);
  gluTessCallback(tobj, GLU_TESS_COMBINE, myCombine);
  gluTessBeginPolygon(tobj, NULL);
    gluTessBeginContour(tobj);
      gluTessVertex(tobj, v, v);
      ...
    gluTessEndContour(tobj);
  gluTessEndPolygon(tobj);

  Typically, the tessellated polygon should be stored in a display list	so
  that it does not need	to be retessellated every time it is rendered.

See Also

  glBegin, glEdgeFlag, glVertex, gluNewTess, gluErrorString, gluTessVertex,
  gluTessBeginPolygon, gluTessBeginContour, gluTessProperty, gluTessNormal

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


gluTessCallback.3gl

gluTessCallback.3gl (GLU versions 1.0 and 1.1)

Name

  gluTessCallback - define a callback for a tessellation object

C Specification

  void gluTessCallback(	GLUtriangulatorObj *tobj,
            GLenum which,
            void (*fn)( )

Parameters

  tobj	 Specifies the tessellation object (created with gluNewTess).

  which	 Specifies the callback	being defined.	The following values are
     valid:	GLU_BEGIN, GLU_EDGE_FLAG, GLU_VERTEX, GLU_END, and GLU_ERROR.

  fn	 Specifies the function	to be called.

Description

  gluTessCallback is used to indicate a	callback to be used by a tessellation
  object.  If the specified callback is	already	defined, then it is replaced.
  If fn	is NULL, then the existing callback is erased.

  These	callbacks are used by the tessellation object to describe how a
  polygon specified by the user	is broken into triangles.

  The legal callbacks are as follows:

  GLU_BEGIN The	begin callback is invoked like glBegin to indicate the start
        of a (triangle) primitive.	The function takes a single argument
        of type GLenum that	is either GL_TRIANGLE_FAN, GL_TRIANGLE_STRIP,
        or GL_TRIANGLES.

  GLU_EDGE_FLAG
        The	edge flag callback is similar to glEdgeFlag.  The function
        takes a single Boolean flag	that indicates which edges of the
        created triangles were part	of the original	polygon	defined	by
        the	user, and which	were created by	the tessellation process.  If
        the	flag is	GL_TRUE, then each vertex that follows begins an edge
        that was part of the original polygon.  If the flag	is GL_FALSE,
        then each vertex that follows begins an edge that was generated
        by the tessellator.	 The edge flag callback	(if defined) is
        invoked before the first vertex callback is	made.

        Since triangle fans	and triangle strips do not support edge
        flags, the begin callback is not called with GL_TRIANGLE_FAN or
        GL_TRIANGLE_STRIP if an edge flag callback is provided.  Instead,
        the	fans and strips	are converted to independent triangles.

  GLU_VERTEX
        The	vertex callback	is invoked between the begin and end
        callbacks.	It is similar to glVertex, and it defines the
        vertices of	the triangles created by the tessellation process.
        The	function takes a pointer as its	only argument.	This pointer
        is identical to the	opaque pointer provided	by the user when the
        vertex was described (see gluTessVertex).

  GLU_END   The	end callback serves the	same purpose as	glEnd. It indicates
        the	end of a primitive and it takes	no arguments.

  GLU_ERROR The	error callback is called when an error is encountered.	The
        one	argument is of type GLenum, and	it indicates the specific
        error that occurred.  There	are eight errors unique	to polygon
        tessellation, named	GLU_TESS_ERROR1	through	GLU_TESS_ERROR8.
        Character strings describing these errors can be retrieved with
        the	gluErrorString call.

Example

  Polygons tessellated can be rendered directly	like this:

  gluTessCallback(tobj, GLU_BEGIN, glBegin);
  gluTessCallback(tobj, GLU_VERTEX, glVertex3dv);
  gluTessCallback(tobj, GLU_END, glEnd);

  gluBeginPolygon(tobj);
     gluTessVertex(tobj, v, v);
     ...
  gluEndPolygon(tobj);

  Typically, the tessellated polygon should be stored in a display list	so
  that it does not need	to be retessellated every time it is rendered.

See Also

  glBegin, glEdgeFlag, glVertex, gluDeleteTess,	gluErrorString,	gluNewTess,
  gluTessVertex

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


gluTessEndPolygon.3gl

gluTessEndPolygon.3gl (GLU version 1.2 and later)

Name

  gluTessEndPolygon - delimit a	polygon	description

C Specification

  void gluTessEndPolygon( GLUtesselator	*tess )

Parameters

  tess	Specifies the tessellation object (created with	gluNewTess).

Description

  gluTessBeginPolygon and gluTessEndPolygon delimit the	definition of a
  nonconvex polygon. Within each gluTessBeginPolygon/gluTessEndPolygon pair,
  there	must be	one or more calls to gluTessBeginContour/gluTessEndContour.
  Within each contour, there are zero or more calls to gluTessVertex. The
  vertices specify a closed contour (the last vertex of	each contour is
  automatically	linked to the first).  See the gluTessVertex,
  gluTessBeginContour and gluTessEndContour reference pages for	more details.

  Once gluTessEndPolygon is called, the	polygon	is tessellated,	and the
  resulting triangles are described through callbacks.	See gluTessCallback
  for descriptions of the callback functions.

Example

  A quadrilateral with a triangular hole in it can be described	like this:

  gluTessBeginPolygon(tobj, NULL);
   gluTessBeginContour(tobj);
     gluTessVertex(tobj, v1, v1);
     gluTessVertex(tobj, v2, v2);
     gluTessVertex(tobj, v3, v3);
     gluTessVertex(tobj, v4, v4);
   gluTessEndContour(tobj);
   gluTessBeginContour(tobj);
     gluTessVertex(tobj, v5, v5);
     gluTessVertex(tobj, v6, v6);
     gluTessVertex(tobj, v7, v7);
   gluTessEndContour(tobj);
  gluTessEndPolygon(tobj);

See Also

  gluNewTess, gluTessBeginContour, gluTessVertex, gluTessCallback,
  gluTessProperty, gluTessNormal, gluTessBeginPolygon

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


gluTessNormal.3gl

gluTessNormal.3gl (GLU version 1.2 and later)

Name

  gluTessNormal	- specify a normal for a polygon

C Specification

  void gluTessNormal( GLUtesselator *tess,
              GLdouble x,
              GLdouble y,
              GLdouble z )

Parameters

  tess	Specifies the tessellation object (created with	gluNewTess).

  x	Specifies the first component of the normal.

  y	Specifies the second component of the normal.

  z	Specifies the third component of the normal.

Description

  gluTessNormal	describes a normal for a polygon that the user is defining.
  All input data will be projected onto	a plane	perpendicular to one of	the
  three	coordinate axes	before tessellation and	all output triangles will be
  oriented CCW with respect to the normal (CW orientation can be obtained by
  reversing the	sign of	the supplied normal). For example, if you know that
  all polygons lie in the x-y plane, call gluTessNormal(tess, 0.0, 0.0,	1.0)
  before rendering any polygons.

  If the supplied normal is (0,0,0) (the default value), the normal is
  determined as	follows. The direction of the normal, up to its	sign, is
  found	by fitting a plane to the vertices, without regard to how the
  vertices are connected. It is	expected that the input	data lies
  approximately	in the plane; otherwise	projection perpendicular to one	of
  the three coordinate axes may	substantially change the geometry. The sign
  of the normal	is chosen so that the sum of the signed	areas of all input
  contours is non-negative (where a CCW	contour	has positive area).

  The supplied normal persists until it	is changed by another call to
  gluTessNormal.

See Also

  gluTessBeginPolygon, gluTessEndPolygon

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


gluTessProperty.3gl

gluTessProperty.3gl (GLU version 1.2 and later)

Name

  gluTessProperty - set	a tessellation object property

C Specification

  void gluTessProperty(	GLUtesselator *tess,
            GLenum which,
            GLdouble value )

Parameters

  tess	 Specifies the tessellation object (created with gluNewTess).

  which	 Specifies the property	to be set.  Valid values are
     GLU_TESS_WINDING_RULE,	GLU_TESS_BOUNDARY_ONLY,	GLU_TESS_TOLERANCE.

  value	 Specifies the value of	the indicated property.

Description

  gluTessProperty is used to control properties	stored in a tessellation
  object.  These properties affect the way that	the polygons are interpreted
  and rendered.	The legal values for which are as follows:

  GLU_TESS_WINDING_RULE	   determines which parts of the polygon are on	the
               "interior". value may be set	to one of
               GLU_TESS_WINDING_ODD, GLU_TESS_WINDING_NONZERO,
               GLU_TESS_WINDING_POSITIVE, or
               GLU_TESS_WINDING_NEGATIVE, or
               GLU_TESS_WINDING_ABS_GEQ_TWO.

               To understand how the winding rule works first
               consider that the input contours partition the
               plane into regions. The winding rule	determines
               which of these regions are inside the polygon.

               For a single	contour	C, the winding number of a
               point x is simply the signed	number of revolutions
               we make around x as we travel once around C (where
               CCW is positive). When there	are several contours,
               the individual winding numbers are summed. This
               procedure associates	a signed integer value with
               each	point x	in the plane. Note that	the winding
               number is the same for all points in	a single
               region.

               The winding rule classifies a region	as "inside"
               if its winding number belongs to the	chosen
               category (odd, nonzero, positive, negative, or
               absolute value of at	least two). The	previous GLU
               tessellator (prior to GLU 1.2) used the "odd"
               rule. The "nonzero" rule is another common way to
               define the interior.	The other three	rules are
               useful for polygon CSG operations.

  GLU_TESS_BOUNDARY_ONLY   is a	boolean	value ("value" should be set to
               GL_TRUE or GL_FALSE). When set to GL_TRUE, a	set
               of closed contours separating the polygon interior
               and exterior	are returned instead of	a
               tessellation. Exterior contours are oriented	CCW
               with	respect	to the normal, interior	contours are
               oriented CW.	The GLU_TESS_BEGIN and
               GLU_TESS_BEGIN_DATA callbacks use the type
               GL_LINE_LOOP	for each contour.

  GLU_TESS_TOLERANCE	   specifies a tolerance for merging features to
               reduce the size of the output.  For example,	two
               vertices which are very close to each other might
               be replaced by a single vertex. The tolerance is
               multiplied by the largest coordinate	magnitude of
               any input vertex; this specifies the	maximum
               distance that any feature can move as the result
               of a	single merge operation.	If a single feature
               takes part in several merge operations, the total
               distance moved could	be larger.

               Feature merging is completely optional; the
               tolerance is	only a hint.  The implementation is
               free	to merge in some cases and not in others, or
               to never merge features at all. The default
               tolerance is	zero.

               The current implementation merges vertices only if
               they	are exactly coincident,	regardless of the
               current tolerance. A	vertex is spliced into an
               edge	only if	the implementation is unable to
               distinguish which side of the edge the vertex lies
               on. Two edges are merged only when both endpoints
               are identical.

See Also

  gluGetTessProperty

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


gluTessVertex.3gl

gluTessVertex.3gl (GLU version 1.2 and later)

Name

  gluTessVertex	- specify a vertex on a	polygon

C Specification

  void gluTessVertex( GLUtesselator *tess,
              GLdouble coords[3],
              void *data )

Parameters

  tess	  Specifies the	tessellation object (created with gluNewTess).

  coords  Specifies the	location of the	vertex.

  data	  Specifies an opaque pointer passed back to the user with the vertex
      callback (as specified by gluTessCallback).

Description

  gluTessVertex	describes a vertex on a	polygon	that the user is defining.
  Successive gluTessVertex calls describe a closed contour.  For example, if
  the user wants to describe a quadrilateral, then gluTessVertex should	be
  called four times.  gluTessVertex can	only be	called between
  gluTessBeginContour and gluTessEndContour.

  data normally	points to a structure containing the vertex location, as well
  as other per-vertex attributes such as color and normal.  This pointer is
  passed back to the user through the GLU_TESS_VERTEX or GLU_TESS_VERTEX_DATA
  callback after tessellation (see the gluTessCallback reference page).

Example

  A quadrilateral with a triangular hole in it can be described	as follows:

  gluTessBeginPolygon(tobj, NULL);
   gluTessBeginContour(tobj);
     gluTessVertex(tobj, v1, v1);
     gluTessVertex(tobj, v2, v2);
     gluTessVertex(tobj, v3, v3);
     gluTessVertex(tobj, v4, v4);
   gluTessEndContour(tobj);
   gluTessBeginContour(tobj);
     gluTessVertex(tobj, v5, v5);
     gluTessVertex(tobj, v6, v6);
     gluTessVertex(tobj, v7, v7);
   gluTessEndContour(tobj); gluTessEndPolygon(tobj);

See Also

  gluTessBeginPolygon, gluNewTess, gluTessBeginContour,	gluTessCallback,
  gluTessProperty, gluTessNormal, gluTessEndPolygon

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


gluTessVertex.3gl

gluTessVertex.3gl (GLU versions 1.0 and 1.1)

Name

  gluTessVertex	- specify a vertex on a	polygon

C Specification

  void gluTessVertex( GLUtriangulatorObj *tobj,
              GLdouble v[3],
              void *data )

Parameters

  tobj	Specifies the tessellation object (created with	gluNewTess).

  v	Specifies the location of the vertex.

  data	Specifies an opaque pointer passed back	to the user with the vertex
    callback (as specified by gluTessCallback).

Description

  gluTessVertex	describes a vertex on a	polygon	that the user is defining.
  Successive gluTessVertex calls describe a closed contour.  For example, if
  the user wants to describe a quadrilateral, then gluTessVertex should	be
  called four times.  gluTessVertex can	only be	called between
  gluBeginPolygon and gluEndPolygon.

  data normally	points to a structure containing the vertex location, as well
  as other per-vertex attributes such as color and normal.  This pointer is
  passed back to the user through the GLU_VERTEX callback after	tessellation
  (see the gluTessCallback reference page).

Example

  A quadrilateral with a triangular hole in it can be described	as follows:

  gluBeginPolygon(tobj);
     gluTessVertex(tobj, v1, v1);
     gluTessVertex(tobj, v2, v2);
     gluTessVertex(tobj, v3, v3);
     gluTessVertex(tobj, v4, v4);
  gluNextContour(tobj, GLU_INTERIOR);
     gluTessVertex(tobj, v5, v5);
     gluTessVertex(tobj, v6, v6);
     gluTessVertex(tobj, v7, v7);
  gluEndPolygon(tobj);

See Also

  gluBeginPolygon, gluNewTess, gluNextContour, gluTessCallback


gluUnProject.3gl

Name

  gluUnProject - map window coordinates	to object coordinates

C Specification

  int gluUnProject( GLdouble winx,
            GLdouble winy,
            GLdouble winz,
            const GLdouble modelMatrix[16],
            const GLdouble projMatrix[16],
            const GLint	viewport[4],
            GLdouble *objx,
            GLdouble *objy,
            GLdouble *objz )

Parameters

  winx,	winy, winz
          Specify the window coordinates to be mapped.

  modelMatrix	  Specifies the	modelview matrix (as from a glGetDoublev
          call).

  projMatrix	  Specifies the	projection matrix (as from a glGetDoublev
          call).

  viewport	  Specifies the	viewport (as from a glGetIntegerv call).

  objx,	objy, objz
          Returns the computed object coordinates.

Description

  gluUnProject maps the	specified window coordinates into object coordinates
  using	modelMatrix, projMatrix, and viewport.	The result is stored in	objx,
  objy,	and objz.  A return value of GL_TRUE indicates success,	and GL_FALSE
  indicates failure.

See Also

  glGet, gluProject

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glVertex.3gl

Name

  glVertex2d, glVertex2f, glVertex2i, glVertex2s, glVertex3d, glVertex3f,
  glVertex3i, glVertex3s, glVertex4d, glVertex4f, glVertex4i, glVertex4s,
  glVertex2dv, glVertex2fv, glVertex2iv, glVertex2sv, glVertex3dv,
  glVertex3fv, glVertex3iv, glVertex3sv, glVertex4dv, glVertex4fv,
  glVertex4iv, glVertex4sv - specify a vertex

C Specification

  void glVertex2d( GLdouble x,
           GLdouble y )
  void glVertex2f( GLfloat x,
           GLfloat y )
  void glVertex2i( GLint x,
           GLint y )
  void glVertex2s( GLshort x,
           GLshort y )
  void glVertex3d( GLdouble x,
           GLdouble y,
           GLdouble z )
  void glVertex3f( GLfloat x,
           GLfloat y,
           GLfloat z )
  void glVertex3i( GLint x,
           GLint y,
           GLint z )
  void glVertex3s( GLshort x,
           GLshort y,
           GLshort z )
  void glVertex4d( GLdouble x,
           GLdouble y,
           GLdouble z,
           GLdouble w )
  void glVertex4f( GLfloat x,
           GLfloat y,
           GLfloat z,
           GLfloat w )
  void glVertex4i( GLint x,
           GLint y,
           GLint z,
           GLint w )
  void glVertex4s( GLshort x,
           GLshort y,
           GLshort z,
           GLshort w )

Parameters

  x, y,	z, w
       Specify x, y, z, and	w coordinates of a vertex.  Not	all
       parameters are present in all forms of the command.

C Specification

  void glVertex2dv( const GLdouble *v )
  void glVertex2fv( const GLfloat *v )
  void glVertex2iv( const GLint	*v )
  void glVertex2sv( const GLshort *v )
  void glVertex3dv( const GLdouble *v )
  void glVertex3fv( const GLfloat *v )
  void glVertex3iv( const GLint	*v )
  void glVertex3sv( const GLshort *v )
  void glVertex4dv( const GLdouble *v )
  void glVertex4fv( const GLfloat *v )
  void glVertex4iv( const GLint	*v )
  void glVertex4sv( const GLshort *v )

Parameters

  v    Specifies a pointer to an array of two, three, or four elements.	 The
       elements	of a two-element array are x and y; of a three-element array,
       x, y, and z; and	of a four-element array, x, y, z, and w.

Description

  glVertex commands are	used within glBegin/glEnd pairs	to specify point,
  line,	and polygon vertices.  The current color, normal, and texture
  coordinates are associated with the vertex when glVertex is called.

  When only x and y are	specified, z defaults to 0.0 and w defaults to 1.0.
  When x, y, and z are specified, w defaults to	1.0.

Notes

  Invoking glVertex outside of a glBegin/glEnd pair results in undefined
  behavior.

See Also

  glBegin, glCallList, glColor,	glEdgeFlag, glEvalCoord, glIndex, glMaterial,
  glNormal, glRect, glTexCoord

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glVertexPointerEXT.3gl

Name

  glVertexPointerEXT - define an array of vertex data

C Specification

  void glVertexPointerEXT( GLint size,
               GLenum type,
               GLsizei stride,
               GLsizei count,
               const GLvoid	*pointer )

Parameters

  size	   Specifies the number	of coordinates per vertex, must	be 2,3,	or 4.

  type	   Specifies the data type of each coordinate in the array.  Symbolic
       constants GL_SHORT, GL_INT, GL_FLOAT, or GL_DOUBLE_EXT are
       accepted.

  stride   Specifies the byte offset between consecutive vertexes. If stride
       is 0	the vertexes are understood to be tightly packed in the
       array.

  count	   Specifies the number	of vertexes, counting from the first, that
       are static.

  pointer  Specifies a pointer to the first coordinate of the first vertex in
       the array.

Description

  glVertexPointerEXT specifies the location and data format of an array of
  vertex coordinates to use when rendering using the vertex array extension.
  size specifies the number of coordinates per vertex and type the
  data type of the coordinates. stride gives the byte stride from one vertex
  to the next allowing vertexes and attributes to be packed into a single
  array or stored in separate arrays. (Single-array storage may be more
  efficient on some implementations.) count indicates the number of array
  elements (counting from the first) that are static. Static elements may be
  modified by the application, but once they are modified, the application
  must explicitly respecify the array before using it for any rendering. When
  a vertex array is specified, size, type, stride, count, and pointer are saved
  as client-side state, and static array elements may be cached by the
  implementation.

  The vertex array is enabled and disabled using glEnable and glDisable	with
  the argument GL_VERTEX_ARRAY_EXT. If enabled,	the vertex array is used when
  glDrawArraysEXT or glArrayElementEXT is called.

  Use glDrawArraysEXT to define	a sequence of primitives (all of the same
  type)	from pre-specified vertex and vertex attribute arrays.	Use
  glArrayElementEXT to specify primitives by indexing vertexes and vertex
  attributes.

Notes

  Non-static array elements are	not accessed until glArrayElementEXT or
  glDrawArraysEXT is executed.

  By default the vertex	array is disabled and it won't be accessed when
  glArrayElementEXT or glDrawArraysEXT is called.

  Although it is not an error to call glVertexPointerEXT between the
  execution of glBegin and the corresponding execution of glEnd, the results
  are undefined.

  glVertexPointerEXT will typically be implemented on the client side with no
  protocol.

  Since	the vertex array parameters are	client side state, they	are not	saved
  or restored by glPushAttrib and glPopAttrib.

  glVertexPointerEXT commands are not entered into display lists.

  glVertexPointerEXT is	part of	the EXT_vertex_array extension, not
  part of the core GL command set. If "GL_EXT_vertex_array" is
  included in the string returned by glGetString, when called with argument
  GL_EXTENSIONS, extension EXT_vertex_array is supported.

Errors

  GL_INVALID_VALUE is generated	if size	is not 2, 3, or	4.

  GL_INVALID_ENUM is generated if type is is not an accepted value.

  GL_INVALID_VALUE is generated	if stride or count is negative.

Associated Gets

  glIsEnabled with argument GL_VERTEX_ARRAY_EXT
  glGet	with argument GL_VERTEX_ARRAY_SIZE_EXT
  glGet	with argument GL_VERTEX_ARRAY_TYPE_EXT
  glGet	with argument GL_VERTEX_ARRAY_STRIDE_EXT
  glGet	with argument GL_VERTEX_ARRAY_COUNT_EXT
  glGetPointervEXT with	argument GL_VERTEX_ARRAY_POINTER_EXT

See Also

  glArrayElementEXT, glColorPointerEXT,	glDrawArraysEXT,
  glEdgeFlagPointerEXT,	glGetPointervEXT, glIndexPointerEXT,
  glNormalPointerEXT, glTexCoordPointerEXT, glEnable

Introduction | Alphabetic | Specification

Last Edited: Fri Feb 24, 1995

AFV


glViewport.3gl

Name

  glViewport - set the viewport

C Specification

  void glViewport( GLint x,
           GLint y,
           GLsizei width,
           GLsizei height )

Parameters

  x, y Specify the lower left corner of	the viewport rectangle,	in pixels.
       The default is (0,0).

  width, height
       Specify the width and height, respectively, of the viewport.  When a
       GL context is first attached to a window, width and height are set to
       the dimensions of that window.

Description

  glViewport specifies the affine transformation of x and y from normalized
  device coordinates to	window coordinates.  Let (x  , y  ) be normalized
                                                   nd   nd
  device coordinates.  Then the	window coordinates x , y ) are computed as
  follows:					    w	w

                                        width
                x  = (x  +1)----- + x
                 w	   nd	  2

                                        height
                y  = (y  +1)------ + y
                 w	   nd	   2

  Viewport width and height are	silently clamped to a range that depends on
  the implementation.  This range is queried by	calling	glGet with argument
  GL_MAX_VIEWPORT_DIMS.

Errors

  GL_INVALID_VALUE is generated	if either width	or height is negative.

  GL_INVALID_OPERATION is generated if glViewport is executed between the
  execution of glBegin and the corresponding execution of glEnd.

Associated Gets

  glGet	with argument GL_VIEWPORT
  glGet	with argument GL_MAX_VIEWPORT_DIMS

See Also

  glDepthRange

Introduction | Alphabetic | Specification

Last Edited: Fri, May 26, 1995

AFV


glXChooseVisual.3gl

Name

  glXChooseVisual - return a visual that matches specified attributes

C Specification

  XVisualInfo* glXChooseVisual(	Display	*dpy,
                int screen,
                int *attribList	)

Parameters

  dpy	      Specifies	the connection to the X	server.

  screen      Specifies	the screen number.

  attribList  Specifies	a list of Boolean attributes and integer
          attribute/value pairs.  The last attribute must be None.

Description

  glXChooseVisual returns a pointer to an XVisualInfo structure	describing
  the visual that best meets a minimum specification.  The Boolean GLX
  attributes of	the visual that	is returned will match the specified values,
  and the integer GLX attributes will meet or exceed the specified minimum
  values.  If all other	attributes are equivalent, then	TrueColor and
  PseudoColor visuals have priority over DirectColor and StaticColor visuals,
  respectively.	 If no conforming visual exists, NULL is returned.  To free
  the data returned by this function, use XFree.

  All Boolean GLX attributes default to	False except GLX_USE_GL, which
  defaults to True.  All integer GLX attributes	default	to zero.  Default
  specifications are superseded	by attributes included in attribList.
  Boolean attributes included in attribList are	understood to be True.
  Integer attributes are followed immediately by the corresponding desired or
  minimum value.  The list must	be terminated with None.

  The interpretations of the various GLX visual	attributes are as follows:

  GLX_USE_GL		Ignored.  Only visuals that can	be rendered with GLX
            are considered.

  GLX_BUFFER_SIZE	Must be	followed by a nonnegative integer that
            indicates the desired color index buffer size.	The
            smallest index buffer of at least the specified	size
            is preferred.  Ignored if GLX_RGBA is asserted.

  GLX_LEVEL		Must be	followed by an integer buffer-level
            specification.	This specification is honored
            exactly.  Buffer level zero corresponds	to the
            default	frame buffer of	the display.  Buffer level
            one is the first overlay frame buffer, level two the
            second overlay frame buffer, and so on.	 Negative
            buffer levels correspond to underlay frame buffers.

  GLX_RGBA		If present, only TrueColor and DirectColor visuals
            are considered.	 Otherwise, only PseudoColor and
            StaticColor visuals are	considered.

  GLX_DOUBLEBUFFER	If present, only double-buffered visuals are
            considered.   Otherwise, only single-buffered visuals
            are considered.

  GLX_STEREO		If present, only stereo	visuals	are considered.
            Otherwise, only	monoscopic visuals are considered.

  GLX_AUX_BUFFERS	Must be	followed by a nonnegative integer that
            indicates the desired number of	auxiliary buffers.
            Visuals	with the  smallest number of auxiliary
            buffers	that meets or exceeds the specified number
            are preferred.

  GLX_RED_SIZE		Must be	followed by a nonnegative minimum size
            specification.	If this	value is zero, the smallest
            available red buffer is	preferred.  Otherwise, the
            largest	available red buffer of	at least the minimum
            size is	preferred.

  GLX_GREEN_SIZE	Must be	followed by a nonnegative minimum size
            specification.	If this	value is zero, the smallest
            available green	buffer is preferred.  Otherwise, the
            largest	available green	buffer of at least the
            minimum	size is	preferred.

  GLX_BLUE_SIZE		Must be	followed by a nonnegative minimum size
            specification.	If this	value is zero, the smallest
            available blue buffer is preferred.  Otherwise,	the
            largest	available blue buffer of at least the minimum
            size is	preferred.

  GLX_ALPHA_SIZE	Must be	followed by a nonnegative minimum size
            specification.	If this	value is zero, the smallest
            available alpha	buffer is preferred.  Otherwise, the
            largest	available alpha	buffer of at least the
            minimum	size is	preferred.

  GLX_DEPTH_SIZE	Must be	followed by a nonnegative minimum size
            specification.	If this	value is zero, visuals with
            no depth buffer	are preferred.	Otherwise, the
            largest	available depth	buffer of at least the
            minimum	size is	preferred.

  GLX_STENCIL_SIZE	Must be	followed by a nonnegative integer that
            indicates the desired number of	stencil	bitplanes.
            The smallest stencil buffer of at least	the specified
            size is	preferred.  If the desired value is zero,
            visuals	with no	stencil	buffer are preferred.

  GLX_ACCUM_RED_SIZE	Must be	followed by a nonnegative minimum size
            specification.	If this	value is zero, visuals with
            no red accumulation buffer are preferred.  Otherwise,
            the largest possible red accumulation buffer of	at
            least the minimum size is preferred.

  GLX_ACCUM_GREEN_SIZE	Must be	followed by a nonnegative minimum size
            specification.	If this	value is zero, visuals with
            no green accumulation buffer are preferred.
            Otherwise, the largest possible	green accumulation
            buffer of at least the minimum size is preferred.

  GLX_ACCUM_BLUE_SIZE	Must be	followed by a nonnegative minimum size
            specification.	If this	value is zero, visuals with
            no blue	accumulation buffer are	preferred.
            Otherwise, the largest possible	blue accumulation
            buffer of at least the minimum size is preferred.

  GLX_ACCUM_ALPHA_SIZE	Must be	followed by a nonnegative minimum size
            specification.	If this	value is zero, visuals with
            no alpha accumulation buffer are preferred.
            Otherwise, the largest possible	alpha accumulation
            buffer of at least the minimum size is preferred.

Examples

  attribList =
          {GLX_RGBA, GLX_RED_SIZE, 4, GLX_GREEN_SIZE, 4, GLX_BLUE_SIZE,
          4, None};

  Specifies a single-buffered RGB visual in the	normal frame buffer, not an
  overlay or underlay buffer.  The returned visual supports at least four
  bits each of red, green, and blue, and possibly no bits of alpha.  It	does
  not support color index mode,	double-buffering, or stereo display.  It may
  or may not have one or more auxiliary	color buffers, a depth buffer, a
  stencil buffer, or an	accumulation buffer.

Notes

  XVisualInfo is defined in Xutil.h.  It is a structure	that includes visual,
  visualID, screen, and	depth elements.

  glXChooseVisual is implemented as a client-side utility using	only
  XGetVisualInfo and glXGetConfig.  Calls to these two routines	can be used
  to implement selection algorithms other than the generic one implemented by
  glXChooseVisual.

  GLX implementers are strongly	discouraged, but not proscribed, from
  changing the selection algorithm used	by glXChooseVisual.  Therefore,
  selections may change	from release to	release	of the client-side library.

  There	is no direct filter for	picking	only visuals that support GLXPixmaps.
  GLXPixmaps are supported for visuals whose GLX_BUFFER_SIZE.  is one of the
  Pixmap depths	supported by the X server.

Errors

  NULL is returned if an undefined GLX attribute is encountered	in
  attribList.

See Also

  glXCreateContext, glXGetConfig

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXCopyContext.3gl

Name

  glXCopyContext - copy	state from one rendering context to another

C Specification

  void glXCopyContext( Display *dpy,
               GLXContext src,
               GLXContext dst,
               GLuint mask )

Parameters

  dpy	Specifies the connection to the	X server.

  src	Specifies the source context.

  dst	Specifies the destination context.

  mask	Specifies which	portions of src	state are to be	copied to dst.

Description

  glXCopyContext copies	selected groups	of state variables from	src to dst.
  mask indicates which groups of state variables are to	be copied.  mask
  contains the bitwise OR of the same symbolic names that are passed to	the
  OpenGL command glPushAttrib.	The single symbolic constant
  GL_ALL_ATTRIB_BITS can be used to copy the maximum possible portion of
  rendering state.

  The copy can be done only if the renderers named by src and dst share	an
  address space.  Two rendering	contexts share an address space	if both	are
  nondirect using the same server, or if both are direct and owned by a
  single process.  Note	that in	the nondirect case it is not necessary for
  the calling threads to share an address space, only for their	related
  rendering contexts to	share an address space.

  Not all values for OpenGL state can be copied.  For example, pixel pack and
  unpack state,	render mode state, and select and feedback state are not
  copied.  The state that can be copied	is exactly the state that is
  manipulated by OpenGL	command	 glPushAttrib.

  An implicit glFlush is done by glXCopyContext	if src is the current context
  for the calling thread.

  If src is not	the current context for	the thread issuing the request,	then
  the state of the src context is undefined.

Notes

  Two rendering	contexts share an address space	if both	are nondirect using
  the same server, or if both are direct and owned by a	single process.

  A process is a single	execution environment, implemented in a	single
  address space, consisting of one or more threads.

  A thread is one of a set of subprocesses that	share a	single address space,
  but maintain separate	program	counters, stack	spaces,	and other related
  global data.	A thread that is the only member of its	subprocess group is
  equivalent to	a process.

Errors

  BadMatch is generated	if rendering contexts src and dst do not share an
  address space	or were	not created with respect to the	same screen.

  BadAccess is generated if dst	is current to any thread (including the
  calling thread) at the time glXCopyContext is	called.

  GLXBadCurrentWindow is generated if src is the current context and the
  current drawable is a	window that is no longer valid.

  GLXBadContext	is generated if	either src or dst is not a valid GLX context.

  BadValue is generated	if undefined mask bits are specified.

See Also

  glPushAttrib,	glXCreateContext, glXIsDirect

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXCreateContext.3gl

Name

  glXCreateContext - create a new GLX rendering	context

C Specification

  GLXContext glXCreateContext( Display *dpy,
                   XVisualInfo *vis,
                   GLXContext shareList,
                   Bool direct )

Parameters

  dpy	     Specifies the connection to the X server.

  vis	     Specifies the visual that defines the frame buffer	resources
         available to the rendering	context.  It is	a pointer to an
         XVisualInfo structure, not	a visual ID or a pointer to a Visual.

  shareList  Specifies the context with	which to share display lists.  NULL
         indicates that no sharing is to take place.

  direct     Specifies whether rendering is to be done with a direct
         connection	to the graphics	system if possible (True) or through
         the X server (False).

Description

  glXCreateContext creates a GLX rendering context and returns its handle.
  This context can be used to render into both windows and GLX pixmaps.	 If
  glXCreateContext fails to create a rendering context,	NULL is	returned.

  If direct is True, then a direct rendering context is	created	if the
  implementation supports direct rendering and the connection is to an X
  server that is local.	 If direct is False, then a rendering context that
  renders through the X	server is always created.  Direct rendering provides
  a performance	advantage in some implementations.  However, direct rendering
  contexts cannot be shared outside a single process, and they cannot be used
  to render to GLX pixmaps.

  If shareList is not NULL, then all display-list indexes and definitions are
  shared by context shareList and by the newly created context.	 An arbitrary
  number of contexts can share a single	display-list space.  However, all
  rendering contexts that share	a single display-list space must themselves
  exist	in the same address space.  Two	rendering contexts share an address
  space	if both	are nondirect using the	same server, or	if both	are direct
  and owned by a single	process.  Note that in the nondirect case, it is not
  necessary for	the calling threads to share an	address	space, only for	their
  related rendering contexts to	share an address space.

Notes

  XVisualInfo is defined in Xutil.h.  It is a structure	that includes visual,
  visualID, screen, and	depth elements.

  A process is a single	execution environment, implemented in a	single
  address space, consisting of one or more threads.

  A thread is one of a set of subprocesses that	share a	single address space,
  but maintain separate	program	counters, stack	spaces,	and other related
  global data.	A thread that is the only member of its	subprocess group is
  equivalent to	a process.

Errors

  NULL is returned if execution	fails on the client side.

  BadMatch is generated	if the context to be created would not share the
  address space	or the screen of the context specified by shareList.

  BadValue is generated	if vis is not a	valid visual (e.g., if the GLX
  implementation does not support it).

  GLXBadContext	is generated if	shareList is not a GLX context and is not
  NULL.

  BadAlloc is generated	if the server does not have enough resources to
  allocate the new context.

See Also

  glXDestroyContext, glXGetConfig, glXIsDirect,	glXMakeCurrent

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXCreateGLXPixmap.3gl

Name

  glXCreateGLXPixmap - create an off-screen GLX	rendering area

C Specification

  GLXPixmap glXCreateGLXPixmap(	Display	*dpy,
                XVisualInfo *vis,
                Pixmap pixmap )

Parameters

  dpy	  Specifies the	connection to the X server.

  vis	  Specifies the	visual that defines the	structure of the rendering
      area.	 It is a pointer to an XVisualInfo structure, not a visual ID
      or a pointer to a Visual.

  pixmap  Specifies the	X pixmap that will be used as the front	left color
      buffer of the	off-screen rendering area.

Description

  glXCreateGLXPixmap creates an	off-screen rendering area and returns its
  XID.	Any GLX	rendering context that was created with	respect	to vis can be
  used to render into this off-screen area.  Use glXMakeCurrent	to associate
  the rendering	area with a GLX	rendering context.

  The X	pixmap identified by pixmap is used as the front left buffer of	the
  resulting off-screen rendering area.	All other buffers specified by vis,
  including color buffers other	than the front left buffer, are	created
  without externally visible names.  GLX pixmaps with double-buffering are
  supported.  However, glXSwapBuffers is ignored by these pixmaps.

  Direct rendering contexts cannot be used to render into GLX pixmaps.

Notes

  XVisualInfo is defined in Xutil.h.  It is a structure	that includes visual,
  visualID, screen, and	depth elements.

Errors

  BadMatch is generated	if the depth of	pixmap does not	match the
  GLX_BUFFER_SIZE value	of vis,	or if pixmap was not created with respect to
  the same screen as vis.

  BadValue is generated	if vis is not a	valid XVisualInfo pointer (e.g., if
  the GLX implementation does not support this visual).

  BadPixmap is generated if pixmap is not a valid pixmap.

  BadAlloc is generated	if the server cannot allocate the GLX pixmap.

See Also

  glXCreateContext, glXIsDirect, glXMakeCurrent

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXDestroyContext.3gl

Name

  glXDestroyContext - destroy a	GLX context

C Specification

  void glXDestroyContext( Display *dpy,
              GLXContext ctx )

Parameters

  dpy  Specifies the connection	to the X server.

  ctx  Specifies the GLX context to be destroyed.

Description

  If GLX rendering context ctx is not current to any thread,
  glXDestroyContext  destroys it immediately.  Otherwise, ctx is destroyed
  when it becomes not current to any thread.  In either	case, the resource ID
  referenced by	ctx is freed immediately.

Errors

  GLXBadContext	is generated if	ctx is not a valid GLX context.

See Also

  glXCreateContext, glXMakeCurrent

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXDestroyGLXPixmap.3gl

Name

  glXDestroyGLXPixmap -	destroy	a GLX pixmap

C Specification

  void glXDestroyGLXPixmap( Display *dpy,
                GLXPixmap pix )

Parameters

  dpy  Specifies the connection	to the X server.

  pix  Specifies the GLX pixmap	to be destroyed.

Description

  If GLX pixmap	pix is not current to any client, glXDestroyGLXPixmap
  destroys it immediately.  Otherwise, pix is destroyed	when it	becomes	not
  current to any client.  In either case, the resource ID is freed
  immediately.

Errors

  GLXBadPixmap is generated if pix is not a valid GLX pixmap.

See Also

  glXCreateGLXPixmap, glXMakeCurrent

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXGetConfig.3gl

Name

  glXGetConfig - return	information about GLX visuals

C Specification

  int glXGetConfig( Display *dpy,
            XVisualInfo	*vis,
            int	attrib,
            int	*value )

Parameters

  dpy	  Specifies the	connection to the X server.

  vis	  Specifies the	visual to be queried.  It is a pointer to an
      XVisualInfo structure, not a visual ID or a pointer to a Visual.

  attrib  Specifies the	visual attribute to be returned.

  value	  Returns the requested	value.

Description

  glXGetConfig sets value to the attrib	value of windows or GLX	pixmaps
  created with respect to vis.	glXGetConfig returns an	error code if it
  fails	for any	reason.	 Otherwise, zero is returned.

  attrib is one	of the following:

  GLX_USE_GL		True if	OpenGL rendering is supported by this visual,
            False otherwise.

  GLX_BUFFER_SIZE	Number of bits per color buffer.  For RGBA visuals,
            GLX_BUFFER_SIZE	is the sum of GLX_RED_SIZE,
            GLX_GREEN_SIZE,	GLX_BLUE_SIZE, and GLX_ALPHA_SIZE.
            For color index	visuals, GLX_BUFFER_SIZE is the	size
            of the color indexes.

  GLX_LEVEL		Frame buffer level of the visual.  Level zero is the
            default	frame buffer.  Positive	levels correspond to
            frame buffers that overlay the default buffer, and
            negative levels	correspond to frame buffers that
            underlay the default buffer.

  GLX_RGBA		True if	color buffers store red, green,	blue, and
            alpha values, False if they store color	indexes.

  GLX_DOUBLEBUFFER	True if	color buffers exist in front/back pairs	that
            can be swapped,	False otherwise.

  GLX_STEREO		True if	color buffers exist in left/right pairs,
            False otherwise.

  GLX_AUX_BUFFERS	Number of auxiliary color buffers that are available.
            Zero indicates that no auxiliary color buffers exist.

  GLX_RED_SIZE		Number of bits of red stored in	each color buffer.
            Undefined if GLX_RGBA is False.

  GLX_GREEN_SIZE	Number of bits of green	stored in each color buffer.
            Undefined if GLX_RGBA is False.

  GLX_BLUE_SIZE		Number of bits of blue stored in each color buffer.
            Undefined if GLX_RGBA is False.

  GLX_ALPHA_SIZE	Number of bits of alpha	stored in each color buffer.
            Undefined if GLX_RGBA is False.

  GLX_DEPTH_SIZE	Number of bits in the depth buffer.

  GLX_STENCIL_SIZE	Number of bits in the stencil buffer.

  GLX_ACCUM_RED_SIZE	Number of bits of red stored in	the accumulation
            buffer.

  GLX_ACCUM_GREEN_SIZE	Number of bits of green	stored in the accumulation
            buffer.

  GLX_ACCUM_BLUE_SIZE	Number of bits of blue stored in the accumulation
            buffer.

  GLX_ACCUM_ALPHA_SIZE	Number of bits of alpha	stored in the accumulation
            buffer.

  The X	protocol allows	a single visual	ID to be instantiated with different
  numbers of bits per pixel.  Windows or GLX pixmaps that will be rendered
  with OpenGL, however,	must be	instantiated with a color buffer depth of
  GLX_BUFFER_SIZE.

  Although a GLX implementation	can export many	visuals	that support OpenGL
  rendering, it	must support at	least two.  One	is an RGBA visual with at
  least	one color buffer, a stencil buffer of at least 1 bit, a	depth buffer
  of at	least 12 bits, and an accumulation buffer.  Alpha bitplanes are
  optional in this visual.  However, its color buffer size must	be as great
  as that of the deepest TrueColor, DirectColor,  PseudoColor, or StaticColor
  visual supported on level zero, and it must itself be	made available on
  level	zero.

  The other required visual is a color index one with at least one color
  buffer, a stencil buffer of at least 1 bit, and a depth buffer of at least
  12 bits.  This visual	must have as many color	bitplanes as the deepest
  PseudoColor or StaticColor visual supported on level zero, and it must
  itself be made available on level zero.

  Applications are best	written	to select the visual that most closely meets
  their	requirements.  Creating	windows	or GLX pixmaps with unnecessary
  buffers can result in	reduced	rendering performance as well as poor
  resource allocation.

Notes

  XVisualInfo is defined in Xutil.h.  It is a structure	that includes visual,
  visualID, screen, and	depth elements.

Errors

  GLX_NO_EXTENSION is returned if dpy does not support the GLX extension.

  GLX_BAD_SCREEN is returned if	the screen of vis does not correspond to a
  screen.

  GLX_BAD_ATTRIB is returned if	attrib is not a	valid GLX attribute.

  GLX_BAD_VISUAL is returned if	vis doesn't support GLX	and an attribute
  other	than GLX_USE_GL	is requested.

See Also

  glXChooseVisual, glXCreateContext

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXGetCurrentContext.3gl

Name

  glXGetCurrentContext - return	the current context

C Specification

  GLXContext glXGetCurrentContext( void	)

Description

  glXGetCurrentContext returns the current context, as specified by
  glXMakeCurrent.  If there is no current context, NULL	is returned.

  glXGetCurrentContext returns client-side information.	 It does not make a
  round	trip to	the server.

See Also

  glXCreateContext, glXMakeCurrent

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXGetCurrentDrawable.3gl

Name

  glXGetCurrentDrawable	- return the current drawable

C Specification

  GLXDrawable glXGetCurrentDrawable( void )

Description

  glXGetCurrentDrawable	returns	the current drawable, as specified by
  glXMakeCurrent.  If there is no current drawable, None is returned.

  glXGetCurrentDrawable	returns	client-side information.  It does not make a
  round	trip to	the server.

See Also

  glXCreateGLXPixmap, glXMakeCurrent

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXIntro.3gl

Name

  glXIntro - Introduction to OpenGL in the X window system

OVERVIEW
  OpenGL is a high-performance 3-D-oriented renderer.  It is available in the
  X window system through the GLX extension.  Use glXQueryExtension and
  glXQueryVersion to establish whether the GLX extension is supported by an X
  server, and if so, what version is supported.

  GLX extended servers make a subset of	their visuals available	for OpenGL
  rendering.  Drawables	created	with these visuals can also be rendered	using
  the core X renderer and with the renderer of any other X extension that is
  compatible with all core X visuals.

  GLX extends drawables	with several buffers other than	the standard color
  buffer.  These buffers include back and auxiliary color buffers, a depth
  buffer, a stencil buffer, and	a color	accumulation buffer.  Some or all are
  included in each X visual that supports OpenGL.

  To render using OpenGL into an X drawable, you must first choose a visual
  that defines the required OpenGL buffers.  glXChooseVisual can be used to
  simplify selecting a compatible visual.  If more control of the selection
  process is required, use XGetVisualInfo and glXGetConfig to select among
  all the available visuals.

  Use the selected visual to create both a GLX context and an X	drawable.
  GLX contexts are created with	glXCreateContext, and drawables	are created
  with either XCreateWindow or glXCreateGLXPixmap.  Finally, bind the context
  and the drawable together using glXMakeCurrent.  This	context/drawable pair
  becomes the current context and current drawable, and	it is used by all
  OpenGL commands until	glXMakeCurrent is called with different	arguments.

  Both core X and OpenGL commands can be used to operate on the	current
  drawable.  The X and OpenGL command streams are not synchronized, however,
  except at explicitly created boundaries generated by calling glXWaitGL,
  glXWaitX, XSync, and glFlush.

Examples

  Below	is the minimum code required to	create an RGBA-format, OpenGL-
  compatible X window and clear	it to yellow.  The code	is correct, but	it
  does not include any error checking.	Return values dpy, vi, cx, cmap, and
  win should all be tested.

  #include <GL/glx.h> #include <GL/gl.h> #include <unistd.h>

  static int attributeList[] = { GLX_RGBA, None	};

  static Bool WaitForNotify(Display *d,	XEvent *e, char	*arg) {
      return (e->type == MapNotify) && (e->xmap.window == (Window)arg);	}

  int main(int argc, char **argv) {
      Display *dpy;
      XVisualInfo *vi;
      Colormap cmap;
      XSetWindowAttributes swa;
      Window win;
      GLXContext cx;
      XEvent event;

      /* get a connection */
      dpy = XOpenDisplay(0);

      /* get an	appropriate visual */
      vi = glXChooseVisual(dpy,	DefaultScreen(dpy), attributeList);

      /* create	a GLX context */
      cx = glXCreateContext(dpy, vi, 0,	GL_TRUE);

      /* create	a color	map */
      cmap = XCreateColormap(dpy, RootWindow(dpy, vi->screen),
                 vi->visual, AllocNone);

      /* create	a window */
      swa.colormap = cmap;
      swa.border_pixel = 0;
      swa.event_mask = StructureNotifyMask;
      win = XCreateWindow(dpy, RootWindow(dpy, vi->screen), 0, 0, 100, 100,
              0, vi->depth,	InputOutput, vi->visual,
              CWBorderPixel|CWColormap|CWEventMask,	&swa);
      XMapWindow(dpy, win);
      XIfEvent(dpy, &event, WaitForNotify, (char*)win);

      /* connect the context to	the window */
      glXMakeCurrent(dpy, win, cx);

      /* clear the buffer */
      glClearColor(1,1,0,1);
      glClear(GL_COLOR_BUFFER_BIT);
      glFlush();

      /* wait a	while */
      sleep(10); }

Notes

  A color map must be created and passed to XCreateWindow.  See	the example
  code above.

  A GLX	context	must be	created	and attached to	an X drawable before OpenGL
  commands can be executed.  OpenGL commands issued while no context/drawable
  pair is current are ignored.

  Exposure events indicate that	all buffers associated with the	specified
  window may be	damaged	and should be repainted.  Although certain buffers of
  some visuals on some systems may never require repainting (the depth
  buffer, for example),	it is incorrect	to code	assuming that these buffers
  will not be damaged.

  GLX commands manipulate XVisualInfo structures rather	than pointers to
  visuals or visual IDs.  XVisualInfo structures contain visual, visualID,
  screen, and depth elements, as well as other X-specific information.

See Also

  glFinish, glFlush, glXChooseVisual, glXCopyContext, glXCreateContext,
  glXCreateGLXPixmap, glXDestroyContext, glXGetConfig, glXIsDirect,
  glXMakeCurrent, glXQueryExtension, glXQueryVersion, glXSwapBuffers,
  glXUseXFont, glXWaitGL, glXWaitX, XCreateColormap, XCreateWindow, XSync

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXIsDirect.3gl

Name

  glXIsDirect -	indicate whether direct	rendering is enabled

C Specification

  Bool glXIsDirect( Display *dpy,
            GLXContext ctx )

Parameters

  dpy  Specifies the connection	to the X server.

  ctx  Specifies the GLX context that is being queried.

Description

  glXIsDirect returns True if ctx is a direct rendering	context, False
  otherwise.  Direct rendering contexts	pass rendering commands	directly from
  the calling process's	address	space to the rendering system, bypassing the
  X server.  Nondirect rendering contexts pass all rendering commands to the
  X server.

Errors

  GLXBadContext	is generated if	ctx is not a valid GLX context.

See Also

  glXCreateContext

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXMakeCurrent.3gl

Name

  glXMakeCurrent - attach a GLX	context	to a window or a GLX pixmap

C Specification

  Bool glXMakeCurrent( Display *dpy,
               GLXDrawable drawable,
               GLXContext ctx )

Parameters

  dpy	    Specifies the connection to	the X server.

  drawable  Specifies a	GLX drawable.  Must be either an X window ID or	a GLX
        pixmap ID.

  ctx	    Specifies a	GLX rendering context that is to be attached to
        drawable.

Description

  glXMakeCurrent does two things: It makes ctx the current GLX rendering
  context of the calling thread, replacing the previously current context if
  there	was one, and it	attaches ctx to	a GLX drawable,	either a window	or a
  GLX pixmap.  As a result of these two	actions, subsequent OpenGL rendering
  calls	use rendering context ctx to modify GLX	drawable drawable.  Because
  glXMakeCurrent always	replaces the current rendering context with ctx,
  there	can be only one	current	context	per thread.

  Pending commands to the previous context, if any, are	flushed	before it is
  released.

  The first time ctx is	made current to	any thread, its	viewport is set	to
  the full size	of drawable.  Subsequent calls by any thread to
  glXMakeCurrent with ctx have no effect on its	viewport.

  To release the current context without assigning a new one, call
  glXMakeCurrent with drawable and ctx set to None and NULL respectively.

  glXMakeCurrent returns True if it is successful, False otherwise.  If	False
  is returned, the previously current rendering	context	and drawable (if any)
  remain unchanged.

Notes

  A process is a single-execution environment, implemented in a	single
  address space, consisting of one or more threads.

  A thread is one of a set of subprocesses that	share a	single address space,
  but maintain separate	program	counters, stack	spaces,	and other related
  global data.	A thread that is the only member of its	subprocess group is
  equivalent to	a process.

Errors

  BadMatch is generated	if drawable was	not created with the same X screen
  and visual as	ctx.  It is also generated if drawable is None and ctx is not
  None.

  BadAccess is generated if ctx	was current to another thread at the time
  glXMakeCurrent was called.

  GLXBadDrawable is generated if drawable is not a valid GLX drawable.

  GLXBadContext	is generated if	ctx is not a valid GLX context.

  GLXBadContextState is	generated if glXMakeCurrent is called between a
  glBegin and the corresponding	call to	glEnd.

  GLXBadContextState is	also generated if the rendering	context	current	to
  the calling thread has OpenGL	renderer state GL_FEEDBACK or GL_SELECT.

  GLXBadCurrentWindow is generated if there are	pending	OpenGL commands	for
  the previous context and the current drawable	is a window that is no longer
  valid.

  BadAlloc may be generated if the server has delayed allocation of ancillary
  buffers until	glXMakeCurrent is called, only to find that it has
  insufficient resources to complete the allocation.

See Also

  glXCreateContext, glXCreateGLXPixmap

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXQueryExtension.3gl

Name

  glXQueryExtension - indicate whether the GLX extension is supported

C Specification

  Bool glXQueryExtension( Display *dpy,
              int *errorBase,
              int *eventBase )

Parameters

  dpy	     Specifies the connection to the X server.

  errorBase  Returns the base error code of the	GLX server extension.

  eventBase  Returns the base event code of the	GLX server extension.

Description

  glXQueryExtension returns True if the	X server of connection dpy supports
  the GLX extension, False otherwise.  If True is returned, then errorBase
  and eventBase	return the error base and event	base of	the GLX	extension.
  Otherwise, errorBase and eventBase are unchanged.

  errorBase and	eventBase do not return	values if they are specified as	NULL.

Notes

  eventBase is included	for future extensions.	GLX does not currently define
  any events.

See Also

  glXQueryVersion

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXQueryVersion.3gl

Name

  glXQueryVersion - return the version numbers of the GLX extension

C Specification

  Bool glXQueryVersion(	Display	*dpy,
            int *major,
            int *minor )

Parameters

  dpy	 Specifies the connection to the X server.

  major	 Returns the major version number of the GLX server extension.

  minor	 Returns the minor version number of the GLX server extension.

Description

  glXQueryVersion returns the major and minor version numbers of the GLX
  extension available on the connection dpy. The client library and the
  server implementations must have the same major version number or else
  glXQueryVersion will return False. The minor version that is returned is
  the minimum of the two minor version numbers.

  major and minor do not return values if they are specified as NULL.

Errors

  glXQueryVersion returns False	if it fails, True otherwise.  major and	minor
  are not updated when False is	returned.

See Also

  glXQueryExtension

Introduction | Alphabetic | Specification

Last Edited: Thu Mar 16, 1995

AFV


glXSwapBuffers.3gl

Name

  glXSwapBuffers - make	back buffer visible

C Specification

  void glXSwapBuffers( Display *dpy,
               GLXDrawable drawable )

Parameters

  dpy	    Specifies the connection to	the X server.

  drawable  Specifies the window whose buffers are to be swapped.

Description

  glXSwapBuffers promotes the contents of the back buffer of drawable to
  become the contents of the front buffer of drawable.	The contents of	the
  back buffer then become undefined.  The update typically takes place during
  the vertical retrace of the monitor, rather than immediately after
  glXSwapBuffers is called.  All GLX rendering contexts	share the same notion
  of which are front buffers and which are back	buffers.

  An implicit glFlush is done by glXSwapBuffers	before it returns.
  Subsequent OpenGL commands can be issued immediately after calling
  glXSwapBuffers, but are not executed until the buffer	exchange is
  completed.

  If drawable was not created with respect to a	double-buffered	visual,
  glXSwapBuffers has no	effect,	and no error is	generated.

Notes

  Synchronization of multiple GLX contexts rendering to	the same double-
  buffered window is the responsibility	of the clients.	 The X
  Synchronization Extension can	be used	to facilitate such cooperation.

Errors

  GLXBadDrawable is generated if drawable is not a valid GLX drawable.

  GLXBadCurrentWindow is generated if dpy and drawable are respectively	the
  display and drawable associated with the current context of the calling
  thread, and drawable identifies a window that	is no longer valid.

See Also

  glFlush

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXUseXFont.3gl

Name

  glXUseXFont -	create bitmap display lists from an X font

C Specification

  void glXUseXFont( Font font,
            int	first,
            int	count,
            int	listBase )

Parameters

  font	    Specifies the font from which character glyphs are to be taken.

  first	    Specifies the index	of the first glyph to be taken.

  count	    Specifies the number of glyphs to be taken.

  listBase  Specifies the index	of the first display list to be	generated.

Description

  glXUseXFont generates	count display lists, named listBase through
  listBase+count-1, each containing a single glBitmap command.	The
  parameters of	the glBitmap command of	display	list listBase+i	are derived
  from glyph first+i.  Bitmap parameters xorig,	yorig, width, and height are
  computed from	font metrics as	descent-1, -lbearing, rbearing-lbearing, and
  ascent+descent, respectively.	 xmove is taken	from the glyph's width
  metric, and ymove is set to zero.  Finally, the glyph's image	is converted
  to the appropriate format for	glBitmap.

  Using	glXUseXFont may	be more	efficient than accessing the X font and
  generating the display lists explicitly, both	because	the display lists are
  created on the server	without	requiring a round trip of the glyph data, and
  because the server may choose	to delay the creation of each bitmap until it
  is accessed.

  Empty	display	lists are created for all glyphs that are requested and	are
  not defined in font.	glXUseXFont is ignored if there	is no current GLX
  context.

Errors

  BadFont is generated if font is not a	valid font.

  GLXBadContextState is	generated if the current GLX context is	in display-
  list construction mode.

  GLXBadCurrentWindow is generated if the drawable associated with the
  current context of the calling thread	is a window, and that window is	no
  longer valid.

See Also

  glBitmap, glXMakeCurrent

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXWaitGL.3gl

Name

  glXWaitGL - complete GL execution prior to subsequent	X calls

C Specification

  void glXWaitGL( void )

Description

  OpenGL rendering calls made prior to glXWaitGL are guaranteed	to be
  executed before X rendering calls made after glXWaitGL.  Although this same
  result can be	achieved using glFinish, glXWaitGL does	not require a round
  trip to the server, and it is	therefore more efficient in cases where
  client and server are	on separate machines.

  glXWaitGL is ignored if there	is no current GLX context.

Notes

  glXWaitGL may	or may not flush the X stream.

Errors

  GLXBadCurrentWindow is generated if the drawable associated with the
  current context of the calling thread	is a window, and that window is	no
  longer valid.

See Also

  glFinish, glFlush, glXWaitX, XSync

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


glXWaitX.3gl

Name

  glXWaitX - complete X	execution prior	to subsequent OpenGL calls

C Specification

  void glXWaitX( void )

Description

  X rendering calls made prior to glXWaitX are guaranteed to be	executed
  before OpenGL	rendering calls	made after glXWaitX.  Although this same
  result can be	achieved using XSync, glXWaitX does not	require	a round	trip
  to the server, and it	is therefore more efficient in cases where client and
  server are on	separate machines.

  glXWaitX is ignored if there is no current GLX context.

Notes

  glXWaitX may or may not flush	the OpenGL stream.

Errors

  GLXBadCurrentWindow is generated if the drawable associated with the
  current context of the calling thread	is a window, and that window is	no
  longer valid.

See Also

  glFinish, glFlush, glXWaitGL,	XSync

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


OpenGL WWW Pages Intro

The OpenGL WWW Pages

**Note** This collection of web pages is a local Tulane copy of materials provided by Digital Equipment Corporation for public distribution.

The OpenGL man pages have been reformatted into HTML for your reference. At this time it is not part of our Open3D product, but merely a convenience for our customers. (And web surfers.)

HTML does not have a good way to write mathematic equations. I have done my best to represent the various formulas in the man pages into something readable on character-based readers. I have used one ISO Latin-1 one character, the raised dot (·, hex b7), to indicate multiplication. If this is a problem for people I can change it to `*'. Superscripts are placed on the line above and subscripts are placed on the line below their referent. Fortunately I didn't run across any instances of subscripts on one line followed by superscripts on the line below.

My primary goals are

  1. Make the man pages readable and correct.
  2. Make sure the formulas, equations and tables are readable and correct.
  3. Keep all information visible for character based readers.
Making everything look pretty is not as important.

If you find any errors or if you have any suggestions, please send mail to andy.vesper@eng.pko.dec.com or andy.vesper@pko.mts.dec.com. I will try to fix them up as I find time. Unfortunately this is a volunteer effort and I so I cannot spend much time on it.

To start, choose either the alphabetic order page or specification order page.

There is also an introductory page located at glXIntro.

The GLU tesselator has changed in version 1.2; here are some notes on the new GLU tesselator

Due to popular demand, I have added a tar file and a zip file, each of which contains all the .html sources. Note that the names are long names with the extension .html, and so are not appropriate to being placed on a DOS disk.

For more OpenGL information, I suggest starting at The OpenGL WWW Center.

You can also read the hypertext version of The OpenGL Graphics System: A Specification (Version 1.0). In this document all formulae are given as images, so they look much better than my ASCII art.

Last Edited: Tuesday, October 24, 1995

AFV


OpenGL Index of routines in Alphabetic Order

Contents

glA glB glC glD glE glF glG glH glI glL glM glN glO glP glR glS glT glV glX glu

OpenGL Index in Alphabetic Order

glA...
glAccum
glAlphaFunc
glArrayElementEXT
glB...
glBegin
glBitmap
glBlendFunc
glC...
glCallList
glCallLists
glClear
glClearAccum
glClearColor
glClearDepth
glClearIndex
glClearStencil
glClipPlane
glColor3b, glColor3bv, glColor3d, glColor3dv, glColor3f, glColor3fv, glColor3i, glColor3iv, glColor3s, glColor3sv, glColor3ub, glColor3ubv, glColor3ui, glColor3uiv, glColor3us, glColor3usv, glColor4b, glColor4bv, glColor4d, glColor4dv, glColor4f, glColor4fv, glColor4i, glColor4iv, glColor4s, glColor4sv, glColor4ub, glColor4ubv, glColor4ui, glColor4uiv, glColor4us, glColor4usv
glColorMask
glColorMaterial
glColorPointerEXT
glCopyPixels
glCullFace
glD...
glDeleteLists
glDepthFunc
glDepthMask
glDepthRange
glDisable
glDrawArraysEXT
glDrawBuffer
glDrawPixels
glE...
glEdgeFlag
glEdgeFlagPointerEXT
glEdgeFlagv
glEnable
glEnd
glEndList
glEvalCoord1d, glEvalCoord1dv, glEvalCoord1f, glEvalCoord1fv, glEvalCoord2d, glEvalCoord2dv, glEvalCoord2f, glEvalCoord2fv
glEvalMesh1, glEvalMesh2
glEvalPoint1, glEvalPoint2
glF...
glFeedbackBuffer
glFinish
glFlush
glFogf, glFogfv, glFogi, glFogiv
glFrontFace
glFrustum
glG...
glGenLists
glGetBooleanv
glGetClipPlane
glGetDoublev
glGetError
glGetFloatv, glGetIntegerv
glGetLightfv, glGetLightiv
glGetMapdv, glGetMapfv, glGetMapiv
glGetMaterialfv, glGetMaterialiv
glGetPixelMapfv, glGetPixelMapuiv, glGetPixelMapusv
 glGetPointervEXT
glGetPolygonStipple
glGetString
glGetTexEnvfv, glGetTexEnviv
glGetTexGendv, glGetTexGenfv, glGetTexGeniv
glGetTexImage
glGetTexLevelParameterfv, glGetTexLevelParameteriv
glGetTexParameterfv, glGetTexParameteriv
glH...
glHint
glI...
glIndexMask
glIndexPointerEXT
glIndexd, glIndexdv, glIndexf, glIndexfv, glIndexi, glIndexiv, glIndexs, glIndexsv
glInitNames
glIsEnabled
glIsList
glL...
glLightModelf, glLightModelfv, glLightModeli, glLightModeliv
glLightf, glLightfv, glLighti, glLightiv
glLineStipple
glLineWidth
glListBase
glLoadIdentity
glLoadMatrixd, glLoadMatrixf
glLoadName
glLogicOp
glM...
glMap1d, glMap1f
glMap2d, glMap2f
glMapGrid1d, glMapGrid1f, glMapGrid2d, glMapGrid2f
glMaterialf, glMaterialfv, glMateriali, glMaterialiv
glMatrixMode
glMultMatrixd, glMultMatrixf
glN...
glNewList
glNormal3b, glNormal3bv, glNormal3d, glNormal3dv, glNormal3f, glNormal3fv, glNormal3i, glNormal3iv, glNormal3s, glNormal3sv
glNormalPointerEXT
glO...
glOrtho
glP...
glPassThrough
glPixelMapfv, glPixelMapuiv, glPixelMapusv
glPixelStoref, glPixelStorei
glPixelTransferf, glPixelTransferi
glPixelZoom
glPointSize
glPolygonMode
glPolygonStipple
glPopAttrib
glPopMatrix
glPopName
glPushAttrib
glPushMatrix
glPushName
glR...
glRasterPos2d, glRasterPos2dv, glRasterPos2f, glRasterPos2fv, glRasterPos2i, glRasterPos2iv, glRasterPos2s, glRasterPos2sv, glRasterPos3d, glRasterPos3dv, glRasterPos3f, glRasterPos3fv, glRasterPos3i, glRasterPos3iv, glRasterPos3s, glRasterPos3sv, glRasterPos4d, glRasterPos4dv, glRasterPos4f, glRasterPos4fv, glRasterPos4i, glRasterPos4iv, glRasterPos4s, glRasterPos4sv
glReadBuffer
glReadPixels
glRectd, glRectdv, glRectf, glRectfv, glRecti, glRectiv, glRects, glRectsv
glRenderMode
glRotated glRotatef
glS...
glScaled, glScalef
glScissor
glSelectBuffer
glShadeModel
glStencilFunc
glStencilMask
glStencilOp
glT...
glTexCoord1d, glTexCoord1dv, glTexCoord1f, glTexCoord1fv, glTexCoord1i, glTexCoord1iv, glTexCoord1s, glTexCoord1sv, glTexCoord2d, glTexCoord2dv, glTexCoord2f, glTexCoord2fv, glTexCoord2i, glTexCoord2iv, glTexCoord2s, glTexCoord2sv, glTexCoord3d, glTexCoord3dv, glTexCoord3f, glTexCoord3fv, glTexCoord3i, glTexCoord3iv, glTexCoord3s, glTexCoord3sv, glTexCoord4d, glTexCoord4dv, glTexCoord4f, glTexCoord4fv, glTexCoord4i, glTexCoord4iv, glTexCoord4s, glTexCoord4sv
glTexCoordPointerEXT,
glTexEnvf, glTexEnvfv, glTexEnvi, glTexEnviv,
glTexGend, glTexGendv, glTexGenf, glTexGenfv, glTexGeni, glTexGeniv
glTexImage1D
glTexImage2D
glTexParameterf, glTexParameterfv, glTexParameteri, glTexParameteriv
glTranslated, glTranslatef
glV...
glVertex2d, glVertex2dv, glVertex2f, glVertex2fv, glVertex2i, glVertex2iv, glVertex2s, glVertex2sv, glVertex3d, glVertex3dv, glVertex3f, glVertex3fv, glVertex3i, glVertex3iv, glVertex3s, glVertex3sv, glVertex4d, glVertex4dv, glVertex4f, glVertex4fv, glVertex4i, glVertex4iv, glVertex4s, glVertex4sv
glVertexPointerEXT
glViewport
glX...
glXChooseVisual
glXCopyContext
glXCreateContext
glXCreateGLXPixmap
glXDestroyContext
glXDestroyGLXPixmap
glXGetConfig
glXGetCurrentContext
glXGetCurrentDrawable
glXIsDirect
glXMakeCurrent
glXQueryExtension
glXQueryVersion
glXSwapBuffers
glXUseXFont
glXWaitGL
glXWaitX
glu...
See notes on the new GLU tesselator

gluBeginCurve
gluBeginPolygon (GLU versions 1.0 and 1.1)
gluBeginPolygon (GLU version 1.2 and later)
gluBeginSurface
gluBeginTrim
gluBuild1DMipmaps
gluBuild2DMipmaps
gluCylinder
gluDeleteNurbsRenderer
gluDeleteQuadric
gluDeleteTess (GLU versions 1.0 and 1.1)
gluDeleteTess (GLU version 1.2 and later)
gluDisk
gluEndCurve
gluEndPolygon (GLU versions 1.0 and 1.1)
gluEndPolygon (GLU version 1.2 and later)
gluEndSurface
gluEndTrim
gluErrorString
gluGetNurbsProperty
gluGetTessProperty (GLU version 1.2 and later)
gluLoadSamplingMatrices
gluLookAt
gluNewNurbsRenderer
gluNewQuadric
gluNewTess (GLU versions 1.0 and 1.1)
gluNewTess (GLU version 1.2 and later)
gluNextContour (GLU versions 1.0 and 1.1)
gluNextContour (GLU version 1.2 and later)
gluNurbsCallback
gluNurbsCurve
gluNurbsProperty
gluNurbsSurface
gluOrtho2D
gluPartialDisk
gluPerspective
gluPickMatrix
gluProject
gluPwlCurve
gluQuadricCallback
gluQuadricDrawStyle
gluQuadricNormals
gluQuadricOrientation
gluQuadricTexture
gluScaleImage
gluSphere
gluTessBeginContour (GLU version 1.2 and later)
gluTessBeginPolygon (GLU version 1.2 and later)
gluTessCallback (GLU versions 1.0 and 1.1)
gluTessCallback (GLU version 1.2 and later)
gluTessEndContour (GLU version 1.2 and later)
gluTessEndPolygon (GLU version 1.2 and later)
gluTessNormal (GLU version 1.2 and later)
gluTessProperty (GLU version 1.2 and later)
gluTessVertex (GLU versions 1.0 and 1.1)
gluTessVertex (GLU version 1.2 and later)
gluUnProject

Introduction | Alphabetic | Specification

Last Edited: Mon, May 22, 1995

AFV


OpenGL Index of routines in Specification Order

Table of Contents

Chapter 2. Fundamentals

Chapter 3. Rasterization

Chapter 4. Per-Fragment Operations and the Framebuffer

Chapter 5. Special Functions

Chapter 6. State and State Requests

OpenGL on the X Window System(tm)

The OpenGL Utility Library

The Vertex Array Extension

Chapter 2. Fundamentals

glGetError
glGetError
glBegin
glBegin, glEnd
glVertex
glVertex2d, glVertex2dv, glVertex2f, glVertex2fv, glVertex2i, glVertex2iv, glVertex2s, glVertex2sv, glVertex3d, glVertex3dv, glVertex3f, glVertex3fv, glVertex3i, glVertex3iv, glVertex3s, glVertex3sv, glVertex4d, glVertex4dv, glVertex4f, glVertex4fv, glVertex4i, glVertex4iv, glVertex4s, glVertex4sv
glNormal
glNormal3b, glNormal3bv, glNormal3d, glNormal3dv, glNormal3f, glNormal3fv, glNormal3i, glNormal3iv, glNormal3s, glNormal3sv
glColor
glColor3b, glColor3bv, glColor3d, glColor3dv, glColor3f, glColor3fv, glColor3i, glColor3iv, glColor3s, glColor3sv, glColor3ub, glColor3ubv, glColor3ui, glColor3uiv, glColor3us, glColor3usv, glColor4b, glColor4bv, glColor4d, glColor4dv, glColor4f, glColor4fv, glColor4i, glColor4iv, glColor4s, glColor4sv, glColor4ub, glColor4ubv, glColor4ui, glColor4uiv, glColor4us, glColor4usv
glIndex
glIndexd, glIndexdv, glIndexf, glIndexfv, glIndexi, glIndexiv, glIndexs, glIndexsv
glTexCoord
glTexCoord1d, glTexCoord1dv, glTexCoord1f, glTexCoord1fv, glTexCoord1i, glTexCoord1iv, glTexCoord1s, glTexCoord1sv, glTexCoord2d, glTexCoord2dv, glTexCoord2f, glTexCoord2fv, glTexCoord2i, glTexCoord2iv, glTexCoord2s, glTexCoord2sv, glTexCoord3d, glTexCoord3dv, glTexCoord3f, glTexCoord3fv, glTexCoord3i, glTexCoord3iv, glTexCoord3s, glTexCoord3sv, glTexCoord4d, glTexCoord4dv, glTexCoord4f, glTexCoord4fv, glTexCoord4i, glTexCoord4iv, glTexCoord4s, glTexCoord4sv
glEdgeFlag
glEdgeFlag, glEdgeFlagv
glRect
glRectd, glRectdv, glRectf, glRectfv, glRecti, glRectiv, glRects, glRectsv
glViewport
glViewport
glDepthRange
glDepthRange
glMatrixMode
glMatrixMode
glLoadMatrix
glLoadMatrixd, glLoadMatrixf
glMultMatrix
glMultMatrixd, glMultMatrixf
glLoadIdentity
glLoadIdentity
glRotate
glRotated, glRotatef
glTranslate
glTranslated, glTranslatef
glScale
glScaled, glScalef
glFrustum
glFrustum
glOrtho
glOrtho
glPushMatrix
glPushMatrix, glPopMatrix
glEnable
glEnable, glDisable
glTexGen
glTexGend, glTexGendv, glTexGenf, glTexGenfv, glTexGeni, glTexGeniv
glClipPlane
glClipPlane
glRasterPos
glRasterPos2d, glRasterPos2dv, glRasterPos2f, glRasterPos2fv, glRasterPos2i, glRasterPos2iv, glRasterPos2s, glRasterPos2sv, glRasterPos3d, glRasterPos3dv, glRasterPos3f, glRasterPos3fv, glRasterPos3i, glRasterPos3iv, glRasterPos3s, glRasterPos3sv, glRasterPos4d, glRasterPos4dv, glRasterPos4f, glRasterPos4fv, glRasterPos4i, glRasterPos4iv, glRasterPos4s, glRasterPos4sv
glFrontFace
glFrontFace
glMaterial
glMaterialf, glMaterialfv, glMateriali, glMaterialiv
glLight
glLightf, glLightfv, glLighti, glLightiv
glLightModel
glLightModelf, glLightModelfv, glLightModeli, glLightModeliv
glColorMaterial
glColorMaterial
glShadeModel
glShadeModel

Chapter 3. Rasterization

glPointSize
glPointSize
glLineWidth
glLineWidth
glLineStipple
glLineStipple
glCullFace
glCullFace
glPolygonStipple
glPolygonStipple
glPolygonMode
glPolygonMode
glPixelStore
glPixelStoref, glPixelStorei
glPixelTransfer
glPixelTransferf, glPixelTransferi
glPixelMap
glPixelMapfv, glPixelMapuiv, glPixelMapusv
glDrawPixels
glDrawPixels
glPixelZoom
glPixelZoom
glBitmap
glBitmap
glTexImage2D
glTexImage2D
glTexImage1D
glTexImage1D
glTexParameter
glTexParameterf, glTexParameterfv, glTexParameteri, glTexParameteriv
glTexEnv
glTexEnvf, glTexEnvfv, glTexEnvi, glTexEnviv
glFog
glFogf, glFogfv, glFogi, glFogiv

Chapter 4. Per-Fragment Operations and the Framebuffer

glScissor
glScissor
glAlphaFunc
glAlphaFunc
glStencilFunc
glStencilFunc
glStencilOp
glStencilOp
glDepthFunc
glDepthFunc
glBlendFunc
glBlendFunc
glLogicOp
glLogicOp
glDrawBuffer
glDrawBuffer
glIndexMask
glIndexMask
glColorMask
glColorMask
glDepthMask
glDepthMask
glStencilMask
glStencilMask
glClear
glClear
glClearColor
glClearColor
glClearIndex
glClearIndex
glClearDepth
glClearDepth
glClearStencil
glClearStencil
glClearAccum
glClearAccum
glAccum
glAccum
glReadPixels
glReadPixels
glReadBuffer
glReadBuffer
glCopyPixels
glCopyPixels

Chapter 5. Special Functions

glMap1
glMap1d, glMap1f
glMap2
glMap2d, glMap2f
glEvalCoord
glEvalCoord1d, glEvalCoord1dv, glEvalCoord1f, glEvalCoord1fv, glEvalCoord2d, glEvalCoord2dv, glEvalCoord2f, glEvalCoord2fv
glMapGrid
glMapGrid1d, glMapGrid1f, glMapGrid2d, glMapGrid2f
glEvalMesh
glEvalMesh1, glEvalMesh2
glEvalPoint
glEvalPoint1, glEvalPoint2
glInitNames
glInitNames
glPushName
glPushName, glPopName
glLoadName
glLoadName
glRenderMode
glRenderMode
glSelectBuffer
glSelectBuffer
glFeedbackBuffer
glFeedbackBuffer
glPassThrough
glPassThrough
glNewList
glNewList, glEndList
glCallList
glCallList
glCallLists
glCallLists
glListBase
glListBase
glGenLists
glGenLists
glIsList
glIsList
glDeleteLists
glDeleteLists
glFlush
glFlush
glFinish
glFinish
glHint
glHint

Chapter 6. State and State Requests

glGet
glGetBooleanv, glGetDoublev, glGetFloatv, glGetIntegerv
glIsEnabled
glIsEnabled
glGetClipPlane
glGetClipPlane
glGetLight
glGetLightfv, glGetLightiv
glGetMaterial
glGetMaterialfv, glGetMaterialiv
glGetTexEnv
glGetTexEnvfv, glGetTexEnviv
glGetTexGen
glGetTexGendv, glGetTexGenfv, glGetTexGeniv
glGetTexParameter
glGetTexParameterfv, glGetTexParameteriv
glGetTexLevelParameter
glGetTexLevelParameterfv, glGetTexLevelParameteriv
glGetPixelMap
glGetPixelMapfv, glGetPixelMapuiv, glGetPixelMapusv
glGetMap
glGetMapdv, glGetMapfv, glGetMapiv
glGetTexImage
glGetTexImage
glGetPolygonStipple
glGetPolygonStipple
glGetString
glGetString
glPushAttrib
glPushAttrib, glPopAttrib

OpenGL on the X Window System(tm)

glXIntro
Introduction
glXQueryExtension
glXQueryExtension
glXQueryVersion
glXQueryVersion
glXGetConfig
glXGetConfig
glXChooseVisual
glXChooseVisual
glXCreateGLXPixmap
glXCreateGLXPixmap
glXDestroyGLXPixmap
glXDestroyGLXPixmap
glXCreateContext
glXCreateContext
glXIsDirect
glXIsDirect
glXDestroyContext
glXDestroyContext
glXCopyContext
glXCopyContext
glXMakeCurrent
glXMakeCurrent
glXGetCurrentContext
glXGetCurrentContext
glXGetCurrentDrawable
glXGetCurrentDrawable
glXWaitGL
glXWaitGL
glXWaitX
glXWaitX
glXSwapBuffers
glXSwapBuffers
glXUseXFont
glXUseXFont

The OpenGL Utility Library

See notes on the new GLU tesselator

gluScaleImage
gluScaleImage
gluBuild1DMipmaps
gluBuild1DMipmaps
gluBuild2DMipmaps
gluBuild2DMipmaps
gluOrtho2D
gluOrtho2D
gluPerspective
gluPerspective
gluLookAt
gluLookAt
gluPickMatrix
gluPickMatrix
gluProject
gluProject
gluUnProject
gluUnProject
gluNewQuadric
gluNewQuadric
gluDeleteQuadric
gluDeleteQuadric
gluQuadricCallback
gluQuadricCallback
gluQuadricNormals
gluQuadricNormals
gluQuadricTexture
gluQuadricTexture
gluQuadricOrientation
gluQuadricOrientation
gluQuadricDrawStyle
gluQuadricDrawStyle
gluSphere
gluSphere
gluCylinder
gluCylinder
gluDisk
gluDisk
gluPartialDisk
gluPartialDisk
gluNewNurbsRenderer
gluNewNurbsRenderer
gluDeleteNurbsRenderer
gluDeleteNurbsRenderer
gluNurbsCallback
gluNurbsCallback
gluBeginCurve
gluBeginCurve, gluEndCurve
gluNurbsCurve
gluNurbsCurve
gluBeginSurface
gluBeginSurface, gluEndSurface
gluNurbsSurface
gluNurbsSurface
gluBeginTrim
gluBeginTrim, gluEndTrim
gluPwlCurve
gluPwlCurve
gluNurbsProperty
gluNurbsProperty
gluLoadSamplingMatrices
gluLoadSamplingMatrices
gluGetNurbsProperty
gluGetNurbsProperty
gluErrorString
gluErrorString

The GLU tesselator for GLU versions 1.0 and 1.1:

gluNewTess
gluNewTess
gluDeleteTess
gluDeleteTess
gluTessCallback
gluTessCallback
gluBeginPolygon
gluBeginPolygon, gluEndPolygon
gluTessVertex
gluTessVertex
gluNextContour
gluNextContour

The GLU tesselator for GLU version 1.2 and later:

gluNewTess
gluNewTess
gluDeleteTess
gluDeleteTess
gluTessCallback
gluTessCallback
gluTessBeginPolygon
gluTessBeginPolygon
gluTessEndPolygon
gluTessEndPolygon
gluTessVertex
gluTessVertex
gluTessBeginContour
gluBeginContour, gluEndContour
gluTessProperty
gluTessProperty
gluGetTessProperty
gluGetTessProperty
gluTessNormal
gluTessNormal
gluBeginPolygon
gluBeginPolygon, gluEndPolygon (Obsolete)
gluNextContour
gluNextContour (Obsolete)

The Vertex Array Extension

glArrayElementEXT
glArrayElementEXT
glColorPointerEXT
glColorPointerEXT
glDrawArraysEXT
glDrawArraysEXT
glEdgeFlagPointerEXT
glEdgeFlagPointerEXT
glGetPointervEXT
glGetPointervEXT
glIndexPointerEXT
glIndexPointerEXT
glNormalPointerEXT
glNormalPointerEXT
glTexCoordPointerEXT
glTexCoordPointerEXT
glVertexPointerEXT
glVertexPointerEXT


GLU Version 1.2 Polygon Tessellator Notes

Some notes on the new polygon tessellator in GLU version 1.2

GLU version 1.2 introduces a new polygon tessellator. The following routines have become obsolete and should not be used for new development based on GLU version 1.2 and later.
    Obsolete Routine	Replace with

    gluBeginPolygon		gluTessBeginPolygon; gluTessBeginContour
    gluEndPolygon		gluTessEndContour; gluTessEndPolygon
    gluNextContour		gluTessEndContour; gluTessBeginContour

The following routines are new in GLU version 1.2:

    gluGetTessProperty
    gluTessBeginContour
    gluTessBeginPolygon
    gluTessEndContour
    gluTessEndPolygon
    gluTessNormal
    gluTessProperty

Some routines have had an argument or return-value changed from ``GLUtriangulatorObj *'' to ``GLUtesselator *''. Since the GLU header file for version 1.2 has a typedef from GLUtriangulatorObj to GLUtesselator, source code should not need any immediate changes. However, at some time the source code should be moved to the GLU version 1.2 name.

    gluBeginPolygon
    gluDeleteTess
    gluEndPolygon
    gluNewTess
    gluNextContour
    gluTessCallback
    gluTessVertex

One routine has been expanded to support more valid values for the which argument:

    gluTessCallback

To distinguish what version of the GLU library you are using at compile time, check for the following pre-processor symbols:

    none defined			version 1.0
    GLU_VERSION_1_1			version 1.1 or later
    GLU_VERSION_1_2			version 1.2 or later

Digital Open3D supports the following versions of the GLU library:

    Digital Open3D Version		GLU Version Supported

    Before Version 2.6		version 1.0
    Version 2.6			version 1.1
    Version 3.0 and later		version 1.2

Introduction | Alphabetic | Specification

Last Edited: Thursday, September 28, 1995

AFV


Attribute Groups

Mask Bit | Attribute Group
GL_ACCUM_BUFFER_BIT | accum-buffer
GL_ALL_ATTRIB_BITS | —
GL_COLOR_BUFFER_BIT | color-buffer
GL_CURRENT_BIT | current
GL_DEPTH_BUFFER_BIT | depth-buffer
GL_ENABLE_BIT | enable
GL_EVAL_BIT | eval
GL_FOG_BIT | fog
GL_HINT_BIT | hint
GL_LIGHTING_BIT | lighting
GL_LINE_BIT | line
GL_LIST_BIT | list
GL_PIXEL_MODE_BIT | pixel
GL_POINT_BIT | point
GL_POLYGON_BIT | polygon
GL_POLYGON_STIPPLE_BIT | polygon-stipple
GL_SCISSOR_BIT | scissor
GL_STENCIL_BUFFER_BIT | stencil-buffer
GL_TEXTURE_BIT | texture
GL_TRANSFORM_BIT | transform
GL_VIEWPORT_BIT | viewport

void glPopAttrib(void);

Restores the values of those state variables that were saved with the last glPushAttrib.


State Variables for Current Values and Associated Data

GL_CURRENT_COLOR
Description: Current color
Attribute group: current
Initial value: 1,1,1,1
Get command: glGetIntegerv
glGetFloatv

GL_CURRENT_INDEX
Description: Current color index
Attribute group: current
Initial value: 1
Get command: glGetIntegerv
glGetFloatv

GL_CURRENT_TEXTURE_COORDS
Description: Current texture coordinates
Attribute group: current
Initial value: 0,0,0,1
Get command: glGetFloatv

GL_CURRENT_NORMAL
Description: Current normal
Attribute group: current
Initial value: 0,0,1
Get command: glGetFloatv

GL_CURRENT_RASTER POSITION
Description: Current raster position
Attribute group: current
Initial value: 0,0,0,1
Get command: glGetFloatv

GL_CURRENT_RASTER_DISTANCE
Description: Current raster distance
Attribute group: current
Initial value: 0
Get command: glGetFloatv

GL_CURRENT_RASTER_COLOR
Description: Color associated with raster position
Attribute group: current
Initial value: 1,1,1,1
Get command: glGetIntegerv
glGetFloatv

GL_CURRENT_RASTER_INDEX
Description: Color index associated with raster position
Attribute group: current
Initial value: 1
Get command: glGetIntegerv
glGetFloatv

GL_CURRENT_RASTER_TEXTURE_COORDS
Description: Texture coordinates associated with raster position
Attribute group: current
Initial value: 0,0,0,1
Get command: glGetFloatv

GL_CURRENT_RASTER_POSITION_VALID
Description: Raster position valid bit
Attribute group: current
Initial value: GL_TRUE
Get command: glGetBooleanv

GL_EDGE_FLAG
Description: Edge flag
Attribute group: current
Initial value: GL_TRUE
Get command: glGetBooleanv


Transformation State Variables

GL_MODELVIEW_MATRIX
Description: Modelview matrix stack
Attribute group: —
Initial value: Identity
Get command: glGetFloatv

GL_PROJECTION_MATRIX
Description: Projection matrix stack
Attribute group: —
Initial value: Identity
Get command: glGetFloatv

GL_TEXTURE_MATRIX
Description: Texture matrix stack
Attribute group: —
Initial value: Identity
Get command: glGetFloatv

GL_VIEWPORT
Description: Viewport origin and extent
Attribute group: viewport
Initial value: —
Get command: glGetIntegerv

GL_DEPTH_RANGE
Description: Depth range near and far
Attribute group: viewport
Initial value: 0,1
Get command: glGetFloatv

GL_MODELVIEW_STACK_DEPTH
Description: Modelview matrix stack pointer
Attribute group: —
Initial value: 1
Get command: glGetIntegerv

GL_PROJECTION_STACK_DEPTH
Description: Projection matrix stack pointer
Attribute group: —
Initial value: 1
Get command: glGetIntegerv

GL_TEXTURE_STACK_DEPTH
Description: Texture matrix stack pointer
Attribute group: —
Initial value: 1
Get command: glGetIntegerv

GL_MATRIX_MODE
Description: Current matrix mode
Attribute group: transform
Initial value: GL_MODELVIEW
Get command: glGetIntegerv

GL_NORMALIZE
Description: Current normal normalization on/off
Attribute group: transform/enable
Initial value: GL_FALSE
Get command: glIsEnabled

GL_CLIP_PLANEi
Description: User clipping plane coefficients
Attribute group: transform
Initial value: 0,0,0,0
Get command: glGetClipPlane

GL_CLIP_PLANEi
Description: ith user clipping plane enabled
Attribute group: transform/enable
Initial value: GL_FALSE
Get command: glIsEnabled


Coloring State Variables

GL_FOG_COLOR
Description: Fog color
Attribute group: fog
Initial value: 0,0,0,0
Get command: glGetFloatv

GL_FOG_INDEX
Description: Fog index
Attribute group: fog
Initial value: 0
Get command: glGetFloatv

GL_FOG_DENSITY
Description: Exponential fog density
Attribute group: fog
Initial value: 1.0
Get command: glGetFloatv

GL_FOG_START
Description: Linear fog start
Attribute group: fog
Initial value: 0.0
Get command: glGetFloatv

GL_FOG_END
Description: Linear fog end
Attribute group: fog
Initial value: 1.0
Get command: glGetFloatv

GL_FOG_MODE
Description: Fog mode
Attribute group: fog
Initial value: GL_EXP
Get command: glGetIntegerv

GL_FOG
Description: True if fog enabled
Attribute group: fog/enable
Initial value: GL_FALSE
Get command: glIsEnabled

GL_SHADE_MODEL
Description: glShadeModel setting
Attribute group: lighting
Initial value: GL_SMOOTH
Get command: glGetIntegerv


Lighting State Variables

GL_LIGHTING
Description: True if lighting is enabled
Attribute group: lighting/enable
Initial value: GL_FALSE
Get command: glIsEnabled

GL_COLOR_MATERIAL
Description: True if color tracking is enabled
Attribute group: lighting
Initial value: GL_FALSE
Get command: glIsEnabled

GL_COLOR_MATERIAL_PARAMETER
Description: Material properties tracking current color
Attribute group: lighting
Initial value: GL_AMBIENT_AND_DIFFUSE
Get command: glGetIntegerv

GL_COLOR_MATERIAL_FACE
Description: Face(s) affected by color tracking
Attribute group: lighting
Initial value: GL_FRONT_AND_BACK
Get command: glGetIntegerv

GL_AMBIENT
Description: Ambient material color
Attribute group: lighting
Initial value: (0.2, 0.2, 0.2, 1.0)
Get command: glGetMaterialfv

GL_DIFFUSE
Description: Diffuse material color
Attribute group: lighting
Initial value: (0.8, 0.8, 0.8, 1.0)
Get command: glGetMaterialfv

GL_SPECULAR
Description: Specular material color
Attribute group: lighting
Initial value: (0.0, 0.0, 0.0, 1.0)
Get command: glGetMaterialfv

GL_EMISSION
Description: Emissive material color
Attribute group: lighting
Initial value: (0.0, 0.0, 0.0, 1.0)
Get command: glGet

GL_SHININESS
Description: Specular exponent of material
Attribute group: lighting
Initial value: 0.0
Get command: glGetMaterialfv

GL_LIGHT_MODEL_AMBIENT
Description: Ambient scene color
Attribute group: lighting
Initial value: (0.2, 0.2, 0.2, 0.1)
Get command: glGetFloatv

GL_LIGHT_MODEL_LOCAL_VIEWER
Description: Viewer is local
Attribute group: lighting
Initial value: GL_FALSE
Get command: glGetBooleanv

GL_LIGHT_MODEL_TWO_SIDE
Description: Use two-sided lighting
Attribute group: lighting
Initial value: GL_FALSE
Get command: glGetBooleanv

GL_AMBIENT
Description: Ambient intensity of light i
Attribute group: lighting
Initial value: (0.0, 0.0, 0.0, 1.0)
Get command: glGetLightfv

GL_DIFFUSE
Description: Diffuse intensity of light i
Attribute group: lighting
Initial value: —
Get command: glGetLightfv

GL_SPECULAR
Description: Specular intensity of light i
Attribute group: lighting
Initial value: —
Get command: glGetLightfv

GL_POSITION
Description: Position of light i
Attribute group: lighting
Initial value: (0.0, 0.0, 1.0, 0.0)
Get command: glGetLightfv

GL_CONSTANT_ATTENUATION
Description: Constant attenuation factor
Attribute group: lighting
Initial value: 1.0
Get command: glGetLightfv

GL_LINEAR_ATTENUATION
Description: Linear attenuation factor
Attribute group: lighting
Initial value: 0.0
Get command: glGetLightfv

GL_QUADRATIC_ATTENUATION
Description: Quadratic attenuation factor
Attribute group: lighting
Initial value: 0.0
Get command: glGetLightfv

GL_SPOT_DIRECTION
Description: Spotlight direction of light i
Attribute group: lighting
Initial value: (0.0, 0.0, –1.0)
Get command: glGetLightfv

GL_SPOT_EXPONENT
Description: Spotlight exponent of light i
Attribute group: lighting
Initial value: 0.0
Get command: glGetLightfv

GL_SPOT_CUTOFF
Description: Spotlight angle of light i
Attribute group: lighting
Initial value: 180.0
Get command: glGetLightfv

GL_LIGHTi
Description: True if light i enabled
Attribute group: lighting/enable
Initial value: GL_FALSE
Get command: glIsEnabled

GL_COLOR_INDEXES
Description: C (a) , C (d) , and C (s) for color-index lighting
Attribute group: lighting/enable
Initial value: 0, 1, 1
Get command: glGetFloatv


Rasterization State Variables

GL_POINT_SIZE
Description: Point size
Attribute group: point
Initial value: 1.0
Get command: glGetFloatv

GL_POINT_SMOOTH
Description: Point aliasing on
Attribute group: point/enable
Initial value: GL_FALSE
Get command: glIsEnabled

GL_LINE_WIDTH
Description: Line width
Attribute group: line
Initial value: 1.0
Get command: glGetFloatv

GL_LINE_SMOOTH
Description: Line antialiasing on
Attribute group: line/enable
Initial value: GL_FALSE
Get command: glIsEnabled

GL_LINE_STIPPLE_PATTERN
Description: Line stipple
Attribute group: line
Initial value: 1's
Get command: glGetIntegerv

GL_LINE_STIPPLE_REPEAT
Description: Line stipple repeat
Attribute group: line
Initial value: 1
Get command: glGetIntegerv

GL_LINE_STIPPLE
Description: Line stipple enable
Attribute group: line/enable
Initial value: GL_FALSE
Get command: glIsEnabled

GL_CULL_FACE
Description: Polygon culling enabled
Attribute group: polygon/enable
Initial value: GL_FALSE
Get command: glIsEnabled

GL_CULL_FACE_MODE
Description: Cull front-/back-facing polygons
Attribute group: polygon
Initial value: GL_BACK
Get command: glGetIntegerv

GL_FRONT_FACE
Description: Polygon front-face CW/CCW indicator
Attribute group: polygon
Initial value: GL_CCW
Get command: glGetIntegerv

GL_POLYGON_SMOOTH
Description: Polygon antialiasing on
Attribute group: polygon/enable
Initial value: GL_FALSE
Get command: glIsEnabled

GL_POLYGON_MODE
Description: Polygon rasterization mode (front and back)
Attribute group: polygon
Initial value: GL_FILL
Get command: glGetIntegerv

GL_POLYGON_STIPPLE
Description: Polygon stipple enable
Attribute group: polygon/enable
Initial value: GL_FALSE
Get command: glIsEnabled

—
Description: Polygon stipple pattern
Attribute group: polygon-stipple
Initial value: 1's
Get command: glGetPolygonStipple


Texturing State Variables

GL_TEXTURE_x
Description: True if x-D texturing enabled (x is 1-D or 2-D)
Attribute group: texture/enable
Initial value: GL_FALSE
Get command: glIsEnabled

GL_TEXTURE
Description: x-D texture image at level of detail i
Attribute group: —
Initial value: —
Get command: glGetTexImage

GL_TEXTURE_WIDTH
Description: x-D texture image i's width
Attribute group: —
Initial value: 0
Get command: glGetTexLevelParameter

GL_TEXTURE_HEIGHT
Description: x-D texture image i's height
Attribute group: —
Initial value: 0
Get command: glGetTexLevelParameter

GL_TEXTURE_BORDER
Description: x-D texture image i's border
Attribute group: —
Initial value: 0
Get command: glGetTexLevelParameter

GL_TEXTURE_COMPONENTS
Description: Texture image components
Attribute group: —
Initial value: 1
Get command: glGetTexLevelParameter

GL_TEXTURE_BORDER_COLOR
Description: Texture border color
Attribute group: texture
Initial value: 0,0,0,0
Get command: glGetTexParameter

GL_TEXTURE_MIN_FILTER
Description: Texture minification function
Attribute group: texture
Initial value: GL_NEAREST_MIPMAP_LINEAR
Get command: glGetTexParameter

GL_TEXTURE_MAG_FILTER
Description: Texture magnification function
Attribute group: texture
Initial value: GL_LINEAR
Get command: glGetTexParameter

GL_TEXTURE_WRAP_x
Description: Texture wrap mode (x is S or T)
Attribute group: texture
Initial value: GL_REPEAT
Get command: glGetTexParameter

GL_TEXTURE_ENV_MODE
Description: Texture application function
Attribute group: texture
Initial value: GL_MODULATE
Get command: glGetTexEnviv

GL_TEXTURE_ENV_COLOR
Description: Texture environment color
Attribute group: texture
Initial value: 0,0,0,0
Get command: glGetTexEnvfv

GL_TEXTURE_GEN_x
Description: Texgen is enabled (x is S, T, R, or Q)
Attribute group: texture/enable
Initial value: GL_FALSE
Get command: glIsEnabled

GL_EYE_LINEAR
Description: Texgen plane equation coefficients
Attribute group: texture
Initial value: —
Get command: glGetTexGenfv

GL_OBJECT_LINEAR
Description: Texgen object linear coefficients
Attribute group: texture
Initial value: —
Get command: glGetTexGenfv

GL_TEXTURE_GEN_MODE
Description: Function used for texgen
Attribute group: texture
Initial value: GL_EYTE_LINEAR
Get command: glGetTexGeniv


Pixel Operations

GL_SCISSOR_TEST
Description: Scissoring enabled
Attribute group: scissor/enable
Initial value: GL_FALSE
Get command: glIsEnabled

GL_SCISSOR_BOX
Description: Scissor box
Attribute group: scissor
Initial value: —
Get command: glGetIntegerv

GL_STENCIL_TEST
Description: Stenciling enabled
Attribute group: stencil-buffer/enable
Initial value: GL_FALSE
Get command: glIsEnabled

GL_STENCIL_FUNC
Description: Stencil function
Attribute group: stencil-buffer
Initial value: GL_ALWAYS
Get command: glGetIntegerv

GL_STENCIL_VALUE_MASK
Description: Stencil mask
Attribute group: stencil-buffer
Initial value: 1's
Get command: glGetIntegerv

GL_STENCIL_REF
Description: Stencil reference value
Attribute group: stencil-buffer
Initial value: 0
Get command: glGetIntegerv

GL_STENCIL_FAIL
Description: Stencil fail action
Attribute group: stencil-buffer
Initial value: GL_KEEP
Get command: glGetIntegerv

GL_STENCIL_PASS_DEPTH_FAIL
Description: Stencil depth buffer fail action
Attribute group: stencil-buffer
Initial value: GL_KEEP
Get command: glGetIntegerv

GL_STENCIL_PASS_DEPTH_PASS
Description: Stencil depth buffer pass action
Attribute group: stencil-buffer
Initial value: GL_KEEP
Get command: glGetIntegerv

GL_ALPHA_TEST
Description: Alpha test enabled
Attribute group: color-buffer/enable
Initial value: GL_FALSE
Get command: glIsEnabled

GL_ALPHA_TEST_FUNC
Description: Alpha test function
Attribute group: color-buffer
Initial value: GL_ALWAYS
Get command: glGetIntegerv

GL_ALPHA_TEST_REF
Description: Alpha test reference value
Attribute group: color-buffer
Initial value: 0
Get command: glGetIntegerv

GL_DEPTH_TEST
Description: Depth buffer enabled
Attribute group: depth-buffer/enable
Initial value: GL_FALSE
Get command: glIsEnabled

GL_DEPTH_FUNC
Description: Depth buffer test function
Attribute group: depth-buffer
Initial value: GL_LESS
Get command: glGetIntegerv

GL_BLEND
Description: Blending enabled
Attribute group: color-buffer/enable
Initial value: GL_FALSE
Get command: glIsEnabled

GL_BLENC_SRC
Description: Blending source function
Attribute group: color-buffer
Initial value: GL_ONE
Get command: glGetIntegerv

GL_BLEND_DST
Description: Blending destination function
Attribute group: color-buffer
Initial value: GL_ZERO
Get command: glGetIntegerv

GL_LOGIC_OP
Description: Logical operation enabled
Attribute group: color-buffer/enable
Initial value: GL_FALSE
Get command: glIsEnabled

GL_LOGIC_OP_MODE
Description: Logical operation function
Attribute group: color-buffer
Initial value: GL_COPY
Get command: glGetIntegerv

GL_DITHER
Description: Dithering enabled
Attribute group: color-buffer/enable
Initial value: GL_TRUE
Get command: glIsEnabled


Framebuffer Control State Variables

GL_DRAW_BUFFER
Description: Buffers selected for drawing
Attribute group: color-buffer
Initial value: —
Get command: glGetIntegerv

GL_INDEX_WRITEMASK
Description: Color-index writemask
Attribute group: color-buffer
Initial value: 1's
Get command: glGetIntegerv

GL_COLOR_WRITEMASK
Description: Color write enables; R, G, B, or A
Attribute group: color-buffer
Initial value: GL_TRUE
Get command: glGetBooleanv

GL_DEPTH_WRITEMASK
Description: Depth buffer enabled for writing
Attribute group: depth-buffer
Initial value: GL_TRUE
Get command: glGetBooleanv

GL_STENCIL_WRITEMASK
Description: Stencil-buffer writemask
Attribute group: stencil-buffer
Initial value: 1's
Get command: glGetIntegerv

GL_COLOR_CLEAR_VALUE
Description: Color-buffer clear value (RGBA mode)
Attribute group: color-buffer
Initial value: 0, 0, 0, 0
Get command: glGetFloatv

GL_INDEX_CLEAR_VALUE
Description: Color-buffer clear value (color-index mode)
Attribute group: color-buffer
Initial value: 0
Get command: glGetFloatv

GL_DEPTH_CLEAR_VALUE
Description: Depth-buffer clear value
Attribute group: depth-buffer
Initial value: 1
Get command: glGetIntegerv

GL_STENCIL_CLEAR_VALUE
Description: Stencil-buffer clear value
Attribute group: stencil-buffer
Initial value: 0
Get command: glGetIntegerv

GL_ACCUM_CLEAR_VALUE
Description: Accumulation-buffer clear value
Attribute group: accum-buffer
Initial value: 0
Get command: glGetFloatv


Pixel State Variables

GL_UNPACK_SWAP_BYTES
Description: Value of GL_UNPACK_SWAP_BYTES
Attribute group: —
Initial value: GL_FALSE
Get command: glGetBooleanv

GL_UNPACK_LSB_FIRST
Description: Value of GL_UNPACK_LSB_FIRST
Attribute group: —
Initial value: GL_FALSE
Get command: glGetBooleanv

GL_UNPACK_ROW_LENGTH
Description: Value of GL_UNPACK_ROW_LENGTH
Attribute group: —
Initial value: 0
Get command: glGetIntegerv

GL_UNPACK_SKIP_ROWS
Description: Value of GL_UNPACK_SKIP_ROWS
Attribute group: —
Initial value: 0
Get command: glGetIntegerv

GL_UNPACK_SKIP_PIXELS
Description: Value of GL_UNPACK_SKIP_PIXELS
Attribute group: —
Initial value: 0
Get command: glGetIntegerv

GL_UNPACK_ALIGNMENT
Description: Value of GL_UNPACK_ALIGNMENT
Attribute group: —
Initial value: 4
Get command: glGetIntegerv

GL_PACK_SWAP_BYTES
Description: Value of GL_PACK_SWAP_BYTES
Attribute group: —
Initial value: GL_FALSE
Get command: glGetBooleanv

GL_PACK_LSB_FIRST
Description: Value of GL_PACK_LSB_FIRST
Attribute group: —
Initial value: GL_FALSE
Get command: glGetBooleanv

GL_PACK_ROW_LENGTH
Description: Value of GL_PACK_ROW_LENGTH
Attribute group: —
Initial value: 0
Get command: glGetIntegerv

GL_PACK_SKIP_ROWS
Description: Value of GL_PACK_SKIP_ROWS
Attribute group: —
Initial value: 0
Get command: glGetIntegerv

GL_PACK_SKIP_PIXELS
Description: Value of GL_PACK_SKIP_PIXELS
Attribute group: —
Initial value: 0
Get command: glGetIntegerv

GL_PACK_ALIGNMENT
Description: Value of GL_PACK_ALIGNMENT
Attribute group: —
Initial value: 4
Get command: glGetIntegerv

GL_MAP_COLOR
Description: True if colors are mapped
Attribute group: pixel
Initial value: GL_FALSE
Get command: glGetBooleanv

GL_MAP_STENCIL
Description: True if stencil values are mapped
Attribute group: pixel
Initial value: GL_FALSE
Get command: glGetBooleanv

GL_INDEX_SHIFT
Description: Value of GL_INDEX_SHIFT
Attribute group: pixel
Initial value: 0
Get command: glGetIntegerv

GL_INDEX_OFFSET
Description: Value of GL_INDEX_OFFSET
Attribute group: pixel
Initial value: 0
Get command: glGetIntegerv

GL_x_SCALE
Description: Value of GL_x_SCALE; x is GL_RED, GL_BLUE, GL_ALPHA, or GL_DEPTH
Attribute group: pixel
Initial value: 1
Get command: glGetFloatv

GL_x_BIAS
Description: Value of GL_x_BIAS; x is GL_RED GL_BLUE, GL_ALPHA, or GL_DEPTH
Attribute group: pixel
Initial value: 0
Get command: glGetFloatv

GL_ZOOM_X
Description: x zoom factor
Attribute group: pixel
Initial value: 1.0
Get command: glGetFloatv

GL_ZOOM_Y
Description: y zoom factor
Attribute group: pixel
Initial value: 1.0
Get command: glGetFloatv

GL_x
Description: glPixelMap translation tables
Attribute group: pixel
Initial value: 0's
Get command: glGetPixelMap

GL_x_SIZE
Description: Size of table x
Attribute group: pixel
Initial value: 1
Get command: glGetIntegerv

GL_READ_BUFFER
Description: Read source buffer
Attribute group: pixel
Initial value: —
Get command: glGetIntegerv


Evaluator State Variables

GL_ORDER
Description: 1-D map order
Attribute group: —
Initial value: 1
Get command: glGetMapiv

GL_ORDER
Description: 2-D map orders
Attribute group: —
Initial value: 1, 1
Get command: glGetMapiv

GL_COEFF
Description: 1-D control points
Attribute group: —
Initial value: —
Get command: glGetMapfv

GL_COEFF
Description: 2-D control points
Attribute group: —
Initial value: —
Get command: glGetMapfv

GL_DOMAIN
Description: 1-D domain endpoints
Attribute group: —
Initial value: —
Get command: glGetMapfv

GL_DOMAIN
Description: 2-D domain endpoints
Attribute group: —
Initial value: —
Get command: glGetMapfv

GL_MAP1_x
Description: 1-D map enables: x is map type
Attribute group: eval/enable
Initial value: GL_FALSE
Get command: glIsEnabled

GL_MAP2_x
Description: 2-D map enables: x is map type
Attribute group: eval/enable
Initial value: GL_FALSE
Get command: glIsEnabled

GL_MAP1_GRID_DOMAIN
Description: 1-D grid endpoints
Attribute group: eval
Initial value: 0, 1
Get command: glGetFloatv

GL_MAP2_GRID_DOMAIN
Description: 2-D grid endpoints
Attribute group: eval
Initial value: 0, 1; 0, 1
Get command: glGetFloatv

GL_MAP1_GRID_SEGMENTS
Description: 1-D grid divisions
Attribute group: eval
Initial value: 1
Get command: glGetFloatv

GL_MAP1_GRID_SEGMENTS
Description: 2-D grid segments
Attribute group: eval
Initial value: 1, 1
Get command: glGetFloatv

GL_AUTO_NORMAL
Description: True if automatic normal generation enabled
Attribute group: eval
Initial value: GL_FALSE
Get command: glIsEnabled


Hint State Variables

GL_PERSPECTIVE_CORRECTION_HINT
Description: Perspective correction hint
Attribute group: hint
Initial value: GL_DON'T CARE
Get command: glGetIntegerv

GL_POINT_SMOOTH_HINT
Description: Point smooth hint
Attribute group: hint
Initial value: GL_DON'T CARE
Get command: glGetIntegerv

GL_LINE_SMOOTH_HINT
Description: Line smooth hint
Attribute group: hint
Initial value: GL_DON'T CARE
Get command: glGetIntegerv

GL_POLYGON_SMOOTH_HINT
Description: Polygon smooth hint
Attribute group: hint
Initial value: GL_DON'T CARE
Get command: glGetIntegerv

GL_FOG_HINT
Description: Fog hint
Attribute group: hint
Initial value: GL_DON'T CARE
Get command: glGetIntegerv


Implementation-Dependent State Variables

GL_MAX_LIGHTS
Description: Maximum number of lights
Attribute group: —
Initial value: 8
Get command: glGetIntegerv

GL_MAX_CLIP_PLANES
Description: Maximum number of user clipping planes
Attribute group: —
Initial value: 6
Get command: glGetIntegerv

GL_MAX_MODELVIEW_STACK_DEPTH
Description: Maximum modelview-matrix stack depth
Attribute group: —
Initial value: 32
Get command: glGetIntegerv

GL_MAX_PROJECTION_STACK_DEPTH
Description: Maximum projection-matrix stack depth
Attribute group: —
Initial value: 2
Get command: glGetIntegerv

GL_MAX_MAX_TEXTURE_STACK_DEPTH
Description: Maximum depth of texture matrix stack
Attribute group: —
Initial value: 2
Get command: glGetIntegerv

GL_SUBPIXEL_BITS
Description: Number of bits of subpixel precision in x and y
Attribute group: —
Initial value: 4
Get command: glGetIntegerv

GL_MAX_TEXTURE_SIZE
Description: Maximum height or width of a texture image (without borders)
Attribute group: —
Initial value: 64
Get command: glGetIntegerv

GL_MAX_PIXEL_MAP_TABLE
Description: Maximum size of a glPixelMap translation table
Attribute group: —
Initial value: 32
Get command: glGetIntegerv

GL_MAX_NAME_STACK_DEPTH
Description: Maximum selection-name stack depth
Attribute group: —
Initial value: 64
Get command: glGetIntegerv

GL_MAX_LIST_NESTING
Description: Maximum display-list call nesting
Attribute group: —
Initial value: 64
Get command: glGetIntegerv

GL_MAX_EVAL_ORDER
Description: Maximum evaluator polynomial order
Attribute group: —
Initial value: 8
Get command: glGetIntegerv

GL_MAX_VIEWPORT_DIMS
Description: Maximum viewport dimensions
Attribute group: —
Initial value: —
Get command: glGetIntegerv

GL_MAX_ATTRIB_STACK_DEPTH
Description: Maximum depth of the attribute stack
Attribute group: —
Initial value: 16
Get command: glGetIntegerv

GL_AUX_BUFFERS
Description: Number of auxiliary buffers
Attribute group: —
Initial value: 0
Get command: glGetBooleanv

GL_RGBA_MODE
Description: True if color buffers store RGBA
Attribute group: —
Initial value: —
Get command: glGetBooleanv

GL_INDEX_MODE
Description: True if color buffers store indexes
Attribute group: —
Initial value: —
Get command: glGetBooleanv

GL_DOUBLEBUFFER
Description: True if front and back buffers exist
Attribute group: —
Initial value: —
Get command: glGetBooleanv

GL_STEREO
Description: True if left and right buffers exist
Attribute group: —
Initial value: —
Get command: glGetFloatv

GL_POINT_SIZE_RANGE
Description: Range (low to high) of antialiased point sizes
Attribute group: —
Initial value: 1, 1
Get command: glGetFloatv

GL_POINT_SIZE_GRANULARITY
Description: Antialiased point size granularity
Attribute group: —
Initial value: —
Get command: glGetFloatv

GL_LINE_WIDTH_RANGE
Description: Range (low to high) of antialiased line widths
Attribute group: —
Initial value: 1, 1
Get command: glGetFloatv

GL_LINE_WIDTH_GRANULARITY
Description: Antialiased line-width granularity
Attribute group: —
Initial value: —
Get command: glGetFloatv


Implementation-Dependent Pixel-Depth State Variables

GL_RED_BITS
Description: Number of bits per red component in color buffers
Attribute group: —
Initial value: —
Get command: glGetIntegerv

GL_GREEN_BITS
Description: Number of bits per green component in color buffers
Attribute group: —
Get command: glGetIntegerv
Initial value: —
GL_BLUE_BITS
Description: Number of bits per blue component in color buffers
Attribute group: —
Initial value: —
Get command: glGetIntegerv

GL_ALPHA_BITS
Description: Number of bits per alpha component in color buffers
Attribute group: —
Initial value: —
Get command: glGetIntegerv

GL_INDEX_BITS
Description: Number of bits per index in color buffers
Attribute group: —
Initial value: —
Get command: glGetIntegerv

GL_DEPTH_BITS
Description: Number of depth-buffer bitplanes
Attribute group: —
Initial value: —
Get command: glGetIntegerv

GL_STENCIL_BITS
Description: Number of stencil bitplanes
Attribute group: —
Initial value: —
Get command: glGetIntegerv

GL_ACCUM_RED_BITS
Description: Number of bits per red component in the accumulation buffer
Attribute group: —
Initial value: —
Get command: glGetIntegerv

GL_ACCUM_GREEN_BITS
Description: Number of bits per green component in the accumulation buffer
Attribute group: —
Initial value: —
Get command: glGetIntegerv

GL_ACCUM_BLUE_BITS
Description: Number of bits per blue component in the accumulation buffer
Attribute group: —
Initial value: —
Get command: glGetIntegerv

GL_ACCUM_ALPHA_BITS
Description: Number of bits per alpha component in the accumulation buffer
Attribute group: —
Initial value: —
Get command: glGetIntegerv


Miscellaneous State Variables

GL_LIST_BASE
Description: Setting of glListBase
Attribute group: list
Initial value: 0
Get command: glGetIntegerv

GL_LIST_INDEX
Description: Number of display lists under construction; 0 if none
Attribute group: —
Initial value: 0
Get command: glGetIntegerv

GL_LIST_MODE
Description: Mode of display list under construction; undefined if none
Attribute group: —
Initial value: 0
Get command: glGetIntegerv

GL_ATTRIB_STACK_DEPTH
Description: Attribute stack pointer
Attribute group: —
Initial value: 0
Get command: glGetIntegerv

GL_NAME_STACK_DEPTH
Description: Name stack depth
Attribute group: —
Initial value: 0
Get command: glGetIntegerv

GL_RENDER_MODE
Description: glRenderMode setting
Attribute group: —
Initial value: GL_RENDER
Get command: glGetIntegerv

—
Description: Current error code(s)
Attribute group: —
Initial value: 0
Get command: glGetError


glAccum

The glAccum function operates on the accumulation buffer.

void glAccum(
  GLenum op,
  GLfloat value
);

Parameters

op
The accumulation buffer operation. The accepted symbolic constants are:
GL_ACCUM
Obtains R, G, B, and A values from the buffer currently selected for reading (see glReadBuffer). Each component value is divided by 2n – 1, where n is the number of bits allocated to each color component in the currently selected buffer. The result is a floating-point value in the range [0,1], which is multiplied by value and added to the corresponding pixel component in the accumulation buffer, thereby updating the accumulation buffer.
GL_LOAD
Similar to GL_ACCUM, except that the current value in the accumulation buffer is not used in the calculation of the new value. That is, the R, G, B, and A values from the currently selected buffer are divided by 2n – 1, multiplied by value, and then stored in the corresponding accumulation buffer cell, overwriting the current value.
GL_ADD
Adds value to each R, G, B, and A in the accumulation buffer.
GL_MULT
Multiplies each R, G, B, and A in the accumulation buffer by value and returns the scaled component to its corresponding accumulation buffer location.
GL_RETURN
Transfers accumulation buffer values to the color buffer or buffers currently selected for writing. Each R, G, B, and A component is multiplied by value, then multiplied by 2n – 1, clamped to the range [0, 2n – 1 ], and stored in the corresponding display buffer cell. The only fragment operations that are applied to this transfer are pixel ownership, scissor, dithering, and color writemasks.
value
A floating-point value used in the accumulation buffer operation. The op parameter determines how value is used.

Remarks

The accumulation buffer is an extended-range color buffer. Images are not rendered into it. Rather, images rendered into one of the color buffers are added to the contents of the accumulation buffer after rendering. You can create effects such as antialiasing (of points, lines, and polygons), motion blur, and depth of field by accumulating images generated with different transformation matrices.

Each pixel in the accumulation buffer consists of red, green, blue, and alpha values. The number of bits per component in the accumulation buffer depends on the implementation. You can examine this number by calling glGetIntegerv four times, with the arguments GL_ACCUM_RED_BITS, GL_ACCUM_GREEN_BITS, GL_ACCUM_BLUE_BITS, and GL_ACCUM_ALPHA_BITS, respectively. Regardless of the number of bits per component, however, the range of values stored by each component is [ – 1, 1]. The accumulation buffer pixels are mapped one-to-one with frame buffer pixels.

The glAccum function operates on the accumulation buffer. The first argument, op, is a symbolic constant that selects an accumulation buffer operation. The second argument, value, is a floating-point value to be used in that operation. Five operations are specified: GL_ACCUM, GL_LOAD, GL_ADD, GL_MULT, and GL_RETURN.

All accumulation buffer operations are limited to the area of the current scissor box and are applied identically to the red, green, blue, and alpha components of each pixel. The contents of an accumulation buffer pixel component are undefined if the glAccum operation results in a value outside the range [ – 1,1]. The operations are as follows:

To clear the accumulation buffer, use the glClearAccum function to specify R, G, B, and A values to set it to, and issue a glClear function with the accumulation buffer enabled.

Only those pixels within the current scissor box are updated by any glAccum operation.

The following functions retrieve information related to the glAccum function:

glGet with argument GL_ACCUM_RED_BITS

glGet with argument GL_ACCUM_GREEN_BITS

glGet with argument GL_ACCUM_BLUE_BITS

glGet with argument GL_ACCUM_ALPHA_BITS

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | op was not an accepted value.
GL_INVALID_OPERATION | There was no accumulation buffer.
GL_INVALID_OPERATION | glAccum was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glBlendFunc, glClear, glClearAccum, glCopyPixels, glEnd, glGet, glLogicOp, glPixelStore, glPixelTransfer, glReadBuffer, glReadPixels, glScissor, glStencilOp


glAddSwapHintRectWIN

The glAddSwapHintRectWIN function specifies a set of rectangles that are to be copied by SwapBuffers.

void glAddSwapHintRectWIN(
  GLint x,
  GLint y,
  GLsizei width,
  GLsizei height
);

Parameters

x
The x-coordinate (in window coordinates) of the lower-left corner of the hint region rectangle.
y
The y-coordinate (in window coordinates) of the lower-left corner of the hint region rectangle.
width
The width of the hint region rectangle.
height
The height of the hint region rectangle.

Remarks

The glAddSwapHintRectWIN function speeds up animation by reducing the amount of repainting between frames. With glAddSwapHintRectWIN, you specify a set of rectangular areas that you want copied when you call SwapBuffers. When you do not specify any rectangles with glAddSwapHintRectWIN before calling SwapBuffers, the entire frame buffer is swapped. Using glAddSwapHintRectWIN to copy only parts of the buffer that changed can significantly increase the performance of SwapBuffers, especially when SwapBuffers is implemented in software.

The glAddSwapHintRectWIN function adds a rectangle to the hint region. When the PFD_SWAP_COPY flag of the PIXELFORMATDESCRIPTOR pixel format structure is set, SwapBuffers uses this region to clip the copying of the back buffer to the front buffer. You don't specify PFD_SWAP_COPY; it is set by capable hardware. The hint region is cleared after each call to SwapBuffers. With some hardware configurations, SwapBuffers can ignore the hint region and exchange the entire buffer. SwapBuffers is implemented by the system, not by the application.

OpenGL maintains a separate hint region for each window. When you call glAddSwapHintRectWIN on any rendering contexts associated with a window, the hint rectangles are combined into a single region.

Call glAddSwapHintRectWIN with a bounding rectangle for each object drawn for a frame and for each rectangle cleared to erase previous frame objects.

Note The glAddSwapHintRectWIN function is an extension function that is not part of the standard OpenGL library but is part of the GL_WIN_swap_hint extension. To check whether your implementation of OpenGL supports glAddSwapHintRectWIN, call glGetString(GL_EXTENSIONS). If it returns GL_WIN_swap_hint, glAddSwapHintRectWIN is supported. To obtain the address of an extension function, call wglGetProcAddress.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glGetString, PIXELFORMATDESCRIPTOR, SwapBuffers, wglGetProcAddress


glAlphaFunc

The glAlphaFunc function specifies the alpha test function.

void glAlphaFunc(
  GLenum func,
  GLclampf ref
);

Parameters

func
The alpha comparison function. The following are the accepted symbolic constants and their meanings.
Symbolic Constant | Meaning
GL_NEVER | Never passes.
GL_LESS | Passes if the incoming alpha value is less than the reference value.
GL_EQUAL | Passes if the incoming alpha value is equal to the reference value.
GL_LEQUAL | Passes if the incoming alpha value is less than or equal to the reference value.
GL_GREATER | Passes if the incoming alpha value is greater than the reference value.
GL_NOTEQUAL | Passes if the incoming alpha value is not equal to the reference value.
GL_GEQUAL | Passes if the incoming alpha value is greater than or equal to the reference value.
GL_ALWAYS | Always passes. This is the default.

ref
The reference value to which incoming alpha values are compared. This value is clamped to the range 0 through 1, where 0 represents the lowest possible alpha value and 1 the highest possible value. The default reference is 0.

Remarks

The alpha test discards fragments depending on the outcome of a comparison between the incoming fragments' alpha values and a constant reference value. The glAlphaFunc function specifies the reference and comparison function. The comparison is performed only if alpha testing is enabled. (For more information on GL_ALPHA_TEST, see glEnable.)

The func and ref parameters specify the conditions under which the pixel is drawn. The incoming alpha value is compared to ref using the function specified by func. If the comparison passes, the incoming fragment is drawn, conditional on subsequent stencil and depth-buffer tests. If the comparison fails, no change is made to the frame buffer at that pixel location.

The glAlphaFunc function operates on all pixel writes, including those resulting from the scan conversion of points, lines, polygons, and bitmaps, and from pixel draw and copy operations. The glAlphaFunc function does not affect screen clear operations.

Alpha testing is done only in RGBA mode.

The following functions retrieve information related to the glAlphaFunc function:

glGet with argument GL_ALPHA_TEST_FUNC

glGet with argument GL_ALPHA_TEST_REF

glIsEnabled with argument GL_ALPHA_TEST

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | func was not an accepted value.
GL_INVALID_OPERATION | glAlphaFunc was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glBlendFunc, glClear, glDepthFunc, glEnable, glEnd, glGet, glIsEnabled, glStencilFunc


glAreTexturesResident

The glAreTexturesResident function determines whether specified texture objects are resident.

GLboolean glAreTexturesResident(
  GLsizei n,
  GLuint * textures
  GLboolean * residences
);

Parameters

n
The number of textures to be queried.
textures
The address of an array containing the names of the textures to be queried.
residences
The address of an array in which the texture residence status is returned. The residence status of a texture named by an element of textures is returned in the corresponding element of residences.

Remarks

On machines with a limited amount of texture memory, OpenGL establishes a ``working set'' of textures that are resident in texture memory. These textures can be bound to a texture target much more efficiently than textures that are not resident.

The glAreTexturesResident function queries the texture residence status of the n textures named by the elements of textures. If all the named textures are resident, glAreTexturesResident returns GL_TRUE, and the contents of residences are undisturbed. If any of the named textures are not resident, glAreTexturesResident returns GL_FALSE, and detailed status is returned in the n elements of residences.

If an element of residences is GL_TRUE, then the texture named by the corresponding element of textures is resident.

To query the residence status of a single bound texture, call glGetTexParameter with the target parameter set to the target texture to which the target is bound and set the pname parameter to GL_TEXTURE_RESIDENT. You must use this method to query the resident status of a default texture.

You cannot include glAreTexturesResident in display lists.

Note The glAreTexturesResident function is only available in OpenGL version 1.1 or later.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_VALUE | n was a negative value.
GL_INVALID_VALUE | An element in textures was zero or did not contain a texture name.
GL_INVALID_OPERATION | glAreTexturesResident was called between a call to glBegin and the corresponding call to glEnd.

See Also

glBegin, glBindTexture, glEnd, glGetTexParameter, glPrioritizeTextures, glTexImage1D, glTexImage2D


glArrayElement

The glArrayElement function specifies the array elements used to render a vertex.

void glArrayElement(
  GLint index
);

Parameters

index
An index in the enabled arrays.

Remarks

Use the glArrayElement function within glBegin and glEnd pairs to specify vertex and attribute data for point, line, and polygon primitives. The glArrayElement function specifies the data for a single vertex using vertex and attribute data located at the index of the enabled vertex arrays.

You can use glArrayElement to construct primitives by indexing vertex data, rather than by streaming through arrays of data in first-to-last order. Because glArrayElement specifies a single vertex only, you can explicitly specify attributes for individual primitives. For example, you can set a single normal for each individual triangle.

When you include calls to glArrayElement in display lists, the necessary array data, determined by the array pointers and enable values, is entered in the display list also. Array pointer and enable values are determined when display lists are created, not when display lists are executed.

You can read and cache static array data at any time with glArrayElement. When you modify the elements of a static array without specifying the array again, the results of any subsequent calls to glArrayElement are undefined.

When you call glArrayElement without first calling glEnableClientState(GL_VERTEX_ARRAY), no drawing occurs, but the attributes corresponding to enabled arrays are modified. Although no error is generated when you specify an array within glBegin and glEnd pairs, the results are undefined.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glColorPointer, glDrawArrays, glEdgeFlagPointer, glEnableClientState, glEnd, glGetPointerv, glGetString, glIndexPointer, glNormalPointer, glTexCoordPointer, glVertexPointer


glBegin, glEnd

The glBegin and glEnd functions delimit the vertices of a primitive or a group of like primitives.

void glBegin(
  GLenum mode
);

void glEnd(
  void
);

Parameters

mode
The primitive or primitives that will be created from vertices presented between glBegin and the subsequent glEnd. The following are accepted symbolic constants and their meanings:
GL_POINTS
Treats each vertex as a single point. Vertex n defines point n. N points are drawn.
GL_LINES
Treats each pair of vertices as an independent line segment. Vertices 2n – 1 and 2n define line n. N/2 lines are drawn.
GL_LINE_STRIP
Draws a connected group of line segments from the first vertex to the last. Vertices n and n+1 define line n. N – 1 lines are drawn.
GL_LINE_LOOP
Draws a connected group of line segments from the first vertex to the last, then back to the first. Vertices n and n+1 define line n. The last line, however, is defined by vertices N and 1. N lines are drawn.
GL_TRIANGLES
Treats each triplet of vertices as an independent triangle. Vertices 3n – 2, 3n –1, and 3n define triangle n. N/3 triangles are drawn.
GL_TRIANGLE_STRIP
Draws a connected group of triangles. One triangle is defined for each vertex presented after the first two vertices. For odd n, vertices n, n + 1, and n + 2 define triangle n. For even n, vertices n + 1, n, and n + 2 define triangle n. N – 2 triangles are drawn.
GL_TRIANGLE_FAN
Draws a connected group of triangles. One triangle is defined for each vertex presented after the first two vertices. Vertices 1, n + 1, and n + 2 define triangle n. N – 2 triangles are drawn.
GL_QUADS
Treats each group of four vertices as an independent quadrilateral. Vertices 4n – 3, 4n – 2, 4n – 1, and 4n define quadrilateral n. N/4 quadrilaterals are drawn.
GL_QUAD_STRIP
Draws a connected group of quadrilaterals. One quadrilateral is defined for each pair of vertices presented after the first pair. Vertices 2n – 1, 2n, 2n + 2, and 2n + 1 define quadrilateral n. N quadrilaterals are drawn. Note that the order in which vertices are used to construct a quadrilateral from strip data is different from that used with independent data.
GL_POLYGON
Draws a single, convex polygon. Vertices 1 through N define this polygon.

Remarks

The glBegin and glEnd functions delimit the vertices that define a primitive or a group of like primitives. The glBegin function accepts a single argument that specifies which of ten ways the vertices are interpreted. Taking n as an integer count starting at one, and N as the total number of vertices specified, the interpretations are as follows:

  • You can use only a subset of OpenGL functions between glBegin and glEnd. The functions you can use are:

    glVertex
    glColor
    glIndex
    glNormal
    glTexCoord
    glEvalCoord
    glEvalPoint
    glMaterial
    glEdgeFlag

    You can also use glCallList or glCallLists to execute display lists that include only the preceding functions. If any other OpenGL function is called between glBegin and glEnd, the error flag is set and the function is ignored.

  • Regardless of the value chosen for mode in glBegin, there is no limit to the number of vertices you can define between glBegin and glEnd. Lines, triangles, quadrilaterals, and polygons that are incompletely specified are not drawn. Incomplete specification results when either too few vertices are provided to specify even a single primitive or when an incorrect multiple of vertices is specified. The incomplete primitive is ignored; the complete primitives are drawn.
  • The minimum specification of vertices for each primitive is:
Minimum Number of Vertices | Type of Primitive
1 | point
2 | line
3 | triangle
4 | quadrilateral
3 | polygon

Modes that require a certain multiple of vertices are GL_LINES (2), GL_TRIANGLES (3), GL_QUADS (4), and GL_QUAD_STRIP (2).

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | mode was set to an unaccepted value.
GL_INVALID_OPERATION | A function other than glVertex, glColor, glIndex, glNormal, glTexCoord, glEvalCoord, glEvalPoint, glMaterial, glEdgeFlag, glCallList, or glCallLists was called between glBegin and the corresponding glEnd.
GL_INVALID_OPERATION | glEnd was called before the corresponding glBegin was called, or glBegin was called within a glBegin/glEnd sequence.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glCallList, glCallLists, glColor, glEdgeFlag, glEvalCoord, glEvalPoint, glIndex, glMaterial, glNormal, glTexCoord, glVertex


glBindTexture

The glBindTexture function enables the creation of a named texture that is bound to a texture target.

void glBindTexture(
  GLenum target,
  GLuint texture
);

Parameters

target
The target to which the texture is bound. Must have the value GL_TEXTURE_1D or GL_TEXTURE_2D.
texture
The name of a texture; the texture name cannot currently be in use.

Remarks

The glBindTexture function enables you to create a named texture. Calling glBindTexture with target set to GL_TEXTURE_1D or GL_TEXTURE_2D, and texture set to the name of the new texture you have created binds the texture name to the appropriate texture target. When a texture is bound to a target, the previous binding for that target is no longer in effect.

Texture names are unsigned integers with the value zero reserved to represent the default texture for each texture target. Texture names and the corresponding texture contents are local to the shared display-list space of the current OpenGL rendering context; two rendering contexts share texture names only if they also share display lists. You can generate a set of new texture names using glGenTextures.

When a texture is first bound, it assumes the dimensionality of its texture target; a texture bound to GL_TEXTURE_1D becomes one-dimensional and a texture bound to GL_TEXTURE_2D becomes two-dimensional. Operations you perform on a texture target also affect a texture bound to the target. When you query a texture target, the return value is the state of the texture bound to it. Texture targets become aliases for textures currently bound to them.

When you bind a texture with glBindTexture, the binding remains active until a different texture is bound to the same target or you delete the bound texture with the glDeleteTextures function. Once you create a named texture you can bind it to a texture target that has the same dimensionality as often as needed.

It is usually much faster to use glBindTexture to bind an existing named texture to one of the texture targets than it is to reload the texture image using glTexImage1D or glTexImage2D. For additional control of texturing performance, use glPrioritizeTextures.

You can include calls to glBindTexture in display lists.

Note The glBindTexture function is only available in OpenGL version 1.1 or later.

The following functions retrieve information related to glBindTexture:

glGet with argument GL_TEXTURE_1D_BINDING

glGet with argument GL_TEXTURE_2D_BINDING

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | target was not an accepted value.
GL_INVALID_OPERATION | texture did not have the same dimensionality as target.
GL_INVALID_OPERATION | glBindTexture was called between a call to glBegin and the corresponding call to glEnd.

See Also

glAreTexturesResident, glDeleteTextures, glGenTextures, glGet, glGetTexParameter, glIsTexture, glPrioritizeTextures, glTexImage1D, glTexImage2D, glTexParameter


glBitmap

The glBitmap function draws a bitmap.

void glBitmap(
  GLsizei width,
  GLsizei height,
  GLfloat xorig,
  GLfloat yorig,
  GLfloat xmove,
  GLfloat ymove,
  const GLubyte * bitmap
);

Parameters

width, height
The pixel width and height of the bitmap image.
xorig, yorig
The location of the origin in the bitmap image. The origin is measured from the lower-left corner of the bitmap, with right and up directions being the positive axes.
xmove, ymove
The x and y offsets to be added to the current raster position after the bitmap is drawn.
bitmap
The address of the bitmap image.

Remarks

A bitmap is a binary image. When drawn, the bitmap is positioned relative to the current raster position, and frame buffer pixels corresponding to 1s in the bitmap are written using the current raster color or index. Frame-buffer pixels corresponding to zeros in the bitmap are not modified.

The bitmap image is interpreted like image data for the glDrawPixels function, with width and height corresponding to the width and height arguments of that function, and with type set to GL_BITMAP and format set to GL_COLOR_INDEX. Modes you specify using glPixelStore affect the interpretation of bitmap image data; modes you specify using glPixelTransfer do not.

If the current raster position is invalid, glBitmap is ignored. Otherwise, the lower-left corner of the bitmap image is positioned at the following window coordinates:

x (w) = ë x (r) – x (o) û

y (w) = ë y (r) – y (o) û

In these coordinates, (xr, yr) is the raster position, and (xo, yo) is the bitmap origin. Fragments are then generated for each pixel corresponding to a 1 in the bitmap image. These fragments are generated using the current raster z-coordinate, color or color index, and current raster texture coordinates. They are then treated just as if they had been generated by a point, line, or polygon, including texture mapping, fogging, and all per-fragment operations such as alpha and depth testing.

After the bitmap has been drawn, the x and y coordinates of the current raster position are offset by xmove and ymove. No change is made to the z-coordinate of the current raster position, or to the current raster color, index, or texture coordinates.

The following functions retrieve information related to the glBitmap function:

glGet with argument GL_CURRENT_RASTER_POSITION
glGet with argument GL_CURRENT_RASTER_COLOR
glGet with argument GL_CURRENT_RASTER_INDEX
glGet with argument GL_CURRENT_RASTER_TEXTURE_COORDS
glGet with argument GL_CURRENT_RASTER_POSITION_VALID

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_VALUE | width or height is negative.
GL_INVALID_OPERATION | glBitmap is called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glDrawPixels, glEnd, glPixelStore, glPixelTransfer, glRasterPos


glBlendFunc

The glBlendFunc function specifies pixel arithmetic.

void glBlendFunc(
  GLenum sfactor,
  GLenum dfactor
);

Parameters

sfactor
Specifies how the red, green, blue, and alpha source-blending factors are computed. Nine symbolic constants are accepted: GL_ZERO, GL_ONE, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, and GL_SRC_ALPHA_SATURATE.
dfactor
Specifies how the red, green, blue, and alpha destination-blending factors are computed. Eight symbolic constants are accepted: GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, and GL_ONE_MINUS_DST_ALPHA.

Remarks

In RGB mode, pixels can be drawn using a function that blends the incoming (source) RGBA values with the RGBA values that are already in the frame buffer (the destination values). By default, blending is disabled. Use glEnable and glDisable with the GL_BLEND argument to enable and disable blending.

When enabled, glBlendFunc defines the operation of blending. The sfactor parameter specifies which of nine methods is used to scale the source color components. The dfactor parameter specifies which of eight methods is used to scale the destination color components. The eleven possible methods are described in the following table. Each method defines four scale factors, one each for red, green, blue, and alpha.

In the table and in subsequent equations, source and destination color components are referred to as (R (s) ,G (s) ,B (s) ,A (s) ) and (R (d) ,G (d) ,B (d) ,A (d) ). They are understood to have integer values between zero and (k (R) ,k (G) ,k (B) ,k (A) ), where

k (c) = 2^m (c) – 1

and (m (R) ,m (G) ,m (B) ,m (A) ) is the number of red, green, blue, and alpha bitplanes.

Source and destination scale factors are referred to as (s (R) ,s (G) ,s (B) ,s (A) ) and (d (R) ,d (G) ,d (B) ,d (A) ). The scale factors described in the table, denoted (f (R) ,f (G) ,f (B) ,f (A) ), represent either source or destination factors. All scale factors have range [0,1].

Parameter | (f (R) ,f (G) ,f (B) ,f (A) )
GL_ZERO | (0,0,0,0)
GL_ONE | (1,1,1,1)
GL_SRC_COLOR | (R (s) /k (R) ,G (s) /k (G) ,B (s) /k (B) ,A (s) /k (A) )
GL_ONE_MINUS_SRC_COLOR | (1,1,1,1). (R (s) /k (R) ,G (s) /k (G) ,B (s) /k (B) ,A (s) /k (A) )
GL_DST_COLOR | (R (d) /k (R) ,G (d) /k (G) ,B (d) /k (B) ,A (d) /k (A) )
GL_ONE_MINUS_DST_COLOR | (1,1,1,1)
GL_SRC_ALPHA | (R (d) /k (R) ,G (d) /k (G) ,B (d) /k (B) ,A (d) /k (A) ) (A (s) /k (A) ,A (s) /k (A) ,A (s) /k (A) ,A (s) /k (A) )
GL_ONE_MINUS_SRC_ALPHA | (1,1,1,1) (A (s) /k (A) ,A (s) /k (A) ,A (s) /k (A) ,A (s) /k (A) )
GL_DST_ALPHA | (A (d) /k (A) ,A (d) /k (A) ,A (d) /k (A) ,A (d) /k (A) )
GL_ONE_MINUS_DST_ALPHA | (1,1,1,1) (A (d) /k (A) ,A (d) /k (A) ,A (d) /k (A) ,A (d) /k (A) )
GL_SRC_ALPHA_SATURATE | (i,i,i,1)

In the table,

i = min (A (s) ,k (A) – A (d) ) / kA

To determine the blended RGBA values of a pixel when drawing in RGB mode, the system uses the following equations:

R (d) = min(kR,RssR+RddR)

G (d) = min(kG,GssG+GddG)

B (d) = min(kB,BssB+BddB)

A (d) = min(kA,AssA+AddA)

Despite the apparent precision of the above equations, blending arithmetic is not exactly specified, because blending operates with imprecise integer color values. However, a blend factor that should be equal to one is guaranteed not to modify its multiplicand, and a blend factor equal to zero reduces its multiplicand to zero. Thus, for example, when sfactor is GL_SRC_ALPHA, dfactor is GL_ONE_MINUS_SRC_ALPHA, and A (s) is equal to k (A) , the equations reduce to simple replacement:

R (d) = R (s)

G (d) = G (s)

B (d) = B (s)

A (d) = A (s

)

Examples

Transparency is best implemented using glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) with primitives sorted from farthest to nearest. Note that this transparency calculation does not require the presence of alpha bitplanes in the frame buffer.

You can also use glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) for rendering antialiased points and lines in arbitrary order.

To optimize polygon antialiasing, use glBlendFunc(GL_SRC_ALPHA_SATURATE, GL_ONE) with polygons sorted from nearest to farthest. (See the GL_POLYGON_SMOOTH argument in glEnable for information on polygon antialiasing.) Destination alpha bitplanes, which must be present for this blend function to operate correctly, store the accumulated coverage.

Incoming (source) alpha is a material opacity, ranging from 1.0 (K (A) ), representing complete opacity, to 0.0 (0), representing complete transparency.

When you enable more than one color buffer for drawing, each enabled buffer is blended separately, and the contents of the buffer is used for destination color. (See glDrawBuffer.)

Blending affects only RGB rendering. It is ignored by color-index renderers.

The following functions retrieve information related to glBlendFunc:

glGet with argument GL_BLEND_SRC

glGet with argument GL_BLEND_DST

glIsEnabled with argument GL_BLEND

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | Either sfactor or dfactor was not an accepted value.
GL_INVALID_OPERATION | glBlendFunc was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glAlphaFunc, glBegin, glClear, glDisable, glDrawBuffer, glEnable, glGet, glIsEnabled, glLogicOp, glStencilFunc


glCallList

The glCallList function executes a display list.

void glCallList(
  GLuint list
);

Parameters

list
The integer name of the display list to be executed.

Remarks

You start execution of the named display list with glCallList. The functions saved in the display list are executed in order, just as if you called them without using a display list. If list has not been defined as a display list, glCallList is ignored.

The glCallList function can appear inside a display list. To avoid the possibility of infinite recursion resulting from display lists calling one another, a limit is placed on the nesting level of display lists during display-list execution. This limit is at least 64, and it depends on the implementation.

The OpenGL state is not saved and restored across a call to glCallList. Thus, changes made to the OpenGL state during the execution of a display list remain after execution of the display list is completed. To preserve the OpenGL state across glCallList calls, use glPushAttrib, glPopAttrib, glPushMatrix, and glPopMatrix.

You can execute display lists between a call to glBegin and the corresponding call to glEnd, as long as the display list includes only functions that are allowed in this interval.

The following functions retrieve information related to glCallList:

glGet with argument GL_MAX_LIST_NESTING

glIsList

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glCallLists, glDeleteLists, glEnd, glGenLists, glGet, glIsList, glNewList, glPopAttrib, glPopMatrix, glPushAttrib, glPushMatrix


glCallLists

The glCallLists function executes a list of display lists.

void glCallLists(
  GLsizei n,
  GLenum type,
  const GLvoid * lists
);

Parameters

n
The number of display lists to be executed.
type
The type of values in lists. The following symbolic constants are accepted:
GL_BYTE
The lists parameter is treated as an array of signed bytes, each in the range – 128 through 127.
GL_UNSIGNED_BYTE
The lists parameter is treated as an array of unsigned bytes, each in the range 0 through 255.
GL_SHORT
The lists parameter is treated as an array of signed two-byte integers, each in the range – 32768 through 32767.
GL_UNSIGNED_SHORT
The lists parameter is treated as an array of unsigned two-byte integers, each in the range 0 through 65535.
GL_INT
The lists parameter is treated as an array of signed four-byte integers.
GL_UNSIGNED_INT
The lists parameter is treated as an array of unsigned four-byte integers.
GL_FLOAT
The lists parameter is treated as an array of four-byte, floating-point values.
GL_2_BYTES
The lists parameter is treated as an array of unsigned bytes. Each pair of bytes specifies a single display-list name. The value of the pair is computed as 256 times the unsigned value of the first byte plus the unsigned value of the second byte.
GL_3_BYTES
The lists parameter is treated as an array of unsigned bytes. Each triplet of bytes specifies a single display list name. The value of the triplet is computed as 65536 times the unsigned value of the first byte, plus 256 times the unsigned value of the second byte, plus the unsigned value of the third byte.
GL_4_BYTES
The lists parameter is treated as an array of unsigned bytes. Each quadruplet of bytes specifies a single display list name. The value of the quadruplet is computed as 16777216 times the unsigned value of the first byte, plus 65536 times the unsigned value of the second byte, plus 256 times the unsigned value of the third byte, plus the unsigned value of the fourth byte.
lists
The address of an array of name offsets in the display list. The pointer type is void because the offsets can be bytes, shorts, ints, or floats, depending on the value of type.

Remarks

The glCallLists function causes each display list in the list of names passed as lists to be executed. As a result, the functions saved in each display list are executed in order, just as if they were called without using a display list. Names of display lists that have not been defined are ignored.

The glCallLists function provides an efficient means for executing display lists.The n parameter specifies the number of lists with various name formats (specified by the type parameter) glCallLists executes.

The list of display list names is not null-terminated. Rather, n specifies how many names are to be taken from lists.

The glListBase function makes an additional level of indirection available. The glListBase function specifies an unsigned offset that is added to each display list name specified in lists before that display list is executed.

The glCallLists function can appear inside a display list. To avoid the possibility of infinite recursion resulting from display lists calling one another, a limit is placed on the nesting level of display lists during display list execution. This limit must be at least 64, and it depends on the implementation.

The OpenGL state is not saved and restored across a call to glCallLists. Thus, changes made to the OpenGL state during the execution of the display lists remain after execution is completed. Use glPushAttrib, glPopAttrib, glPushMatrix, and glPopMatrix to preserve the OpenGL state across glCallLists calls.

You can execute display lists between a call to glBegin and the corresponding call to glEnd, as long as the display list includes only functions that are allowed in this interval.

The following functions retrieve information related to the glCallLists function:

glGet with argument GL_LIST_BASE

glGet with argument GL_MAX_LIST_NESTING

glIsList

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glCallList, glDeleteLists, glEnd, glGenLists, glGet, glIsList, glListBase, glNewList, glPopAttrib, glPopMatrix, glPushAttrib, glPushMatrix


glClear

The glClear function clears buffers to preset values.

void glClear(
  GLbitfield mask
);

Parameters

mask
Bitwise OR of masks that indicate the buffers to be cleared. The four masks are as follows.
Mask | Buffer to be Cleared
GL_COLOR_BUFFER_BIT | The buffers currently enabled for color writing.
GL_DEPTH_BUFFER_BIT | The depth buffer.
GL_ACCUM_BUFFER_BIT | The accumulation buffer.
GL_STENCIL_BUFFER_BIT | The stencil buffer.

Remarks

The glClear function sets the bitplane area of the window to values previously selected by glClearColor, glClearIndex, glClearDepth, glClearStencil, and glClearAccum. You can clear multiple color buffers simultaneously by selecting more than one buffer at a time using glDrawBuffer.

The pixel-ownership test, the scissor test, dithering, and the buffer writemasks affect the operation of glClear. The scissor box bounds the cleared region. The alpha function, blend function, logical operation, stenciling, texture mapping, and z-buffering are ignored by glClear.

The glClear function takes a single argument (mask) that is the bitwise OR of several values indicating which buffer is to be cleared.

The value to which each buffer is cleared depends on the setting of the clear value for that buffer.

If a buffer is not present, a glClear call directed at that buffer has no effect.

The following functions retrieve information related to glClear:

glGet with argument GL_ACCUM_CLEAR_VALUE

glGet with argument GL_DEPTH_CLEAR_VALUE

glGet with argument GL_INDEX_CLEAR_VALUE

glGet with argument GL_COLOR_CLEAR_VALUE

glGet with argument GL_STENCIL_CLEAR_VALUE

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_VALUE | Any bit other than the four defined bits was set in mask.
GL_INVALID_OPERATION | glClear was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glClearAccum, glClearColor, glClearDepth, glClearIndex, glClearStencil, glDrawBuffer, glGet, glScissor


glClearAccum

The glClearAccum function clears values for the accumulation buffer.

void glClearAccum(
  GLfloat red,
  GLfloat green,
  GLfloat blue,
  GLfloat alpha
);

Parameters

red, green, blue, alpha
The red, green, blue, and alpha values used when the accumulation buffer is cleared. The default values are all zero.

Remarks

The glClearAccum function specifies the red, green, blue, and alpha values used by glClear to clear the accumulation buffer.

Values specified by glClearAccum are clamped to the range [ – 1,1].

The following function retrieves information related to glClearAccum:

glGet with argument GL_ACCUM_CLEAR_VALUE

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glClearAccum was called between a call to glBegin and the corresponding call to glEnd

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glClear, glEnd, glGet


glClearColor

The glClearColor function specifies clear values for the color buffers.

void glClearColor(
  GLclampf red,
  GLclampf green,
  GLclampf blue,
  GLclampf alpha
);

Parameters

red, green, blue, alpha
The red, green, blue, and alpha values used when the color buffers are cleared. The default values are all zero.

Remarks

The glClearColor function specifies the red, green, blue, and alpha values used by glClear to clear the color buffers. Values specified by glClearColor are clamped to the range [0,1].

The following functions retrieve information related to the glClearColor function:

glGet with argument GL_ACCUM_CLEAR_VALUE

glGet with argument GL_COLOR_CLEAR_VALUE

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glClearColor was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glClear, glEnd, glGet


glClearDepth

The glClearDepth function specifies the clear value for the depth buffer.

void glClearDepth(
  GLclampd depth
);

Parameters

depth
The depth value used when the depth buffer is cleared.

Remarks

The glClearDepth function specifies the depth value used by glClear to clear the depth buffer. Values specified by glClearDepth are clamped to the range [0,1].

The following function retrieves information related to the glClearDepth function:

glGet with argument GL_DEPTH_CLEAR_VALUE

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glClearDepth was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glClear, glEnd, glGet


glClearIndex

The glClearIndex function specifies the clear value for the color-index buffers.

void glClearIndex(
  GLfloat c
);

Parameters

c
The index used when the color-index buffers are cleared. The default value is zero.

Remarks

The glClearIndex function specifies the index used by glClear to clear the color-index buffers. The c parameter is not clamped. Rather, c is converted to a fixed-point value with unspecified precision to the right of the binary point. The integer part of this value is then masked with 2^m – 1, where m is the number of bits in a color index stored in the frame buffer.

The following functions retrieve information related to glClearIndex:

glGet with argument GL_INDEX_CLEAR_VALUE

glGet with argument GL_INDEX_BITS

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glClearIndex was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glClear, glEnd, glGet


glClearStencil

The glClearStencil function specifies the clear value for the stencil buffer.

void glClearStencil(
  GLint s
);

Parameters

s
The index used when the stencil buffer is cleared. The default value is zero.

Remarks

The glClearStencil function specifies the index used by glClear to clear the stencil buffer. The s parameter is masked with 2^m – 1, where m is the number of bits in the stencil buffer.

The following functions retrieve information related to the glClearStencil function:

glGet with argument GL_STENCIL_CLEAR_VALUE

glGet with argument GL_STENCIL_BITS

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glClearStencil was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glClear, glEnd, glGet


glClipPlane

The glClipPlane function specifies a plane against which all geometry is clipped.

void glClipPlane(
  GLenum plane,
  const GLdouble * equation
);

Parameters

plane
The clipping plane that is being positioned. Symbolic names of the form GL_CLIP_PLANEi, where i is an integer between 0 and GL_MAX_CLIP_PLANES – 1, are accepted.
equation
The address of an array of four double-precision floating-point values. These values are interpreted as a plane equation.

Remarks

Geometry is always clipped against the boundaries of a six-plane frustum in x, y, and z. The glClipPlane function allows the specification of additional planes, not necessarily perpendicular to the x- , y- , or z-axis, against which all geometry is clipped. Up to GL_MAX_CLIP_PLANES planes can be specified, where GL_MAX_CLIP_PLANES is at least six in all implementations. Because the resulting clipping region is the intersection of the defined half-spaces, it is always convex.

The glClipPlane function specifies a half-space using a four-component plane equation. When you call glClipPlane, equation is transformed by the inverse of the modelview matrix and stored in the resulting eye coordinates. Subsequent changes to the modelview matrix have no effect on the stored plane-equation components. If the dot product of the eye coordinates of a vertex with the stored plane equation components is positive or zero, the vertex is in with respect to that clipping plane. Otherwise, it is out.

Use the glEnable and glDisable functions to enable and disable clipping planes. Call clipping planes with the argument GL_CLIP_PLANEi, where i is the plane number.

By default, all clipping planes are defined as (0,0,0,0) in eye coordinates and are disabled.

It is always the case that GL_CLIP_PLANEi = GL_CLIP_PLANE0 + i.

The following functions retrieve information related to glClipPlane:

glGetClipPlane

glIsEnabled with argument GL_CLIP_PLANEi

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | plane was not an accepted value.
GL_INVALID_OPERATION | glClipPlane was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glDisable, glEnable, glEnd, glGetClipPlane, glIsEnabled


glColor

glColor3b, glColor3d, glColor3f, glColor3i, glColor3s, glColor3ub, glColor3ui, glColor3us, glColor4b, glColor4d, glColor4f, glColor4i, glColor4s, glColor4ub, glColor4ui, glColor4us, glColor3bv, glColor3dv, glColor3fv, glColor3iv, glColor3sv, glColor3ubv, glColor3uiv, glColor3usv, glColor4bv, glColor4dv, glColor4fv, glColor4iv, glColor4sv, glColor4ubv, glColor4uiv, glColor4usv

These functions set the current color.

void glColor3b(
  GLbyte red,
  GLbyte green,
  GLbyte blue
);

void glColor3d(
  GLdouble red,
  GLdouble green,
  GLdouble blue
);

void glColor3f(
  GLfloat red,
  GLfloat green,
  GLfloat blue
);

void glColor3i(
  GLint red,
  GLint green,
  GLint blue
);

void glColor3s(
  GLshort red,
  GLshort green,
  GLshort blue
);

void glColor3ub(
  GLubyte red,
  GLubyte green,
  GLubyte blue
);

void glColor3ui(
  GLuint red,
  GLuint green,
  GLuint blue
);

void glColor3us(
  GLushort red,
  GLushort green,
  GLushort blue
);

void glColor4b(
  GLbyte red,
  GLbyte green,
  GLbyte blue,
  GLbyte alpha
);

void glColor4d(
  GLdouble red,
  GLdouble green,
  GLdouble blue,
  GLdouble alpha
);

void glColor4f(
  GLfloat red,
  GLfloat green,
  GLfloat blue,
  GLfloat alpha
);

void glColor4i(
  GLint red,
  GLint green,
  GLint blue,
  GLint alpha
);

void glColor4s(
  GLshort red,
  GLshort green,
  GLshort blue,
  GLshort alpha
);

void glColor4ub(
  GLubyte red,
  GLubyte green,
  GLubyte blue,
  GLubyte alpha
);

void glColor4ui(
  GLuint red,
  GLuint green,
  GLuint blue,
  GLuint alpha
);

void glColor4us(
  GLushort red,
  GLushort green,
  GLushort blue,
  GLushort alpha
);

Parameters

red, green, blue
New red, green, and blue values for the current color.
alpha
A new alpha value for the current color. Included only in the four-argument glColor4 function.
void glColor3bv(
  const GLbyte *v
);

void glColor3dv(
  const GLdouble *v
);

void glColor3fv(
  const GLfloat *v
);

void glColor3iv(
  const GLint *v
);

void glColor3sv(
  const GLshort *v
);

void glColor3ubv(
  const GLubyte *v
);

void glColor3uiv(
  const GLuint *v
);

void glColor3usv(
  const GLushort *v
);

void glColor4bv(
  const GLbyte *v
);

void glColor4dv(
  const GLdouble *v
);

void glColor4fv(
  const GLfloat *v
);

void glColor4iv(
  const GLint *v
);

void glColor4sv(
  const GLshort *v
);

void glColor4ubv(
  const GLubyte *v
);

void glColor4uiv(
  const GLuint *v
);

void glColor4usv(
  const GLushort *v
);

Parameters

v
A pointer to an array that contains red, green, blue, and (sometimes) alpha values.

Remarks

OpenGL stores both a current single-valued color index and a current four-valued RGBA color. The glColor function sets a new four-valued RGBA color.

There are two major variants to glColor:

  • The glColor3 variants specify new red, green, and blue values explicitly, and set the current alpha value to 1.0 implicitly.
  • The glColor4 variants specify all four color components explicitly.

The glColor3b, glColor4b, glColor3s, glColor4s, glColor3i, and glColor4i functions take three or four signed byte, short, or long integers as arguments. When you append v to the name, the color functions can take a pointer to an array of such values.

Current color values are stored in floating-point format, with unspecified mantissa and exponent sizes. Unsigned integer color components, when specified, are linearly mapped to floating-point values such that the largest representable value maps to 1.0 (full intensity), and zero maps to 0.0 (zero intensity). Signed integer color components, when specified, are linearly mapped to floating-point values such that the most positive representable value maps to 1.0, and the most negative representable value maps to –1.0. Floating-point values are mapped directly.

Neither floating-point nor signed integer values are clamped to the range [0,1] before updating the current color. However, color components are clamped to this range before they are interpolated or written into a color buffer.

You can update the current color at any time. In particular, you can call glColor between a call to glBegin and the corresponding call to glEnd.

The following functions retrieve information related to the glColor functions:

glGet with argument GL_CURRENT_COLOR

glGet with argument GL_RGBA_MODE

See Also

glBegin, glEnd, glGet, glIndex


glColorMask

The glColorMask function enables and disables writing of frame-buffer color components.

void glColorMask(
  GLboolean red,
  GLboolean green,
  GLboolean blue,
  GLboolean alpha
);

Parameters

red, green, blue, alpha
Specify whether red, green, blue, and alpha can or cannot be written into the frame buffer. The default values are all GL_TRUE, indicating that the color components can be written.

Remarks

The glColorMask function specifies whether the individual color components in the frame buffer can or cannot be written. If red is GL_FALSE, for example, no change is made to the red component of any pixel in any of the color buffers, regardless of the drawing operation attempted.

Changes to individual bits of components cannot be controlled. Rather, changes are either enabled or disabled for entire color components.

The following functions retrieve information related to glColorMask:

glGet with argument GL_COLOR_WRITEMASK

glGet with argument GL_RGBA_MODE

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glColorMask was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glColor, glDepthMask, glEnd, glGet, glIndex, glIndexMask, glStencilMask


glColorMaterial

The glColorMaterial function causes a material color to track the current color.

void glColorMaterial(
  GLenum face,
  GLenum mode
);

Parameters

face
Specifies whether front, back, or both front and back material parameters should track the current color. Accepted values are GL_FRONT, GL_BACK, and GL_FRONT_AND_BACK. The default value is GL_FRONT_AND_BACK.
mode
Specifies which of several material parameters track the current color. Accepted values are GL_EMISSION, GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, and GL_AMBIENT_AND_DIFFUSE. The default value is GL_AMBIENT_AND_DIFFUSE.

Remarks

The glColorMaterial function specifies which material parameters track the current color. When you enable GL_COLOR_MATERIAL, the material parameter or parameters specified by mode, of the material or materials specified by face, track the current color at all times. You enable and disable GL_COLOR_MATERIAL with the functions glEnable and glDisable, which you call with GL_COLOR_MATERIAL as their argument. By default, GL_COLOR_MATERIAL is disabled.

With glColorMaterial, you can change a subset of material parameters for each vertex using only the glColor function, without calling glMaterial. If you are going to specify only such a subset of parameters for each vertex, it is better to do so with glColorMaterial than with glMaterial.

The following functions retrieve information related to glColorMaterial:

glGet with argument GL_COLOR_MATERIAL_PARAMETER

glGet with argument GL_COLOR_MATERIAL_FACE

glIsEnabled with argument GL_COLOR_MATERIAL

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | face or mode was not an accepted value.
GL_INVALID_OPERATION | glColorMaterial was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glColor, glDisable, glEnable, glEnd, glGet, glIsEnabled, glLight, glLightModel, glMaterial


glColorPointer

The glColorPointer function defines an array of colors.

void glColorPointer(
  GLint size,
  GLenum type,
  GLsizei stride,
  GLsizei count,
  const GLvoid * pointer
);

Parameters

size
The number of components per color. The value must be either 3 or 4.
type
The data type of each color component in a color array. Acceptable data types are specified with the following constants: GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FLOAT, or GL_DOUBLE.
stride
The byte offset between consecutive colors. When stride is zero, the colors are tightly packed in the array.
count
The number of static colors, counting from the first color.
pointer
A pointer to the first component of the first color element in a color array.

Remarks

The glColorPointer function specifies the location and data format of an array of color components to use when rendering. The stride parameter determines the byte offset from one color to the next, enabling the packing of vertex attributes in a single array or storage in separate arrays. In some implementations, storing vertex attributes in a single array can be more efficient than the use of separate arrays. Starting from the first color array element, count indicates the total number of static elements. You can modify static elements, but once the elements are modified, you must explicitly specify the array again before using the array for any rendering. Nonstatic color array elements are not accessed until you call glDrawArrays or glArrayElement.

The color array is enabled when you specify the GL_COLOR_ARRAY constant with glEnableClientState. Calling glArrayElement, or glDrawArrays uses the color array that is thus enabled. By default, the color array is disabled. The glColorPointer calls are not entered in display lists.

When you specify a color array using glColorPointer, the values of all the function's color array parameters are saved in a client-side state, and you can cache static array elements. Because the color array parameters are in a client-side state, glPushAttrib and glPopAttrib do not save or restore the parameters' values.

Although specifying the color array within glBegin and glEnd pairs does not generate an error, the results are undefined.

The following functions retrieve information related to the glColorPointer function:

glIsEnabled with argument GL_COLOR_ARRAY

glGet with argument GL_COLOR_ARRAY_SIZE

glGet with argument GL_COLOR_ARRAY_TYPE

glGet with argument GL_COLOR_ARRAY_STRIDE

glGet with argument GL_COLOR_ARRAY_COUNT

glGetPointerv with argument GL_COLOR_ARRAY_POINTER

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_VALUE | size was not 3 or 4.
GL_INVALID_ENUM | type was not an accepted value.
GL_INVALID_VALUE | stride or count was negative.

See Also

glArrayElement, glBegin, glDrawArrays, glEdgeFlagPointer, glEnableClientState, glEnd, glGet, glGetString, glGetPointerv, glIndexPointer, glIsEnabled, glNormalPointer, glPopAttrib, glPushAttrib, glTexCoordPointer, glVertexPointer


glColorTableEXT

The glColorTableEXT function specifies the format and size of a palette for targeted paletted textures.

void glColorTableEXT(
  GLenum target,
  GLenum internalFormat,
  GLsizei width,
  GLenum format,
  GLenum type,
  const GLvoid * data
);

Parameters

target
The target texture that is to have its palette changed. Must be TEXTURE_1D, TEXTURE_2D, PROXY_TEXTURE_1D, or PROXY_TEXTURE_2D.
internalFormat
The internal format and resolution of the palette. This parameter can assume one of the following symbolic values:
Constant | Base Format | R Bits | G Bits | B Bits | A Bits
GL_R3_G3_B2 GL_RGB 3 3 2 —
GL_RGB4 GL_RGB 4 4 4 —
GL_RGB5 GL_RGB 5 5 5 —
GL_RGB8 GL_RGB 8 8 8 —
GL_RGB10 GL_RGB 10 10 10 —
GL_RGB12 GL_RGB 12 12 12 —
GL_RGB16 GL_RGB 16 16 16 —
GL_RGBA2 | GL_RGBA | 2 | 2 | 2 | 2
GL_RGBA4 | GL_RGBA | 4 | 4 | 4 | 4
GL_RGB5_A1 | GL_RGBA | 5 | 5 | 5 | 1
GL_RGBA8 | GL_RGBA | 8 | 8 | 8 | 8
GL_RGB10_A2 | GL_RGBA | 10 | 10 | 10 | 2
GL_RGB12 | GL_RGBA | 12 | 12 | 12 | 12
GL_RGBA16 | GL_RGBA | 16 | 16 | 16 | 16

width
The size of the palette. Must be 2^n ≥ 1 for some integer n.
format
The format of the pixel data. The following symbolic constants are accepted:
GL_RGBA
Each pixel is a group of four components in this order: red, green, blue, alpha. The RGBA format is determined in this way:

1.The glColorTableEXT function converts floating-point values directly to an internal format with unspecified precision. Signed integer values are mapped linearly to the internal format such that the most positive representable integer value maps to 1.0, and the most negative representable integer value maps to –1.0. Unsigned integer data is mapped similarly: the largest integer value maps to 1.0, and zero maps to 0.0.

2.The glColorTableEXT function multiplies the resulting color values by GL_c_SCALE and adds them to GL_c_BIAS, where c is RED, GREEN, BLUE, and ALPHA for the respective color components. The results are clamped to the range [0,1].

3.If GL_MAP_COLOR is TRUE, glColorTableEXT scales each color component by the size of lookup table GL_PIXEL_MAP_c_TO_c, then replaces the component by the value that it references in that table; c is R, G, B, or A, respectively.

4.The glColorTableEXT function converts the resulting RGBA colors to fragments by attaching the current raster position z-coordinate and texture coordinates to each pixel, then assigning x and y window coordinates to the nth fragment such that

x (n) = x (r) + n mod width

y (n) = y (r) + ë n/width û

where (x (r) , y (r) ) is the current raster position.

5.These pixel fragments are then treated just like the fragments generated by rasterizing points, lines, or polygons. The glColorTableEXT function applies texture mapping, fog, and all the fragment operations before writing the fragments to the frame buffer.

GL_RED
Each pixel is a single red component.

The glColorTableEXT function converts this component to the internal format in the same way that the red component of an RGBA pixel is, then converts it to an RGBA pixel with green and blue set to 0.0, and alpha set to 1.0. After this conversion, the pixel is treated just as if it had been read as an RGBA pixel.

GL_GREEN
Each pixel is a single green component.

The glColorTableEXT function converts this component to the internal format in the same way that the green component of an RGBA pixel is, and then converts it to an RGBA pixel with red and blue set to 0.0, and alpha set to 1.0. After this conversion, the pixel is treated just as if it had been read as an RGBA pixel.

GL_BLUE
Each pixel is a single blue component.

The glColorTableEXT function converts this component to the internal format in the same way that the blue component of an RGBA pixel is, and then converts it to an RGBA pixel with red and green set to 0.0, and alpha set to 1.0. After this conversion, the pixel is treated just as if it had been read as an RGBA pixel.

GL_ALPHA
Each pixel is a single alpha component.

The glColorTableEXT function converts this component to the internal format in the same way that the alpha component of an RGBA pixel is, and then converts it to an RGBA pixel with red, green, and blue set to 0.0. After this conversion, the pixel is treated just as if it had been read as an RGBA pixel.

GL_RGB
Each pixel is a group of three components in this order: red, green, blue.

The glColorTableEXT function converts each component to the internal format in the same way that the red, green, and blue components of an RGBA pixel are. The color triple is converted to an RGBA pixel with alpha set to 1.0. After this conversion, the pixel is treated just as if it had been read as an RGBA pixel.

GL_BGR_EXT
Each pixel is a group of three components in this order: blue, green, red.

GL_BGR_EXT provides a format that matches the memory layout of Windows device-independent bitmaps (DIBs). Thus your applications can use the same data with Win32 function calls and OpenGL pixel function calls.

GL_BGRA_EXT
Each pixel is a group of four components in this order: blue, green, red, alpha.

GL_BGRA_EXT provides a format that matches the memory layout of Windows device-independent bitmaps (DIBs). Thus your applications can use the same data with Win32 function calls and OpenGL pixel function calls.

type
The data type for data. The following symbolic constants are accepted: GL_UNSIGNED_BYTE, GL_BYTE, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, and GL_FLOAT.

The following table summarizes the meaning of the valid constants for the type parameter.
Constant | Meaning
GL_UNSIGNED_BYTE | Unsigned 8-bit integer
GL_BYTE | Signed 8-bit integer
GL_UNSIGNED_SHORT | Unsigned 16-bit integer
GL_SHORT | Signed 16-bit integer
GL_UNSIGNED_INT | Unsigned 32-bit integer
GL_INT | 32-bit integer
GL_FLOAT | Single-precision floating-point value

data
A pointer to the paletted texture data. The data is treated as single pixels of a 1-D texture palette entry for a palette entry.

Remarks

Paletted textures are defined with a palette of colors and a set of image data that is composed of indexes to color entries of a palette (a color table).

The glColorTableEXT function specifies the texture palette of a targeted texture. It takes the data from memory and converts the data as if each palette entry is a single pixel of a 1-D texture. The glColorTableEXT function unpacks and converts the data and translates it into an internal format that matches the given format as closely as possible.

If a palette's width is greater than the range of the color indexes in the texture data, some of the palette entries are unused. If a palette's width is less than the range of the color indexes in the texture data, the most significant bits of the texture data are ignored and only the appropriate number of bits in the index are used when accessing the palette. When you specify a proxy target using PROXY_TEXTURE_1D or PROXY_TEXTURE_2D, the palette of the proxy texture is resized and its parameters are set but no data is transferred or accessed.

When the target parameter is GL_PROXY_TEXTURE_1D or GL_PROXY_TEXTURE_2D, and the implementation does not support the values specified for either format or width, glColorTableEXT can fail to create the requested color table. In this case, the color table is empty and all parameters retrieved will be zero. You can determine whether OpenGL supports a particular color table format and size by calling glColorTableEXT with a proxy target, and then calling glGetColorTableParameterivEXT or glGetColorTableParameterfvEXT to determine whether the width parameter matches that set by glColorTableEXT. If the retrieved width is zero, the color table request by glColorTable failed. If the retrieved width is not zero, you can call glColorTable with the real target with TEXTURE_1D or TEXTURE_2D to set the color table.

Note The glColorTableEXT function is an extension function that is not part of the standard OpenGL library but is part of the GL_EXT_paletted_texture extension. To check whether your implementation of OpenGL supports glColorTableEXT, call glGetString (GL_EXTENSIONS). If it returns GL_EXT_paletted_texture, glColorTableEXT is supported. To obtain the function address of an extension function, call wglGetProcAddress.

To retrieve the actual color table data specified by the glColorTableEXT function, call glGetColorTableEXT. To retrieve the parameters, such as width and format, of the color table specified by the glColorTableEXT function, call the glGetColorTableParameterivEXT or glGetColorTableParameterfvEXT function.

Error Codes

The following are the errors generated and their conditions.

Error Code | Condition
GL_INVALID_VALUE | width was an invalid integer.
GL_INVALID_ENUM | target, internalFormat, format, or type was not an accepted value.
GL_INVALID_OPERATION | glColorTableEXT was called between glBegin and glEnd pairs.

See Also

glBegin, glColorSubTableEXT, glEnd, glGetColorTableEXT, glGetColorTableParameterfvEXT, glGetColorTableParameterivEXT, wglGetProcAddress


glColorSubTableEXT

The glColorSubTableEXT function specifies a portion of the targeted texture's palette to be replaced.

void glColorSubTableEXT(
  GLenum target,
  GLsizei start,
  GLsizei count,
  GLenum format,
  GLenum type,
  const GLvoid * data
);

Parameters

target
The target paletted texture that is to have its palette changed. Must be TEXTURE_1D or TEXTURE_2D.
start
The starting palette index entry of the palette to be changed.
count
The number of palette index entries of the palette to be changed beginning at start. The count parameter determines the range of palette index entries that are changed.
format
The format of the pixel data. The following symbolic constants are accepted:
GL_RGBA
Each pixel is a group of four components in the following order: red, green, blue, alpha. The RGBA format is determined in this way:

1.The glColorSubTableEXT function converts floating-point values directly to an internal format with unspecified precision. Signed integer values are mapped linearly to the internal format such that the most positive representable integer value maps to 1.0, and the most negative representable value maps to – 1.0. Unsigned integer data is mapped similarly: the largest integer value maps to 1.0, and zero maps to 0.0.

2.The glColorSubTableEXT function multiplies the resulting color values by GL_c_SCALE and adds them to GL_c_BIAS, where c is RED, GREEN, BLUE, and ALPHA for the respective color components. The results are clamped to the range [0,1].

3.If GL_MAP_COLOR is TRUE, glColorSubTableEXT scales each color component by the size of lookup table GL_PIXEL_MAP_c_TO_c, then replaces the component by the value that it references in that table; c is R, G, B, or A, respectively.

4.The glColorSubTableEXT function converts the resulting RGBA colors to fragments by attaching the current raster position z-coordinate and texture coordinates to each pixel, then assigning x and y window coordinates to the nth fragment such that

x (n) = x (r) + n mod width

y (n) = y (r) + ë n/width û

where (x (r) , y (r) ) is the current raster position.

5.These pixel fragments are then treated just like the fragments generated by rasterizing points, lines, or polygons. The glColorSubTableEXT function applies texture mapping, fog, and all the fragment operations before writing the fragments to the frame buffer.

GL_RED
Each pixel is a single red component.

The glColorSubTableEXT function converts this component to the internal format in the same way that the red component of an RGBA pixel is, then converts it to an RGBA pixel with green and blue set to 0.0, and alpha set to 1.0. After this conversion, the pixel is treated just as if it had been read as an RGBA pixel.

GL_GREEN
Each pixel is a single green component.

The glColorSubTableEXT function converts this component to the internal format in the same way that the green component of an RGBA pixel is, and then converts it to an RGBA pixel with red and blue set to 0.0, and alpha set to 1.0. After this conversion, the pixel is treated just as if it had been read as an RGBA pixel.

GL_BLUE
Each pixel is a single blue component.

The glColorSubTableEXT function converts this component to the internal format in the same way that the blue component of an RGBA pixel is, and then converts it to an RGBA pixel with red and green set to 0.0, and alpha set to 1.0. After this conversion, the pixel is treated just as if it had been read as an RGBA pixel.

GL_ALPHA
Each pixel is a single alpha component.

The glColorSubTableEXT function converts this component to the internal format in the same way that the alpha component of an RGBA pixel is, and then converts it to an RGBA pixel with red, green, and blue set to 0.0. After this conversion, the pixel is treated just as if it had been read as an RGBA pixel.

GL_RGB
Each pixel is a group of three components in this order: red, green, blue.

The glColorSubTableEXT function converts each component to the internal format in the same way that the red, green, and blue components of an RGBA pixel are. The color triple is converted to an RGBA pixel with alpha set to 1.0. After this conversion, the pixel is treated just as if it had been read as an RGBA pixel.

GL_BGR_EXT
Each pixel is a group of three components in this order: blue, green, red.

GL_BGR_EXT provides a format that matches the memory layout of Windows device-independent bitmaps (DIBs). Thus your applications can use the same data with Win32 function calls and OpenGL pixel function calls.

GL_BGRA_EXT
Each pixel is a group of four components in this order: blue, green, red, alpha.

GL_BGRA_EXT provides a format that matches the memory layout of Windows device-independent bitmaps (DIBs). Thus your applications can use the same data with Win32 function calls and OpenGL pixel function calls.

type
The data type for data. The following symbolic constants are accepted: GL_UNSIGNED_BYTE, GL_BYTE, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, and GL_FLOAT.

The following table summarizes the meaning of the valid constants for the type parameter.
Constant | Meaning
GL_UNSIGNED_BYTE | Unsigned 8-bit integer
GL_BYTE | Signed 8-bit integer
GL_UNSIGNED_SHORT | Unsigned 16-bit integer
GL_SHORT | Signed 16-bit integer
GL_UNSIGNED_INT | Unsigned 32-bit integer
GL_INT | 32-bit integer
GL_FLOAT | Single-precision floating-point value

data
A pointer to the paletted texture data. The data is treated as single pixels of a 1-D texture palette entry for a palette entry.

Remarks

The glColorSubTableEXT function specifies portions of the current targeted texture's palette to be replaced. Unlike glColorTableEXT, you cannot specify the target parameter to be a proxy texture palette.

Note The glColorSubTableEXT function is an extension function that is not part of the standard OpenGL library but is part of the GL_EXT_paletted_texture extension. To check whether your implementation of OpenGL supports glColorSubTableEXT, call glGetString (GL_EXTENSIONS). If it returns GL_EXT_paletted_texture, glColorSubTableEXT is supported. To obtain the function address of an extension function, call wglGetProcAddress.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_VALUE | start or count was an invalid integer.
GL_INVALID_ENUM | target, format, or type was not an accepted value.
GL_INVALID_OPERATION | glColorSubTableEXT was called between glBegin and glEnd pairs.

See Also

glBegin, glColorTableEXT, glEnd, glGetColorTableEXT, glGetColorTableParameterfvEXT, glGetColorTableParameterivEXT, glGetString, wglGetProcAddress


glCopyPixels

The glCopyPixels function copies pixels in the frame buffer.

void glCopyPixels(
  GLint x,
  GLint y,
  GLsizei width,
  GLsizei height,
  GLenum type
);

Parameters

x, y
The window coordinates of the lower-left corner of the rectangular region of pixels to be copied.
width, height
The dimensions of the rectangular region of pixels to be copied. Both must be nonnegative.
type
Specifies whether glCopyPixels is to copy color values, depth values, or stencil values. The acceptable symbolic constants are:
GL_COLOR
The glCopyPixels function reads indexes or RGBA colors from the buffer currently specified as the read source buffer (see glReadBuffer).

If OpenGL is in color-index mode:

1.Each index that is read from this buffer is converted to a fixed-point format with an unspecified number of bits to the right of the binary point.

2.Each index is shifted left by GL_INDEX_SHIFT bits, and added to GL_INDEX_OFFSET.

If GL_INDEX_SHIFT is negative, the shift is to the right. In either case, zero bits fill otherwise unspecified bit locations in the result.

3.If GL_MAP_COLOR is true, the index is replaced with the value that it references in lookup table GL_PIXEL_MAP_I_TO_I.

4.Whether the lookup replacement of the index is done or not, the integer part of the index is then ANDed with 2^b – 1, where b is the number of bits in a color-index buffer.

If OpenGL is in RGBA mode:

1.The red, green, blue, and alpha components of each pixel that is read are converted to an internal floating-point format with unspecified precision.

2.The conversion maps the largest representable component value to 1.0, and component value zero to 0.0.

3.The resulting floating-point color values are then multiplied by GL_c_SCALE and added to GL_c_BIAS, where c is RED, GREEN, BLUE, and ALPHA for the respective color components.

4.The results are clamped to the range [0,1].

5.If GL_MAP_COLOR is true, each color component is scaled by the size of lookup table GL_PIXEL_MAP_c_TO_c, and then replaced by the value that it references in that table; c is R, G, B, or A, respectively.

The resulting indexes or RGBA colors are then converted to fragments by attaching the current raster position z-coordinate and texture coordinates to each pixel, and then assigning window coordinates (x (r) + i, y (r) + j), where (x (r) y (r) ) is the current raster position, and the pixel was the pixel in the i position in the j row. These pixel fragments are then treated just like the fragments generated by rasterizing points, lines, or polygons. Texture mapping, fog, and all the fragment operations are applied before the fragments are written to the frame buffer.

GL_DEPTH
Depth values are read from the depth buffer and converted directly to an internal floating-point format with unspecified precision. The resulting floating-point depth value is then multiplied by GL_DEPTH_SCALE and added to GL_DEPTH_BIAS. The result is clamped to the range [0,1].

The resulting depth components are then converted to fragments by attaching the current raster position color or color index and texture coordinates to each pixel, then assigning window coordinates (x (r) + i, y (r) + j), where (x (r) , y (r) ) is the current raster position, and the pixel was the pixel in the i position in the j row. These pixel fragments are then treated just like the fragments generated by rasterizing points, lines, or polygons. Texture mapping, fog, and all the fragment operations are applied before the fragments are written to the frame buffer.

GL_STENCIL
Stencil indexes are read from the stencil buffer and converted to an internal fixed-point format with an unspecified number of bits to the right of the binary point. Each fixed-point index is then shifted left by GL_INDEX_SHIFT bits, and added to GL_INDEX_OFFSET. If GL_INDEX_SHIFT is negative, the shift is to the right. In either case, zero bits fill otherwise unspecified bit locations in the result. If GL_MAP_STENCIL is true, the index is replaced with the value that it references in lookup table GL_PIXEL_MAP_S_TO_S. Whether the lookup replacement of the index is done or not, the integer part of the index is then ANDed with 2^b – 1, where b is the number of bits in the stencil buffer. The resulting stencil indexes are then written to the stencil buffer such that the index read from the i location of the j row is written to location (x (r) + i, y (r) + j), where (x (r) , y (r) ) is the current raster position. Only the pixel-ownership test, the scissor test, and the stencil writemask affect these writes.

Remarks

The glCopyPixels function copies a screen-aligned rectangle of pixels from the specified frame buffer location to a region relative to the current raster position. Its operation is well defined only if the entire pixel source region is within the exposed portion of the window. Results of copies from outside the window, or from regions of the window that are not exposed, are hardware dependent and undefined.

The x and y parameters specify the window coordinates of the lower-left corner of the rectangular region to be copied. The width and height parameters specify the dimensions of the rectangular region to be copied. Both width and height must be nonnegative.

Several parameters control the processing of the pixel data while it is being copied. These parameters are set with three functions: glPixelTransfer, glPixelMap, and glPixelZoom. This topic describes the effects on glCopyPixels of most, but not all, of the parameters specified by these three functions.

The glCopyPixels function copies values from each pixel with the lower-left corner at (x + i, y + j) for 0≤i<width and 0≤j<height. This pixel is said to be the i pixel in the j row. Pixels are copied in row order from the lowest to the highest row, left to right in each row.

The type parameter specifies whether color, depth, or stencil data is to be copied.

The rasterization described thus far assumes pixel zoom factors of 1.0. If you use glPixelZoom to change the x and y pixel zoom factors, pixels are converted to fragments as follows. If (x (r) , y (r) ) is the current raster position, and a given pixel is in the i location in the j row of the source pixel rectangle, then fragments are generated for pixels whose centers are in the rectangle with corners at

(x (r) + zoom (x) i, y (r) + zoom (y) j)

and

(x (r) + zoom (x) (i + 1), y (r) + zoom (y) (j + 1 ))

where zoom (x) is the value of GL_ZOOM_X and zoom (y) is the value of GL_ZOOM_Y.

Modes specified by glPixelStore have no effect on the operation of glCopyPixels.

The following functions retrieve information related to glCopyPixels:

glGet with argument GL_CURRENT_RASTER_POSITION

glGet with argument GL_CURRENT_RASTER_POSITION_VALID

To copy the color pixel in the lower-left corner of the window to the current raster position, use

glCopyPixels(0, 0, 1, 1, GL_COLOR);

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | type was not an accepted value.
GL_INVALID_VALUE | Either width or height was negative.
GL_INVALID_OPERATION | type was GL_DEPTH and there was no depth buffer.
GL_INVALID_OPERATION | type was GL_STENCIL and there was no stencil buffer.
GL_INVALID_OPERATION | glCopyPixels was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glDepthFunc, glDrawBuffer, glDrawPixels, glEnd, glGet, glPixelMap, glPixelStore, glPixelTransfer, glPixelZoom, glRasterPos, glReadBuffer, glReadPixels, glStencilFunc


glCopyTexImage1D

The glCopyTexImage1D function copies pixels from the frame buffer into a one-dimensional texture image.

void glCopyTexImage1D(
  GLenum target,
  GLint level,
  GLenum internalFormat,
  GLint x,
  GLint y,
  GLsizei width,
  GLint border
);

Parameters

target
The target for which the image data will be changed. Must have the value GL_TEXTURE_1D.
level
The level-of-detail number. Level 0 is the base image. Level n is the nth mipmap reduction image.
internalFormat
The internal format and resolution of the texture data. The values 1, 2, 3, and 4 are not accepted for internalFormat. This parameter can assume one of the following symbolic values:
Constant Base Format R Bits G Bits B Bits A Bits L Bits I Bits
ALPHA — — — — — — —
ALPHA4 ALPHA — — — 4 — —
ALPHA8 ALPHA — — — 8 — —
ALPHA12 ALPHA — — — 12 — —
ALPHA16 ALPHA — — — 16 — —
LUMINANCE — — — — — — —
LUMINANCE4 LUMINANCE — — — — 4 —
LUMINANCE8 LUMINANCE — — — — 8 —
LUMINANCE12 LUMINANCE — — — — 12 —
LUMINANCE16 LUMINANCE — — — — 16 —
LUMINANCE_ALPHA — — — — — — —
LUMINANCE4_ALPHA4 LUMINANCE_ALPHA — — — 4 4 —
LUMINANCE6_ALPHA2 LUMINANCE_ALPHA — — — 2 6 —
LUMINANCE8_ALPHA8 LUMINANCE_ALPHA — — — 8 8 —
LUMINANCE12_ALPHA4 LUMINANCE_ALPHA — — — 4 12 —
LUMINANCE12_ALPHA12 LUMINANCE_ALPHA — — — 12 12 —
LUMINANCE16_ALPHA16 LUMINANCE_ALPHA — — — 16 16 —
INTENSITY — — — — — — —
INTENSITY4 INTENSITY — — — — — 4
INTENSITY8 INTENSITY — — — — — 8
INTENSITY12 INTENSITY — — — — — 12
INTENSITY16 INTENSITY — — — — — 16
GL_RGB — — — — — — —
GL_R3_G3_B2 GL_RGB 3 3 2 — — —
GL_RGB4 GL_RGB 4 4 4 — — —
GL_RGB5 GL_RGB 5 5 5 — — —
GL_RGB8 GL_RGB 8 8 8 — — —
GL_RGB10 GL_RGB 10 10 10 — — —
GL_RGB12 GL_RGB 12 12 12 — — —
GL_RGB16 GL_RGB 16 16 16 — — —
GL_RGBA — — — — — — —
GL_RGBA2 GL_RGBA 2 2 2 2 — —
GL_RGBA4 GL_RGBA 4 4 4 4 — —
GL_RGB5_A1 GL_RGBA 5 5 5 1 — —
GL_RGBA8 GL_RGBA 8 8 8 8 — —
GL_RGB10_A2 GL_RGBA 10 10 10 2 — —
GL_RGBA12 GL_RGBA 12 12 12 12 — —
GL_RGBA16 GL_RGBA 16 16 16 16 — —

x, y
The window coordinates of the lower-left corner of the row of pixels to be copied.
width
The width of the texture image. Must be 2^n + 2 * border for some integer n. The height of the texture image is 1.
border
The width of the border. Must be either zero or 1.

Remarks

The glCopyTexImage1D function defines a one-dimensional texture image using pixels from the current frame buffer, rather than from main memory as is the case for glTexImage1D.

Using the mipmap level specified with level, texture arrays are defined as a pixel row aligned with the lower-left corner of the window at the coordinates specified by x and y, with a length equal to width + 2 * border. The internal format of the texture array is specified with the internalFormat parameter.

The glCopyTexImage1D function processes the pixels in a row in the same way as glCopyPixels, except that before the final conversion of the pixels, all pixel component values are clamped to the range [0, 1] and converted to the texture's internal format for storage in the texture array. Pixel ordering is determined with lower x coordinates corresponding to lower texture coordinates. If any of the pixels within a specified row of the current frame buffer are outside the window associated with the current rendering context, then their values are undefined.

You cannot include calls to glCopyTexImage1D in display lists.

Note The glCopyTexImage1D function is only available in OpenGL version 1.1 or later.

Texturing has no effect in color-index mode. The glPixelStore and glPixelTransfer functions affect texture images in exactly the way they affect glDrawPixels.

The following function retrieves information related to glCopyTexImage1D:

glIsEnabled with argument GL_TEXTURE_1D

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | target was not an accepted value.
GL_INVALID_VALUE | level was less than zero or greater than log sub 2(max), where max is the returned value of GL_MAX_TEXTURE_SIZE.
GL_INVALID_VALUE | border was not zero or 1.
GL_INVALID_VALUE | width was less than zero, greater than 2 + GL_MAX_TEXTURE_SIZE; or width cannot be represented as 2^n + 2 * border for some integer n.
GL_INVALID_OPERATION | glCopyTexImage1D was called between a call to glBegin and the corresponding call to glEnd.

See Also

glCopyPixels, glCopyTexImage2D, glDrawPixels, glFog, glPixelStore, glPixelTransfer, glTexEnv, glTexGen, glTexImage1D, glTexImage2D, glTexParameter


glCopyTexImage2D

The glCopyTexImage2D function copies pixels from the frame buffer into a two-dimensional texture image.

void glCopyTexImage2D(
  GLenum target,
  GLint level,
  GLenum internalFormat,
  GLint x,
  GLint y,
  GLsizei width,
  GLsizei height,
  GLint border
);

Parameters

target
The target to which the image data will be changed. Must have the value GL_TEXTURE_2D.
level
The level-of-detail number. Level 0 is the base image. Level n is the nth mipmap reduction image.
internalFormat
The internal format and resolution of the texture data. The values 1, 2, 3, and 4 are not accepted for internalFormat. The parameter can assume one of the following symbolic values:
Constant Base Format R Bits G Bits B Bits A Bits L Bits I Bits
ALPHA — — — — — — —
ALPHA4 ALPHA — — — 4 — —
ALPHA8 ALPHA — — — 8 — —
ALPHA12 ALPHA — — — 12 — —
ALPHA16 ALPHA — — — 16 — —
LUMINANCE — — — — — — —
LUMINANCE4 LUMINANCE — — — — 4 —
LUMINANCE8 LUMINANCE — — — — 8 —
LUMINANCE12 LUMINANCE — — — — 12 —
LUMINANCE16 LUMINANCE — — — — 16 —
LUMINANCE_ALPHA — — — — — — —
LUMINANCE4_ALPHA4 LUMINANCE_ALPHA — — — 4 4 —
LUMINANCE6_ALPHA2 LUMINANCE_ALPHA — — — 2 6 —
LUMINANCE8_ALPHA8 LUMINANCE_ALPHA — — — 8 8 —
LUMINANCE12_ALPHA4 LUMINANCE_ALPHA — — — 4 12 —
LUMINANCE12_ALPHA12 LUMINANCE_ALPHA — — — 12 12 —
LUMINANCE16_ALPHA16 LUMINANCE_ALPHA — — — 16 16 —
INTENSITY — — — — — — —
INTENSITY4 INTENSITY — — — — — 4
INTENSITY8 INTENSITY — — — — — 8
INTENSITY12 INTENSITY — — — — — 12
INTENSITY16 INTENSITY — — — — — 16
GL_RGB — — — — — — —
GL_R3_G3_B2 GL_RGB 3 3 2 — — —
GL_RGB4 GL_RGB 4 4 4 — — —
GL_RGB5 GL_RGB 5 5 5 — — —
GL_RGB8 GL_RGB 8 8 8 — — —
GL_RGB10 GL_RGB 10 10 10 — — —
GL_RGB12 GL_RGB 12 12 12 — — —
GL_RGB16 GL_RGB 16 16 16 — — —
GL_RGBA — — — — — — —
GL_RGBA2 GL_RGBA 2 2 2 2 — —
GL_RGBA4 GL_RGBA 4 4 4 4 — —
GL_RGB5_A1 GL_RGBA 5 5 5 1 — —
GL_RGBA8 GL_RGBA 8 8 8 8 — —
GL_RGB10_A2 GL_RGBA 10 10 10 2 — —
GL_RGBA12 GL_RGBA 12 12 12 12 — —
GL_RGBA16 GL_RGBA 16 16 16 16 — —

x, y
The window coordinates of the lower-left corner of the rectangular region of pixels to be copied.
width
The width of the texture image. Must be 2^n + 2 * border for some integer n.
height
The height of the texture image. Must be 2^n + 2 * border for some integer n.
border
The width of the border. Must be either zero or 1.

Remarks

The glCopyTexImage2D function defines a two-dimensional texture image using pixels from the current frame buffer, rather than from main memory as is the case for glTexImage2D.

Using the mipmap level specified with level, texture arrays are defined as a rectangle of pixels with the lower-left corner located at the coordinates x and y, width equal to width + (2 * border), and a height equal to height + (2 * border). The internal format of the texture array is specified with the internalFormat parameter.

The glCopyTexImage2D function processes the pixels in a row in the same way as glCopyPixels except that before the final conversion of the pixels, all pixel component values are clamped to the range [0, 1] and converted to the texture's internal format for storage in the texture array. Pixel ordering is determined with lower x and y coordinates corresponding to lower s and t texture coordinates. If any of the pixels within a specified row of the current frame buffer are outside the window associated with the current rendering context, then their values are undefined.

You cannot include calls to glCopyTexImage2D in display lists.

Note The glCopyTexImage2D function is only available in OpenGL version 1.1 or later.

Texturing has no effect in color-index mode. The glPixelStore and glPixelTransfer functions affect texture images in exactly the way they affect glDrawPixels.

The following function retrieves information related to glCopyTexImage2D:

glIsEnabled with argument GL_TEXTURE_2D

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | target was not an accepted value.
GL_INVALID_VALUE | level was less than zero or greater than log sub 2(max), where max is the returned value of GL_MAX_TEXTURE_SIZE.
GL_INVALID_VALUE | border was not zero or 1.
GL_INVALID_VALUE | width was less than zero, greater than 2 + GL_MAX_TEXTURE_SIZE; or width cannot be represented as 2^n + 2 * border for some integer n.
GL_INVALID_OPERATION | glCopyTexImage2D was called between a call to glBegin and the corresponding call to glEnd.

See Also

glBegin, glCopyTexImage1D, glDrawPixels, glEnd, glFog, glPixelStore, glPixelTransfer, glTexEnv, glTexGen, glTexImage1D, glTexImage2D, glTexParameter


glCopyTexSubImage1D

The glCopyTexSubImage1D function copies a sub-image of a one-dimensional texture image from the frame buffer.

void glCopyTexSubImage1D(
  GLenum target,
  GLint level,
  GLint xoffset,
  GLint x,
  GLint y,
  GLsizei width
);

Parameters

target
The target to which the image data will be changed. Must have the value GL_TEXTURE_1D.
level
The level-of-detail number. Level 0 is the base image. Level n is the nth mipmap reduction image.
xoffset
The texel offset within the texture array.
x, y
The window coordinates of the lower-left corner of the row of pixels to be copied.
width
The width of the sub-image of the texture image. Specifying a texture sub-image with zero width has no effect.

Remarks

The glCopyTexSubImage1D function replaces a portion of a one-dimensional texture image using pixels from the current frame buffer, rather than from main memory as is the case for glTexSubImage1D.

A row of pixels beginning with the window coordinates specified by x and y and with the length width replaces the portion of the texture array with the indexes xoffset through xoffset + (width – 1). The destination in the texture array cannot include any texels outside the originally specified texture array.

The glCopyTexSubImage1D function processes the pixels in a row in the same way as glCopyPixels except that before the final conversion of the pixels, all pixel component values are clamped to the range [0, 1] and converted to the texture's internal format for storage in the texture array. Pixel ordering is determined with lower x coordinates corresponding to lower texture coordinates. If any of the pixels within a specified row of the current frame buffer are outside the window associated with the current rendering context, then their values are undefined.

No change is made to the internalFormat, width, or border parameter of the specified texture array or to texel values outside the specified texture sub-image.

You cannot include calls to glCopyTexSubImage1D in display lists.

Note The glCopyTexSubImage1D function is only available in OpenGL version 1.1 or later.

Texturing has no effect in color-index mode. The glPixelStore and glPixelTransfer functions affect texture images in exactly the way they affect the way pixels are drawn using glDrawPixels.

The following functions retrieve information related to glCopyTexSubImage1D:

glGetTexImage

glIsEnabled with argument GL_TEXTURE_1D

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | target was not an accepted value.
GL_INVALID_VALUE | level was less than zero or greater than log sub 2(max), where max is the returned value of GL_MAX_TEXTURE_SIZE.
GL_INVALID_VALUE | xoffset was less than border or (xoffset + width) was greater than (w + border), where w is GL_TEXTURE_WIDTH and border is GL_TEXTURE_BORDER. Note that w includes twice the border width.
GL_INVALID_VALUE | width was less than border or y was less than border, where border is the border width of the texture array.
GL_INVALID_OPERATION | The texture array was not defined by a previous glTexImage1D operation.
GL_INVALID_OPERATION | glCopyTexSubImage1D was called between a call to glBegin and the corresponding call to glEnd.

See Also

glBegin, glCopyTexSubImage2D, glDrawPixels, glEnd, glFog, glPixelStore, glPixelTransfer, glTexEnv, glTexGen, glTexImage1D, glTexImage2D, glTexSubImage1D, glTexSubImage2D, glTexParameter


glCopyTexSubImage2D

The glCopyTexSubImage2D function copies a sub-image of a two-dimensional texture image from the frame buffer.

void glCopyTexSubImage2D(
  GLenum target,
  GLint level,
  GLint xoffset,
  GLint yoffset,
  GLint x,
  GLint y,
  GLsizei width,
  GLsizei height
);

Parameters

target
The target to which the image data will be changed and can only have the value GL_TEXTURE_2D.
level
The level-of-detail number. Level 0 is the base image. Level n is the nth mipmap reduction image.
xoffset
The texel offset in the x direction within the texture array.
yoffset
The texel offset in the y direction within the texture array.
x, y
The window coordinates of the lower-left corner of the row of pixels to be copied.
width
The width of the sub-image of the texture image. Specifying a texture sub-image with zero width has no effect.
height
The height of the sub-image of the texture image. Specifying a texture sub-image with zero width has no effect.

Remarks

The glCopyTexSubImage2D function replaces a rectangular portion of a two-dimensional texture image with pixels from the current frame buffer, rather than from main memory as is the case for glTexSubImage2D.

A rectangle of pixels beginning with the x and y window coordinates and with the dimensions width and height replaces the portion of the texture array with the indexes xoffset through xoffset + (width – 1), with the indexes yoffset through yoffset + (width – 1) at the mipmap level specified by level. The destination rectangle in the texture array cannot include any texels outside the originally specified texture array.

The glCopyTexSubImage2D function processes the pixels in a row in the same way as glCopyPixels except that before the final conversion of the pixels, all pixel component values are clamped to the range [0, 1] and converted to the texture's internal format for storage in the texture array. Pixel ordering is determined with lower x coordinates corresponding to lower texture coordinates. If any of the pixels within a specified row of the current frame buffer are outside the window associated with the current rendering context, then their values are undefined.

If any of the pixels within the specified rectangle of the current frame buffer are outside the read window associated with the current rendering context, then the values obtained for those pixels are undefined. No change is made to the internalFormat, width, height, or border parameter of the specified texture array or to texel values outside the specified texture sub-image.

You cannot include calls to glCopyTexSubImage2D in display lists.

Note The glCopyTexSubImage2D function is only available in OpenGL version 1.1 or later.

Texturing has no effect in color-index mode. The glPixelStore and glPixelTransfer functions affect texture images in exactly the way they affect the way pixels are drawn using glDrawPixels.

The following functions retrieve information related to glCopyTexSubImage2D:

glGetTexImage

glIsEnabled with argument GL_TEXTURE_2D.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | target was not an accepted value.
GL_INVALID_VALUE | level was less than zero or greater than log sub 2(max), where max is the returned value of GL_MAX_TEXTURE_SIZE.
GL_INVALID_VALUE | xoffset was less than border, (xoffset + width) was greater than (w + border), yoffset was less than border, or (yoffset + height) was greater than (h + border), where w is GL_TEXTURE_WIDTH, h is GL_TEXTURE_HEIGHT, and border is GL_TEXTURE_BORDER. Note that w includes twice the border width.
GL_INVALID_VALUE | width was less than border or y was less than border, where border is the border width of the texture array.
GL_INVALID_OPERATION | The texture array was not defined by a previous glTexImage2D operation.
GL_INVALID_OPERATION | glCopyTexSubImage2D was called between a call to glBegin and the corresponding call to glEnd.

See Also

glBegin, glCopyPixels, glCopyTexSubImage1D, glDrawPixels, glEnd, glFog, glPixelStore, glPixelTransfer, glTexEnv, glTexGen, glTexImage2D, glTexSubImage2D, glTexParameter


glCullFace

The glCullFace function specifies whether front- or back-facing facets can be culled.

void glCullFace(
  GLenum mode
);

Parameters

mode
Specifies whether front- or back-facing facets are candidates for culling. Symbolic constants GL_FRONT and GL_BACK are accepted. The default value is GL_BACK.

Remarks

The glCullFace function specifies whether front- or back-facing facets are culled (as specified by mode) when facet culling is enabled. You enable and disable facet culling using glEnable and glDisable with the argument GL_CULL_FACE. Facets include triangles, quadrilaterals, polygons, and rectangles.

The glFrontFace function specifies which of the clockwise and counterclockwise facets are front-facing and back-facing.

The following functions retrieve information related to glCullFace:

glGet with argument GL_CULL_FACE_MODE

glIsEnabled with argument GL_CULL_FACE

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | mode was not an accepted value.
GL_INVALID_OPERATION | glCullFace was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glDisable, glEnable, glEnd, glFrontFace, glGet, glIsEnabled


glDeleteLists

The glDeleteLists function deletes a contiguous group of display lists.

void glDeleteLists(
  GLuint list,
  GLsizei range
);

Parameters

list
The integer name of the first display list to delete.
range
The number of display lists to delete.

Remarks

The glDeleteLists function causes a contiguous group of display lists to be deleted. The list parameter is the name of the first display list to be deleted, and range is the number of display lists to delete. All display lists d with list ≤ d ≤ list + range – 1 are deleted.

All storage locations allocated to the specified display lists are freed, and the names are available for reuse at a later time. Names within the range that do not have an associated display list are ignored. If range is zero, nothing happens.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_VALUE | range was negative.
GL_INVALID_OPERATION | glDeleteLists was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glCallList, glCallLists, glEnd, glGenLists, glIsList, glNewList


glDeleteTextures

The glDeleteTextures function deletes named textures.

void glDeleteTextures(
  GLsizei n,
  const GLuint * textures
);

Parameters

n
The number of textures to be deleted.
textures
An array of textures to be deleted.

Remarks

The glDeleteTextures function deletes n textures named by the elements of the array textures. After a texture is deleted, it has no contents or dimensionality, and its name is free for reuse (for example, by glGenTextures). The glDeleteTextures function ignores zeros and names that do not correspond to existing textures.

If a texture that is currently bound is deleted, the binding reverts to zero (the default texture).

You cannot include calls to glDeleteTextures in display lists.

Note The glDeleteTextures function is only available in OpenGL version 1.1 or later.

The following function retrieves information related to glDeleteTextures:

glIsTexture

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_VALUE | n was a negative value.
GL_INVALID_OPERATION | glDeleteTextures was called between a call to glBegin and the corresponding call to glEnd.

See Also

glAreTexturesResident, glBegin, glBindTexture, glEnd, glGenTextures, glGet, glGetTexParameter, glIsTexture, glPrioritizeTextures, glTexGen, glTexImage1D, glTexImage2D, glTexParameter


glDepthFunc

The glDepthFunc function specifies the value used for depth-buffer comparisons.

void glDepthFunc(
  GLenum func
);

Parameters

func
The depth-comparison function. The following symbolic constants are accepted.
Symbolic Constant | Meaning
GL_NEVER | Never passes.
GL_LESS | Passes if the incoming z value is less than the stored z value. This is the default value.
GL_EQUAL | Passes if the incoming z value is equal to the stored z value.
GL_LEQUAL | Passes if the incoming z value is less than or equal to the stored z value.
GL_GREATER | Passes if the incoming z value is greater than the stored z value.
GL_NOTEQUAL | Passes if the incoming z value is not equal to the stored z value.
GL_GEQUAL | Passes if the incoming z value is greater than or equal to the stored z value.
GL_ALWAYS | Always passes.

Remarks

The glDepthFunc function specifies the function used to compare each incoming pixel z value with the z value present in the depth buffer. The comparison is performed only if depth testing is enabled. (See glEnable with the argument GL_DEPTH_TEST.)

Initially, depth testing is disabled.

The following functions retrieve information related to glDepthFunc:

glGet with argument GL_DEPTH_FUNC

glIsEnabled with argument GL_DEPTH_TEST

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | func was not an accepted value.
GL_INVALID_OPERATION | glDepthFunc was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glDepthRange, glEnable, glEnd, glGet, glIsEnabled


glDepthMask

The glDepthMask function enables or disables writing into the depth buffer.

void glDepthMask(
  GLboolean flag
);

Parameters

flag
Specifies whether the depth buffer is enabled for writing. If flag is zero, depth-buffer writing is disabled. Otherwise, it is enabled. Initially, depth-buffer writing is enabled.

Remarks

The following function retrieves information related to glDepthMask:

glGet with argument GL_DEPTH_WRITEMASK

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glDepthMask was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glColorMask, glDepthFunc, glDepthRange, glEnd, glGet, glIndexMask, glStencilMask


glDepthRange

The glDepthRange function specifies the mapping of z values from normalized device coordinates to window coordinates.

void glDepthRange(
  GLclampd znear,
  GLclampd zfar
);

Parameters

znear
The mapping of the near clipping plane to window coordinates. The default value is 0.
zfar
The mapping of the far clipping plane to window coordinates. The default value is 1.

Remarks

After clipping and division by w, z-coordinates range from –1.0 to 1.0, corresponding to the near and far clipping planes. The glDepthRange function specifies a linear mapping of the normalized z-coordinates in this range to window z-coordinates. Regardless of the actual depth buffer implementation, window coordinate depth values are treated as though they range from 0.0 through 1.0 (like color components). Thus, the values accepted by glDepthRange are both clamped to this range before they are accepted.

The default mapping of 0,1 maps the near plane to 0 and the far plane to 1. With this mapping, the depth-buffer range is fully utilized.

It is not necessary that znear be less than zfar. Reverse mappings such as 1,0 are acceptable.

The following function retrieves information related to glDepthRange:

glGet with argument GL_DEPTH_RANGE

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glDepthRange was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glDepthFunc, glEnd, glGet, glViewport


glDrawArrays

The glDrawArrays function specifies multiple primitives to render.

void glDrawArrays(
  GLenum mode,
  GLint first,
  GLsizei count
);

Parameters

mode
The kind of primitives to render. The following constants specify acceptable types of primitives: GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES, GL_QUAD_STRIP, GL_QUADS, and GL_POLYGON.
first
The starting index in the enabled arrays.
count
The number of indexes to render.

Remarks

With glDrawArrays, you can specify multiple geometric primitives to render. Instead of calling separate OpenGL functions to pass each individual vertex, normal, or color, you can specify separate arrays of vertices, normals, and colors to define a sequence of primitives (all the same kind) with a single call to glDrawArrays.

When you call glDrawArrays, count sequential elements from each enabled array are used to construct a sequence of geometric primitives, beginning with the first element. The mode parameter specifies what kind of primitive to construct and how to use the array elements to construct the primitives.

After glDrawArrays returns, the values of vertex attributes that are modified by glDrawArrays are undefined. For example, if GL_COLOR_ARRAY is enabled, the value of the current color is undefined after glDrawArrays returns. Attributes not modified by glDrawArrays remain defined. When GL_VERTEX_ARRAY is not enabled, no geometric primitives are generated but the attributes corresponding to enabled arrays are modified.

You can include glDrawArrays in display lists. When you include glDrawArrays in a display list, the necessary array data, determined by the array pointers and the enables, are generated and entered in the display list. The values of array pointers and enables are determined during the creation of display lists.

You can read static array data at any time. If any static array elements are modified and the array is not specified again, the results of any subsequent calls to glDrawArrays are undefined.

Although no error is generated when you specify an array more than once within glBegin and glEnd pairs, the results are undefined.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_VALUE | count was negative.
GL_INVALID_ENUM | mode was not an accepted value.
GL_INVALID_OPERATION | glDrawArrays was called between a call to glBegin and the corresponding call to glEnd.

See Also

glArrayElement, glBegin, glColorPointer, glEdgeFlagPointer, glEnd, glGetPointerv, glGetString, glIndexPointer, glNormalPointer, glTexCoordPointer, glVertexPointer


glDrawBuffer

The glDrawBuffer function specifies which color buffers are to be drawn into.

void glDrawBuffer(
  GLenum mode
);

Parameters

mode
Specifies up to four color buffers to be drawn into with the following acceptable symbolic constants:
GL_NONE
No color buffers are written.
GL_FRONT_LEFT
Only the front-left color buffer is written.
GL_FRONT_RIGHT
Only the front-right color buffer is written.
GL_BACK_LEFT
Only the back-left color buffer is written.
GL_BACK_RIGHT
Only the back-right color buffer is written.
GL_FRONT
Only the front-left and front-right color buffers are written. If there is no front-right color buffer, only the front left-color buffer is written.
GL_BACK
Only the back-left and back-right color buffers are written. If there is no back-right color buffer, only the back-left color buffer is written.
GL_LEFT
Only the front-left and back-left color buffers are written. If there is no back-left color buffer, only the front-left color buffer is written.
GL_RIGHT
Only the front-right and back-right color buffers are written. If there is no back-right color buffer, only the front-right color buffer is written.
GL_FRONT_AND_BACK
All the front and back color buffers (front-left, front-right, back-left, back-right) are written. If there are no back color buffers, only the front-left and front-right color buffers are written. If there are no right color buffers, only the front-left and back-left color buffers are written. If there are no right or back color buffers, only the front-left color buffer is written.
GL_AUXi
Only the auxiliary color buffer i is written; i is between 0 and GL_AUX_BUFFERS –1. (GL_AUX_BUFFERS is not the upper limit; use glGet to query the number of available aux buffers.)

The default value is GL_FRONT for single-buffered contexts, and GL_BACK for double-buffered contexts.

Remarks

When colors are written to the frame buffer, they are written into the color buffers specified by glDrawBuffer.

If more than one color buffer is selected for drawing, then blending or logical operations are computed and applied independently for each color buffer and can produce different results in each buffer.

Monoscopic contexts include only left buffers, and stereoscopic contexts include both left and right buffers. Likewise, single-buffered contexts include only front buffers, and double-buffered contexts include both front and back buffers. The context is selected at OpenGL initialization.

It is always the case that GL_AUXi = GL_AUX0 + i.

The following functions retrieve information related to the glDrawBuffer function:

glGet with argument GL_DRAW_BUFFER

glGet with argument GL_AUX_BUFFERS

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | mode was not an accepted value.
GL_INVALID_OPERATION | None of the buffers indicated by mode existed.
GL_INVALID_OPERATION | glDrawBuffer was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glBlendFunc, glColorMask, glEnd, glGet, glIndexMask, glLogicOp, glReadBuffer


glDrawElements

The glDrawElements function renders primitives from array data.

void glDrawElements(
  GLenum mode,
  GLsizei count,
  GLenum type,
  const GLvoid *indices
);

Parameters

mode
The kind of primitives to render. It can assume one of the following symbolic values: GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES, GL_QUAD_STRIP, GL_QUADS, and GL_POLYGON.
count
The number of elements to be rendered.
type
The type of the values in indices. Must be one of GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, or GL_UNSIGNED_INT.
indices
A pointer to the location where the indices are stored.

Remarks

The glDrawElements function enables you to specify multiple geometric primitives with very few function calls. Instead of calling an OpenGL function to pass each individual vertex, normal, or color, you can specify separate arrays of vertexes, normals, and colors beforehand and use them to define a sequence of primitives (all of the same type) with a single call to glDrawElements.

When you call the glDrawElements function, it uses count sequential elements from indices to construct a sequence of geometric primitives. The mode parameter specifies what kind of primitives are constructed, and how the array elements are used to construct these primitives. If GL_VERTEX_ARRAY is not enabled, no geometric primitives are generated.

Vertex attributes that are modified by glDrawElements have an unspecified value after glDrawElements returns. For example, if GL_COLOR_ARRAY is enabled, the value of the current color is undefined after glDrawElements executes. Attributes that aren't modified remain unchanged.

You can include the glDrawElements function in display lists. When glDrawElements is included in a display list, the necessary array data (determined by the array pointers and enables) is also entered into the display list. Because the array pointers and enables are client-side state variables, their values affect display lists when the lists are created, not when the lists are executed.

Note The glDrawElements function is only available in OpenGL version 1.1 or later.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | mode was not an accepted value.
GL_INVALID_VALUE | count was a negative value.
GL_INVALID_OPERATION | glDrawElements was called between a call to glBegin and the corresponding call to glEnd.

See Also

glArrayElement, glBegin, glColorPointer, glDrawArrays, glEdgeFlagPointer, glEnd, glGetPointerv, glIndexPointer, glNormalPointer, glTexCoordPointer, glVertexPointer


glDrawPixels

The glDrawPixels function writes a block of pixels to the frame buffer.

void glDrawPixels(
  GLsizei width,
  GLsizei height,
  GLenum format,
  GLenum type,
  const GLvoid *pixels
);

Parameters

width, height
The dimensions of the pixel rectangle that will be written into the frame buffer.
format
The format of the pixel data. Acceptable symbolic constants are:
GL_COLOR_INDEX
Each pixel is a single value, a color index.

1.The glDrawPixels function converts each pixel to fixed-point format, with an unspecified number of bits to the right of the binary point, regardless of the memory data type. Floating-point values convert to true fixed-point values. The glDrawPixels function converts signed and unsigned integer data with all fraction bits set to zero. The function converts bitmap data to either 0.0 or 1.0.

2.The glDrawPixels function shifts each fixed-point index left by GL_INDEX_SHIFT bits and adds it to GL_INDEX_OFFSET. If GL_INDEX_SHIFT is negative, the shift is to the right. In either case, zero bits fill otherwise unspecified bit locations in the result.

3.When in RGBA mode, glDrawPixels converts the resulting index to an RGBA pixel using the GL_PIXEL_MAP_I_TO_R, GL_PIXEL_MAP_I_TO_G, GL_PIXEL_MAP_I_TO_B, and GL_PIXEL_MAP_I_TO_A tables. When in the color-index mode and GL_MAP_COLOR is true, the index is replaced with the value that glDrawPixels references in lookup table GL_PIXEL_MAP_I_TO_I.

4.Whether the lookup replacement of the index is done or not, the integer part of the index is ANDed with 2b – 1, where b is the number of bits in a color-index buffer.

5.The resulting indexes or RGBA colors are then converted to fragments by attaching the current raster position z-coordinate and texture coordinates to each pixel, and then assigning x and y window coordinates to the nth fragment such that

x (n) = x (r) + n mod width

y (n) = y (r) + ë n/width û

where (x (r) , y (r) ) is the current raster position.

6.The glDrawPixels function treats these pixel fragments just like the fragments generated by rasterizing points, lines, or polygons. It applies texture mapping, fog, and all the fragment operations before writing the fragments to the frame buffer.

GL_STENCIL_INDEX
Each pixel is a single value, a stencil index.

1.The glDrawPixels function converts it to fixed-point format, with an unspecified number of bits to the right of the binary point, regardless of the memory data type. Floating-point values convert to true fixed-point values. The glDrawPixels function converts signed and unsigned integer data with all fraction bits set to zero. Bitmap data converts to either 0.0 or 1.0.

2.The glDrawPixels function shifts each fixed-point index left by GL_INDEX_SHIFT bits, and adds it to GL_INDEX_OFFSET. If GL_INDEX_SHIFT is negative, the shift is to the right. In either case, zero bits fill otherwise unspecified bit locations in the result.

3.If GL_MAP_STENCIL is true, the index is replaced with the value that glDrawPixels references in lookup table GL_PIXEL_MAP_S_TO_S.

4.Whether the lookup replacement of the index is done or not, the integer part of the index is then ANDed with 2b – 1, where b is the number of bits in the stencil buffer. The resulting stencil indexes are then written to the stencil buffer such that the nth index is written to location

x (n) = x (r) + n mod width

y (n) = y (r) + ë n /width û

where (x (r) , y (r) ) is the current raster position. Only the pixel ownership test, the scissor test, and the stencil writemask affect these writes.

GL_DEPTH_COMPONENT
Each pixel is a single-depth component.

1.The glDrawPixels function converts floating-point data directly to an internal floating-point format with unspecified precision. Signed integer data is mapped linearly to the internal floating-point format such that the most positive representable integer value maps to 1.0, and the most negative representable value maps to –1.0. Unsigned integer data is mapped similarly: the largest integer value maps to 1.0, and zero maps to 0.0.

2.The glDrawPixels function multiplies the resulting floating-point depth value by GL_DEPTH_SCALE and adds it to GL_DEPTH_BIAS. The result is clamped to the range [0,1].

3.The glDrawPixels function converts the resulting depth components to fragments by attaching the current raster position color or color index and texture coordinates to each pixel, and then assigning x and y window coordinates to the nth fragment such that

x (n) = x (r) + n mod width

y (n) = y (r) + ë n /width û

where (x (r) , y (r) ) is the current raster position.

4.These pixel fragments are then treated just like the fragments generated by rasterizing points, lines, or polygons. The glDrawPixels function applies texture mapping, fog, and all the fragment operations before writing the fragments to the frame buffer.

GL_RGBA
Each pixel is a four-component group in this order: red, green, blue, alpha.

1.The glDrawPixels function converts floating-point values directly to an internal floating-point format with unspecified precision. Signed integer values are mapped linearly to the internal floating-point format such that the most positive representable integer value maps to 1.0, and the most negative representable value maps to – 1.0. Unsigned integer data is mapped similarly: the largest integer value maps to 1.0, and zero maps to 0.0.

2.The glDrawPixels function multiplies the resulting floating-point color values by GL_c_SCALE and adds them to GL_c_BIAS, where c is RED, GREEN, BLUE, and ALPHA for the respective color components. The results are clamped to the range [0,1].

3.If GL_MAP_COLOR is true, glDrawPixels scales each color component by the size of lookup table GL_PIXEL_MAP_c_TO_c, and then replaces the component by the value that it references in that table; c is R, G, B, or A, respectively.

4.The glDrawPixels function converts the resulting RGBA colors to fragments by attaching the current raster position z-coordinate and texture coordinates to each pixel, then assigning x and y window coordinates to the nth fragment such that

x (n) = x (r) + n mod width

y (n) = y (r) + ë n /width û

where (x (r) , y (r) ) is the current raster position.

5.These pixel fragments are then treated just like the fragments generated by rasterizing points, lines, or polygons. The glDrawPixels function applies texture mapping, fog, and all the fragment operations before writing the fragments to the frame buffer.

GL_RED
Each pixel is a single red component.

The glDrawPixels function converts this component to the internal floating-point format in the same way that the red component of an RGBA pixel is, and then converts it to an RGBA pixel with green and blue set to 0.0, and alpha set to 1.0. After this conversion, the pixel is treated just as if it had been read as an RGBA pixel.

GL_GREEN
Each pixel is a single green component.

The glDrawPixels function converts this component to the internal floating-point format in the same way that the green component of an RGBA pixel is, and then converts it to an RGBA pixel with red and blue set to 0.0, and alpha set to 1.0. After this conversion, the pixel is treated just as if it had been read as an RGBA pixel.

GL_BLUE
Each pixel is a single blue component.

The glDrawPixels function converts this component to the internal floating-point format in the same way that the blue component of an RGBA pixel is, and then converts it to an RGBA pixel with red and green set to 0.0, and alpha set to 1.0. After this conversion, the pixel is treated just as if it had been read as an RGBA pixel.

GL_ALPHA
Each pixel is a single alpha component.

The glDrawPixels function converts this component to the internal floating-point format in the same way that the alpha component of an RGBA pixel is, and then converts it to an RGBA pixel with red, green, and blue set to 0.0. After this conversion, the pixel is treated just as if it had been read as an RGBA pixel.

GL_RGB
Each pixel is a group of three components in this order: red, green, blue.

The glDrawPixels function converts each component to the internal floating-point format in the same way that the red, green, and blue components of an RGBA pixel are. The color triple is converted to an RGBA pixel with alpha set to 1.0. After this conversion, the pixel is treated just as if it had been read as an RGBA pixel.

GL_LUMINANCE
Each pixel is a single luminance component.

The glDrawPixels function converts this component to the internal floating-point format in the same way that the red component of an RGBA pixel is, and then converts it to an RGBA pixel with red, green, and blue set to the converted luminance value, and alpha set to 1.0. After this conversion, the pixel is treated just as if it had been read as an RGBA pixel.

GL_LUMINANCE_ALPHA
Each pixel is a group of two components in this order: luminance, alpha.

The glDrawPixels function converts the two components to the internal floating-point format in the same way that the red component of an RGBA pixel is, and then converts them to an RGBA pixel with red, green, and blue set to the converted luminance value, and alpha set to the converted alpha value. After this conversion, the pixel is treated just as if it had been read as an RGBA pixel.

GL_BGR_EXT
Each pixel is a group of three components in this order: blue, green, red.

GL_BGR_EXT provides a format that matches the memory layout of Windows device-independent bitmaps (DIBs). Thus your applications can use the same data with Win32 function calls and OpenGL pixel function calls.

GL_BGRA_EXT
Each pixel is a group of four components in this order: blue, green, red, alpha.

GL_BGRA_EXT provides a format that matches the memory layout of Windows device-independent bitmaps (DIBs). Thus your applications can use the same data with Win32 function calls and OpenGL pixel function calls.

type
The data type for pixels. The following symbolic constants are accepted: GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, and GL_FLOAT.

The following table summarizes the meaning of the valid constants for the type parameter.
Type | Meaning
GL_UNSIGNED_BYTE | Unsigned 8-bit integer
GL_BYTE | Signed 8-bit integer
GL_BITMAP | Single bits in unsigned 8-bit integers
GL_UNSIGNED_SHORT | Unsigned 16-bit integer
GL_SHORT | Signed 16-bit integer
GL_UNSIGNED_INT | Unsigned 32-bit integer
GL_INT | 32-bit integer
GL_FLOAT | Single-precision floating-point

pixels
A pointer to the pixel data.

Remarks

The glDrawPixels function reads pixel data from memory and writes it into the frame buffer relative to the current raster position. Use glRasterPos to set the current raster position, and use glGet with argument GL_CURRENT_RASTER_POSITION to query the raster position.

Several parameters define the encoding of pixel data in memory and control the processing of the pixel data before it is placed in the frame buffer. These parameters are set with four functions: glPixelStore, glPixelTransfer, glPixelMap, and glPixelZoom. This topic describes the effects on glDrawPixels of many, but not all, of the parameters specified by these four functions.

Data is read from pixels as a sequence of signed or unsigned bytes, signed or unsigned shorts, signed or unsigned integers, or single-precision floating-point values, depending on type. Each of these bytes, shorts, integers, or floating-point values is interpreted as one color or depth component, or one index, depending on format. Indexes are always treated individually. Color components are treated as groups of one, two, three, or four values, again based on format. Both individual indexes and groups of components are referred to as pixels. If type is GL_BITMAP, the data must be unsigned bytes, and format must be either GL_COLOR_INDEX or GL_STENCIL_INDEX. Each unsigned byte is treated as eight 1-bit pixels, with bit ordering determined by GL_UNPACK_LSB_FIRST (see glPixelStore).

The width by height pixels are read from memory, starting at location pixels. By default, these pixels are taken from adjacent memory locations, except that after all width pixels are read, the read pointer is advanced to the next 4-byte boundary. The glPixelStore function specifies the 4-byte row alignment with argument GL_UNPACK_ALIGNMENT, and you can set it to 1, 2, 4, or 8 bytes. Other pixel store parameters specify different read pointer advancements, both before the first pixel is read, and after all width pixels are read.

The glPixelStore function operates on each of the width-by-height pixels that it reads from memory in the same way, based on the values of several parameters specified by glPixelTransfer and glPixelMap. The details of these operations, as well as the target buffer into which the pixels are drawn, are specific to the format of the pixels, as specified by format.

The rasterization described thus far assumes pixel zoom factors of 1.0. If you use glPixelZoom to change the x and y pixel zoom factors, pixels are converted to fragments as follows. If (x (r) , y (r) ) is the current raster position, and a given pixel is in the n column and m row of the pixel rectangle, then fragments are generated for pixels whose centers are in the rectangle with corners at

(x (r) + zoom (x) n, y (r) + zoom (y) m)

(x (r) + zoom (x) (n + 1), y (r) + zoom (y) (m + 1))

where zoom (x) is the value of GL_ZOOM_X and zoom (y) is the value of GL_ZOOM_Y.

The following functions retrieve information related to glDrawPixels:

glGet with argument GL_CURRENT_RASTER_POSITION

glGet with argument GL_CURRENT_RASTER_POSITION_VALID

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_VALUE | Either width or height was negative.
GL_INVALID_ENUM | Either format or type was not an accepted value.
GL_INVALID_OPERATION | format was GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA, GL_BGR_EXT, GL_BGRA_EXT, GL_LUMINANCE, or GL_LUMINANCE_ALPHA, and OpenGL was in color-index mode.
GL_INVALID_ENUM | type was GL_BITMAP and format was not either GL_COLOR_INDEX or GL_STENCIL_INDEX.
GL_INVALID_OPERATION | format was GL_STENCIL_INDEX and there was no stencil buffer.
GL_INVALID_OPERATION | glDrawPixels was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glAlphaFunc, glBegin, glBlendFunc, glCopyPixels, glDepthFunc, glEnd, glGet, glLogicOp, glPixelMap, glPixelStore, glPixelTransfer, glPixelZoom, glRasterPos, glReadPixels, glScissor, glStencilFunc


glEdgeFlag

glEdgeFlag, glEdgeFlagv

The glEdgeFlag and glEdgeFlagv functions flag edges as either boundary or nonboundary.

void glEdgeFlag(
  GLboolean flag
);

Parameters

flag
In glEdgeFlag, specifies the current edge flag value, either TRUE or FALSE.
void glEdgeFlagv(
  const GLboolean * flag
);

Parameters

flag
In glEdgeFlagv, specifies a pointer to an array that contains a single Boolean element, which replaces the current edge flag value.

Remarks

Each vertex of a polygon, separate triangle, or separate quadrilateral specified between a glBegin/glEnd pair is marked as the start of either a boundary or nonboundary edge. If the current edge flag is TRUE when the vertex is specified, the vertex is marked as the start of a boundary edge. If the current edge flag is FALSE, the vertex is marked as the start of a nonboundary edge. The glEdgeFlag function sets the edge flag to TRUE if flag is nonzero, FALSE otherwise.

The vertices of connected triangles and connected quadrilaterals are always marked as boundary, regardless of the value of the edge flag.

Boundary and nonboundary edge flags on vertices are significant only if GL_POLYGON_MODE is set to GL_POINT or GL_LINE. See glPolygonMode.

Initially, the edge flag bit is TRUE.

The current edge flag can be updated at any time. In particular, glEdgeFlag can be called between a call to glBegin and the corresponding call to glEnd.

The following function retrieves information related to the glEdgeFlag function:

glGet with argument GL_EDGE_FLAG

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glGet, glPolygonMode


glEdgeFlagPointer

The glEdgeFlagPointer function defines an array of edge flags.

void glEdgeFlagPointer(
  GLsizei stride,
  GLsizei count,
  const GLboolean * pointer
);

Parameters

stride
The byte offset between consecutive edge flags. When stride is zero, the edge flags are tightly packed in the array.
count
The number of edge flags, counting from the first, that are static.
pointer
A pointer to the first edge flag in the array.

Remarks

The glEdgeFlagPointer function specifies the location and data of an array of Boolean edge flags to use when rendering. The stride parameter determines the byte offset from one edge flag to the next, which enables the packing of vertices and attributes in a single array or storage in separate arrays. In some implementations, storing the vertices and attributes in a single array can be more efficient than using separate arrays.

Starting from the first edge-flag array element, count indicates the total number of static elements. Your application can modify static elements, but once the elements are modified, the application must explicitly specify the array again before using it for any rendering. Nonstatic array elements are not accessed until you call glDrawArrays or glArrayElement.

Static array data can be read at any time. If any static array elements are modified and the array is not specified again, the results of any subsequent calls to glEdgeFlagPointer are undefined.

An edge-flag array is enabled when you specify the GL_EDGE_FLAG_ARRAY constant with glEnableClientState. When enabled, glDrawArrays or glArrayElement uses the edge-flag array. By default the edge-flag array is disabled.

You cannot include glEdgeFlagPointer in display lists.

When you specify an edge-flag array using glEdgeFlagPointer, the values of all the function's edge-flag array parameters are saved in a client-side state and static array elements can be cached. Because the edge-flag array parameters are in a client-side state, glPushAttrib and glPopAttrib do not save or restore their values.

Although calling glEdgeFlagPointer within a glBegin/glEnd pair does not generate an error, the results are undefined.

The following functions retrieve information related to the glEdgeFlagPointer function:

glIsEnabled with argument GL_EDGE_FLAG_ARRAY

glGet with argument GL_EDGE_FLAG_ARRAY_STRIDE

glGet with argument GL_EDGE_FLAG_ARRAY_COUNT

glGetPointerv with argument GL_EDGE_FLAG_ARRAY_POINTER

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | stride or count was negative.

See Also

glArrayElement, glBegin, glColorPointer, glDrawArrays, glEnableClientState, glEnd, glGet, glGetPointerv, glGetString, glIndexPointer, glIsEnabled, glNormalPointer, glPopAttrib, glPushAttrib, glTexCoordPointer, glVertexPointer


glEnable, glDisable

The glEnable and glDisable functions enable or disable OpenGL capabilities.

void glEnable(
  GLenum cap
);

void glDisable(
  GLenum cap
);

Parameters

cap
A symbolic constant indicating an OpenGL capability.

For discussion of the values cap can take, see the following Remarks section.

Remarks

The glEnable and glDisable functions enable and disable various capabilities. Use glIsEnabled or glGet to determine the current setting of any capability.

Both glEnable and glDisable take a single argument, cap, which can assume one of the following values:

GL_ALPHA_TEST
If enabled, do alpha testing. See glAlphaFunc.
GL_AUTO_NORMAL
If enabled, compute surface normal vectors analytically when either GL_MAP2_VERTEX_3 or GL_MAP2_VERTEX_4 has generated vertices. See glMap2.
GL_BLEND
If enabled, blend the incoming RGBA color values with the values in the color buffers. See glBlendFunc.
GL_CLIP_PLANEi
If enabled, clip geometry against user-defined clipping plane i. See glClipPlane.
GL_COLOR_MATERIAL
If enabled, have one or more material parameters track the current color. See glColorMaterial.
GL_CULL_FACE
If enabled, cull polygons based on their winding in window coordinates. See glCullFace.
GL_DEPTH_TEST
If enabled, do depth comparisons and update the depth buffer. See glDepthFunc and glDepthRange.
GL_DITHER
If enabled, dither color components or indexes before they are written to the color buffer.
GL_FOG
If enabled, blend a fog color into the post-texturing color. See glFog.
GL_LIGHTi
If enabled, include light i in the evaluation of the lighting equation. See glLightModel and glLight.
GL_LIGHTING
If enabled, use the current lighting parameters to compute the vertex color or index. If disabled, associate the current color or index with each vertex. See glMaterial, glLightModel, and glLight.
GL_LINE_SMOOTH
If enabled, draw lines with correct filtering. If disabled, draw aliased lines. See glLineWidth.
GL_LINE_STIPPLE
If enabled, use the current line stipple pattern when drawing lines. See glLineStipple.
GL_LOGIC_OP
If enabled, apply the currently selected logical operation to the incoming and color-buffer indexes. See glLogicOp.
GL_MAP1_COLOR_4
If enabled, calls to glEvalCoord1, glEvalMesh1, and glEvalPoint1 generate RGBA values. See also glMap1.
GL_MAP1_INDEX
If enabled, calls to glEvalCoord1, glEvalMesh1, and glEvalPoint1 generate color indexes. See also glMap1.
GL_MAP1_NORMAL
If enabled, calls to glEvalCoord1, glEvalMesh1, and glEvalPoint1 generate normals. See also glMap1.
GL_MAP1_TEXTURE_COORD_1
If enabled, calls to glEvalCoord1, glEvalMesh1, and glEvalPoint1 generate s texture coordinates. See also glMap1.
GL_MAP1_TEXTURE_COORD_2
If enabled, calls to glEvalCoord1, glEvalMesh1, and glEvalPoint1 generate s and t texture coordinates. See also glMap1.
GL_MAP1_TEXTURE_COORD_3
If enabled, calls to glEvalCoord1, glEvalMesh1, and glEvalPoint1 generate s, t, and r texture coordinates. See also glMap1.
GL_MAP1_TEXTURE_COORD_4
If enabled, calls to glEvalCoord1, glEvalMesh1, and glEvalPoint1 generate s, t, r, and q texture coordinates. See also glMap1.
GL_MAP1_VERTEX_3
If enabled, calls to glEvalCoord1, glEvalMesh1, and glEvalPoint1 generate x, y, and z vertex coordinates. See also glMap1.
GL_MAP1_VERTEX_4
If enabled, calls to glEvalCoord1, glEvalMesh1, and glEvalPoint1 generate homogeneous x, y, z, and w vertex coordinates. See also glMap1.
GL_MAP2_COLOR_4
If enabled, calls to glEvalCoord2, glEvalMesh2, and glEvalPoint2 generate RGBA values. See also glMap2.
GL_MAP2_INDEX
If enabled, calls to glEvalCoord2, glEvalMesh2, and glEvalPoint2 generate color indexes. See also glMap2.
GL_MAP2_NORMAL
If enabled, calls to glEvalCoord2, glEvalMesh2, and glEvalPoint2 generate normals. See also glMap2.
GL_MAP2_TEXTURE_COORD_1
If enabled, calls to glEvalCoord2, glEvalMesh2, and glEvalPoint2 generate s texture coordinates. See also glMap2.
GL_MAP2_TEXTURE_COORD_2
If enabled, calls to glEvalCoord2, glEvalMesh2, and glEvalPoint2 generate s and t texture coordinates. See also glMap2.
GL_MAP2_TEXTURE_COORD_3
If enabled, calls to glEvalCoord2, glEvalMesh2, and glEvalPoint2 generate s, t, and r texture coordinates. See also glMap2.
GL_MAP2_TEXTURE_COORD_4
If enabled, calls to glEvalCoord2, glEvalMesh2, and glEvalPoint2 generate s, t, r, and q texture coordinates. See also glMap2.
GL_MAP2_VERTEX_3
If enabled, calls to glEvalCoord2, glEvalMesh2, and glEvalPoint2 generate x, y, and z vertex coordinates. See also glMap2.
GL_MAP2_VERTEX_4
If enabled, calls to glEvalCoord2, glEvalMesh2, and glEvalPoint2 generate homogeneous x, y, z, and w vertex coordinates. See also glMap2.
GL_NORMALIZE
If enabled, normal vectors specified with glNormal are scaled to unit length after transformation. See glNormal.
GL_POINT_SMOOTH
If enabled, draw points with proper filtering. If disabled, draw aliased points. See glPointSize.
GL_POLYGON_SMOOTH
If enabled, draw polygons with proper filtering. If disabled, draw aliased polygons. See glPolygonMode.
GL_POLYGON_STIPPLE
If enabled, use the current polygon stipple pattern when rendering polygons. See glPolygonStipple.
GL_SCISSOR_TEST
If enabled, discard fragments that are outside the scissor rectangle. See glScissor.
GL_STENCIL_TEST
If enabled, do stencil testing and update the stencil buffer. See glStencilFunc and glStencilOp.
GL_TEXTURE_1D
If enabled, one-dimensional texturing is performed (unless two-dimensional texturing is also enabled). See glTexImage1D.
GL_TEXTURE_2D
If enabled, two-dimensional texturing is performed. See glTexImage2D.
GL_TEXTURE_GEN_Q
If enabled, the q texture coordinate is computed using the texture-generation function defined with glTexGen. Otherwise, the current q texture coordinate is used.
GL_TEXTURE_GEN_R
If enabled, the r texture coordinate is computed using the texture generation function defined with glTexGen. If disabled, the current r texture coordinate is used.
GL_TEXTURE_GEN_S
If enabled, the s texture coordinate is computed using the texture generation function defined with glTexGen. If disabled, the current s texture coordinate is used.
GL_TEXTURE_GEN_T
If enabled, the t texture coordinate is computed using the texture generation function defined with glTexGen. If disabled, the current t texture coordinate is used.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | cap was not one of the values listed in the preceding Remarks section.
GL_INVALID_OPERATION | glEnable was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glAlphaFunc, glArrayElement, glBegin, glBlendFunc, glClipPlane, glColorMaterial, glColorPointer, glCullFace, glDepthFunc, glDepthRange, glDrawArrays, glEdgeFlagPointer, glEnd, glEvalCoord1, glEvalMesh1, glEvalPoint1, glFog, glGet, glIndexPointer, glIsEnabled, glLight, glLightModel, glLineWidth, glLineStipple, glLogicOp, glMap1, glMap2, glMaterial, glNormal, glNormalPointer, glPointSize, glPolygonMode, glPolygonStipple, glScissor, glStencilFunc, glStencilOp, glTexCoordPointer, glTexGen, glTexImage1D, glTexImage2D


glEnableClientState, glDisableClientState

The glEnableClientState and glDisableClientState functions enable and disable arrays respectively.

void glEnableClientState(
  GLenum array
);

void glDisableClientState(
  GLenum array
);

Parameters

array
A symbolic constant for the array you want to enable or disable. This parameter can assume one of the following values:
GL_COLOR_ARRAY
If enabled, use color arrays with calls to glArrayElement, glDrawElements, or glDrawArrays. See also glColorPointer.
GL_EDGE_FLAG_ARRAY
If enabled, use edge flag arrays with calls to glArrayElement, glDrawElements, or glDrawArrays. See also glEdgeFlagPointer.
GL_INDEX_ARRAY
If enabled, use index arrays with calls to glArrayElement, glDrawElements, or glDrawArrays. See also glIndexPointer.
GL_NORMAL_ARRAY
If enabled, use normal arrays with calls to glArrayElement, glDrawElements, or glDrawArrays. See also glNormalPointer.
GL_TEXTURE_COORD_ARRAY
If enabled, use texture coordinate arrays with calls to glArrayElement, glDrawElements, or glDrawArrays. See also glTexCoordPointer.
GL_VERTEX_ARRAY
If enabled, use vertex arrays with calls to glArrayElement, glDrawElements, or glDrawArrays. See also glVertexPointer.

Remarks

The glEnableClientState and glDisableClientState functions enable and disable various individual arrays. Use glIsEnabled or glGet to determine the current setting of any capability.

Calling glEnableClientState and glDisableClientState between calls to glBegin and the corresponding call to glEnd can cause an error. If no error is generated, the behavior is undefined.

Note The glEnableClientState and glDisableClientState functions are only available in OpenGL version 1.1 or later.

Error Codes

The following is the error code generated and its condition.

Error Code | Condition
GL_INVALID_ENUM | array was not an accepted value.

See Also

glArrayElement, glBegin, glColorPointer, glDrawArrays, glDrawElements, glEdgeFlagPointer, glEnable, glEnd, glGetPointerv, glIndexPointer, glInterleavedArrays, glNormalPointer, glTexCoordPointer, glVertexPointer


glEvalCoord

glEvalCoord1d, glEvalCoord1f, glEvalCoord2d, glEvalCoord2f, glEvalCoord1dv, glEvalCoord1fv, glEvalCoord2dv, glEvalCoord2fv

These functions evaluate enabled one- and two-dimensional maps.

void glEvalCoord1d(
  GLdouble u
);

void glEvalCoord1f(
  GLfloat u
);

void glEvalCoord2d(
  GLdouble u,
  GLdouble v
);

void glEvalCoord2f(
  GLfloat u,
  GLfloat v
);

Parameters

u
In glEvalCoord1d, glEvalCoord1f, glEvalCoord2d, and glEvalCoord2f, specifies a value that is the domain coordinate u to the basis function defined in a previous glMap1 or glMap2 function.
v
A value that is the domain coordinate v to the basis function defined in a previous glMap2 function. This argument is not present in a glEvalCoord1 function.
void glEvalCoord1dv(
  const GLdouble * u
);

void glEvalCoord1fv(
  const GLfloat * u
);

void glEvalCoord2dv(
  const GLdouble * u
);

void glEvalCoord2fv(
  const GLfloat * u
);

Parameters

u
In glEvalCoord1dv, glEvalCoord1fv, glEvalCoord2dv, and glEvalCoord2fv, specifies a pointer to an array containing either one or two domain coordinates. The first coordinate is u. The second coordinate is v, which is present only in glEvalCoord2 versions.

Remarks

The glEvalCoord1 function evaluates enabled one-dimensional maps at argument u. The glEvalCoord2 function does the same for two-dimensional maps using two domain values, u and v. Maps are defined with glMap1 and glMap2, and are enabled and disabled with glEnable and glDisable.

When one of the glEvalCoord functions is issued, all currently enabled maps of the indicated dimension are evaluated. Then, for each enabled map, it is as if the corresponding OpenGL function were issued with the computed value. That is, if GL_MAP1_INDEX or GL_MAP2_INDEX is enabled, a glIndex function is simulated. If GL_MAP1_COLOR_4 or GL_MAP2_COLOR_4 is enabled, a glColor function is simulated. If GL_MAP1_NORMAL or GL_MAP2_NORMAL is enabled, a normal vector is produced, and if any of GL_MAP1_TEXTURE_COORD_1, GL_MAP1_TEXTURE_COORD_2, GL_MAP1_TEXTURE_COORD_3, GL_MAP1_TEXTURE_COORD_4, GL_MAP2_TEXTURE_COORD_1, GL_MAP2_TEXTURE_COORD_2, GL_MAP2_TEXTURE_COORD_3, and GL_MAP2_TEXTURE_COORD_4 is enabled, then an appropriate glTexCoord function is simulated.

OpenGL uses evaluated values instead of current values for those evaluations that are enabled, and current values otherwise, for color, color index, normal, and texture coordinates. However, the evaluated values do not update the current values. Thus, if glVertex functions are interspersed with glEvalCoord functions, the color, normal, and texture coordinates associated with the glVertex functions are not affected by the values generated by the glEvalCoord functions, but only by the most recent glColor, glIndex, glNormal, and glTexCoord functions.

No functions are issued for maps that are not enabled. If more than one texture evaluation is enabled for a particular dimension (for example, GL_MAP2_TEXTURE_COORD_1 and GL_MAP2_TEXTURE_COORD_2), then only the evaluation of the map that produces the larger number of coordinates (in this case, GL_MAP2_TEXTURE_COORD_2) is carried out. GL_MAP1_VERTEX_4 overrides GL_MAP1_VERTEX_3, and GL_MAP2_VERTEX_4 overrides GL_MAP2_VERTEX_3, in the same manner. If neither a three- nor four-component vertex map is enabled for the specified dimension, glEvalCoord is ignored.

If automatic normal generation is enabled, glEvalCoord2 calls glEnable with argument GL_AUTO_NORMAL to generate surface normals analytically, regardless of the contents or enabling of the GL_MAP2_NORMAL map. Let

The generated normal n is

If automatic normal generation is disabled, the corresponding normal map GL_MAP2_NORMAL, if enabled, is used to produce a normal. If neither automatic normal generation nor a normal map is enabled, no normal is generated for glEvalCoord2 functions.

The following functions retrieve information related to the glEvalCoord functions:

glIsEnabled with argument GL_MAP1_VERTEX_3

glIsEnabled with argument GL_MAP1_VERTEX_4

glIsEnabled with argument GL_MAP1_INDEX

glIsEnabled with argument GL_MAP1_COLOR_4

glIsEnabled with argument GL_MAP1_NORMAL

glIsEnabled with argument GL_MAP1_TEXTURE_COORD_1

glIsEnabled with argument GL_MAP1_TEXTURE_COORD_2

glIsEnabled with argument GL_MAP1_TEXTURE_COORD_3

glIsEnabled with argument GL_MAP1_TEXTURE_COORD_4

glIsEnabled with argument GL_MAP2_VERTEX_3

glIsEnabled with argument GL_MAP2_VERTEX_4

glIsEnabled with argument GL_MAP2_INDEX

glIsEnabled with argument GL_MAP2_COLOR_4

glIsEnabled with argument GL_MAP2_NORMAL

glIsEnabled with argument GL_MAP2_TEXTURE_COORD_1

glIsEnabled with argument GL_MAP2_TEXTURE_COORD_2

glIsEnabled with argument GL_MAP2_TEXTURE_COORD_3

glIsEnabled with argument GL_MAP2_TEXTURE_COORD_4

glIsEnabled with argument GL_AUTO_NORMAL

glGetMap

See Also

glBegin, glColor, glDisable, glEnable, glEvalMesh, glEvalPoint, glGetMap, glIndex, glIsEnabled, glMap1, glMap2, glMapGrid, glNormal, glTexCoord, glVertex


glEvalMesh

glEvalMesh1, glEvalMesh2

The glEvalMesh1 and glEvalMesh2 functions compute a one- or two-dimensional grid of points or lines.

void glEvalMesh1(
  GLenum mode,
  GLint i1,
  GLint i2
);

Parameters

mode
In glEvalMesh1, specifies whether to compute a one-dimensional mesh of points or lines. The following symbolic constants are accepted: GL_POINT and GL_LINE.
i1, i2
The first and last integer values for grid domain variable i.
void glEvalMesh2(
  GLenum mode,
  GLint i1,
  GLint i2,
  GLint j1,
  GLint j2
);

Parameters

mode
In glEvalMesh2, specifies whether to compute a two-dimensional mesh of points, lines, or polygons. The following symbolic constants are accepted: GL_POINT, GL_LINE, and GL_FILL.
i1, i2
The first and last integer values for grid domain variable i.
j1, j2
The first and last integer values for grid domain variable j.

Remarks

Use glMapGrid and glEvalMesh in tandem to efficiently generate and evaluate a series of evenly spaced map domain values. The glEvalMesh function steps through the integer domain of a one- or two-dimensional grid, whose range is the domain of the evaluation maps specified by glMap1 and glMap2. The mode parameter determines whether the resulting vertices are connected as points, lines, or filled polygons.

In the one-dimensional case, glEvalMesh1, the mesh is generated as if the following code fragment were executed:

glBegin(type) ;

for (i = i1; i <= i2; i += 1)

glEvalCoord1(i·Δu + u (1) )

glEnd( );

where

Δu = (u (2) – u (1) ) / n

and n, u (1) , and u (2) are the arguments to the most recent glMapGrid1 function. The type parameter is GL_POINTS if mode is GL_POINT, or GL_LINES if mode is GL_LINE. The one absolute numeric requirement is that if i = n, then the value computed from i·Δ u + u (1) is exactly u (2) .

In the two-dimensional case, glEvalMesh2, let

Δ u = (u (2) – u (1) )/n

Δ v = (v (2) – v (1) )/m,

where n, u (1) , u (2) , m, v (1) , and v (2) are the arguments to the most recent glMapGrid2 function. Then, if mode is GL_FILL, glEvalMesh2 is equivalent to:

for (j = j1; j < j2; j += 1) {

glBegin(GL_QUAD_STRIP);

for (i = i1; i <= i2; i += 1) {

glEvalCoord2(i·Δ u + u (1 ) , j ·Δ v + v (1) );

glEvalCoord2(i·Δ u + u (1 ) , (j+1) ·Δ v + v (1) );

}

glEnd( );

}

If mode is GL_LINE, then a call to glEvalMesh2 is equivalent to:

for (j = j1; j <= j2; j += 1) {

glBegin(GL_LINE_STRIP);

for (i = i1; i <= i2; i += 1)

glEvalCoord2(i·Δ u + u (1) , j·Δ v + v (1) );

glEnd( );

}

for (i = i1; i <= i2; i += 1) {

glBegin(GL_LINE_STRIP);

for (j = j1; j <= j1; j += 1)

glEvalCoord2(i·Δ u + u (1) , j·Δ v + v (1) );

glEnd( );

}

And finally, if mode is GL_POINT, then a call to glEvalMesh2 is equivalent to:

glBegin(GL_POINTS);

for (j = j1; j <= j2; j += 1) {

for (i = i1; i <= i2; i += 1) {

glEvalCoord2(i·Δ u + u (1) , j·Δ v + v (1) );

}

}

glEnd( );

In all three cases, the only absolute numeric requirements are that if i = n, then the value computed from i·Δ u + u (1) is exactly u (2) , and if j = m, then the value computed from j·Δ v + v (1) is exactly v (2) .

The following functions retrieve information relating to glEvalMesh:

glGet with argument GL_MAP1_GRID_DOMAIN

glGet with argument GL_MAP2_GRID_DOMAIN

glGet with argument GL_MAP1_GRID_SEGMENTS

glGet with argument GL_MAP2_GRID_SEGMENTS

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | mode was not an accepted value.
GL_INVALID_OPERATION | glEvalMesh was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glEvalCoord, glEvalPoint, glMap1, glMap2, glMapGrid


glEvalPoint

glEvalPoint1, glEvalPoint2

The glEvalPoint1 and glEvalPoint2 functions generate and evaluate a single point in a mesh.

void glEvalPoint1(
  GLint i
);

void glEvalPoint2(
  GLint i,
  GLint j
);

Parameters

i
The integer value for grid domain variable i.
j
The integer value for grid domain variable j (glEvalPoint2 only).

Remarks

The glMapGrid and glEvalMesh functions are used in tandem to efficiently generate and evaluate a series of evenly spaced map domain values. You can use glEvalPoint to evaluate a single grid point in the same gridspace that is traversed by glEvalMesh. Calling glEvalPoint1 is equivalent to calling

glEvalCoord1(i·Δu + u (1) );

where

Δu = (u (2) – u (1) ) / n

and n, u (1) , and u (2) are the arguments to the most recent glMapGrid1 function. The one absolute numeric requirement is that if i = n, then the value computed from i·Δ u + u (1) is exactly u (2) .

In the two-dimensional case, glEvalPoint2, let

Δu = (u (2) – u (1) )/n

Δv = (v (2) – v (1) )/m

where n, u (1) , u (2) , m, v (1) , and v (2) are the arguments to the most recent glMapGrid2 function. Then the glEvalPoint2 function is equivalent to calling

glEvalCoord2(i·Δu + u (1) , j·Δv + v (1) );

The only absolute numeric requirements are that if i = n, then the value computed from i·Δu + u (1) is exactly u (2) , and if j = m, then the value computed from j·Δv + v (1) is exactly v (2) .

The following functions retrieve information relating to glEvalPoint1 and glEvalPoint2:

glGet with argument GL_MAP1_GRID_DOMAIN

glGet with argument GL_MAP2_GRID_DOMAIN

glGet with argument GL_MAP1_GRID_SEGMENTS

glGet with argument GL_MAP2_GRID_SEGMENTS

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glEvalCoord, glEvalMesh, glGet, glMap1, glMap2, glMapGrid


glFeedbackBuffer

The glFeedbackBuffer function controls feedback mode.

void glFeedbackBuffer(
  GLsizei size,
  GLenum type,
  GLfloat * buffer
);

Parameters

size
The maximum number of values that can be written into buffer.
type
A symbolic constant that describes the information that will be returned for each vertex. The following symbolic constants are accepted: GL_2D, GL_3D, GL_3D_COLOR, GL_3D_COLOR_TEXTURE, and GL_4D_COLOR_TEXTURE.
buffer
Returns the feedback data.

Remarks

The glFeedbackBuffer function controls feedback. Feedback, like selection, is an OpenGL mode. The mode is selected by calling glRenderMode with GL_FEEDBACK. When OpenGL is in feedback mode, no pixels are produced by rasterization. Instead, information about primitives that would have been rasterized is fed back to the application using OpenGL.

The glFeedbackBuffer function has three arguments:

  • buffer is a pointer to an array of floating-point values into which feedback information is placed.
  • size indicates the size of the array.
  • type is a symbolic constant describing the information that is fed back for each vertex.

You must issue glFeedbackBuffer before feedback mode is enabled (by calling glRenderMode with argument GL_FEEDBACK). Setting GL_FEEDBACK without establishing the feedback buffer, or calling glFeedbackBuffer while OpenGL is in feedback mode, is an error.

Take OpenGL out of feedback mode by calling glRenderMode with a parameter value other than GL_FEEDBACK. When you do this while OpenGL is in feedback mode, glRenderMode returns the number of entries placed in the feedback array. The returned value never exceeds size. If the feedback data required more room than was available in buffer, glRenderMode returns a negative value.

While in feedback mode, each primitive that would be rasterized generates a block of values that get copied into the feedback array. If doing so would cause the number of entries to exceed the maximum, the block is partially written so as to fill the array (if there is any room left at all), and an overflow flag is set. Each block begins with a code indicating the primitive type, followed by values that describe the primitive's vertices and associated data. Entries are also written for bitmaps and pixel rectangles. Feedback occurs after polygon culling and glPolygonMode interpretation of polygons has taken place, so polygons that are culled are not returned in the feedback buffer. It can also occur after polygons with more than three edges are broken up into triangles, if the OpenGL implementation renders polygons by performing this decomposition.

You can insert a marker into the feedback buffer with glPassThrough.

The following is the grammar for the blocks of values written into the feedback buffer. Each primitive is indicated with a unique identifying value followed by some number of vertices. Polygon entries include an integer value indicating how many vertices follow. A vertex is fed back as some number of floating-point values, as determined by type. Colors are fed back as four values in RGBA mode and one value in color-index mode.

feedbackList <— feedbackItem feedbackList | feedbackItem

feedbackItem <— point | lineSegment | polygon | bitmap | pixelRectangle | passThru

point <— GL_POINT_TOKEN vertex

lineSegment <— GL_LINE_TOKEN vertex vertex | GL_LINE_RESET_TOKEN vertex vertex

polygon <— GL_POLYGON_TOKEN n polySpec

polySpec <— polySpec vertex | vertex vertex vertex

bitmap <— GL_BITMAP_TOKEN vertex

pixelRectangle <— GL_DRAW_PIXEL_TOKEN vertex | GL_COPY_PIXEL_TOKEN vertex

passThru <— GL_PASS_THROUGH_TOKEN value

vertex <— 2d | 3d | 3dColor | 3dColorTexture | 4dColorTexture

2d <— value value

3d <— value value value

3dColor <— value value value color

3dColorTexture <— value value value color tex

4dColorTexture <— value value value value color tex

color <— rgba | index

rgba <— value value value value

index <— value

tex <— value value value value

The value parameter is a floating-point number, and n is a floating-point integer giving the number of vertices in the polygon. The following are symbolic floating-point constants: GL_POINT_TOKEN, GL_LINE_TOKEN, GL_LINE_RESET_TOKEN, GL_POLYGON_TOKEN, GL_BITMAP_TOKEN, GL_DRAW_PIXEL_TOKEN, GL_COPY_PIXEL_TOKEN, and GL_PASS_THROUGH_TOKEN. GL_LINE_RESET_TOKEN is returned whenever the line stipple pattern is reset. The data returned as a vertex depends on the feedback type.

The following table gives the correspondence between type and the number of values per vertex; k is 1 in color-index mode and 4 in RGBA mode.

Type | Coordinates | Color | Texture | Total Number of Values
GL_2D | x, y |  |  | 2
GL_3D | x, y, z |  |  | 3
GL_3D_COLOR | x, y, z | k |  | 3 + k
GL_3D_COLOR_TEXTURE | x, y, z, | k | 4 | 7 + k
GL_4D_COLOR_TEXTURE | x, y, z, w | k | 4 | 8 + k

Feedback vertex coordinates are in window coordinates, except w, which is in clip coordinates. Feedback colors are lighted, if lighting is enabled. Feedback texture coordinates are generated, if texture coordinate generation is enabled. They are always transformed by the texture matrix.

The glFeedbackBuffer function, when used in a display list, is not compiled into the display list but rather is executed immediately.

The following function retrieves information related to glFeedbackBuffer:

glGet with argument GL_RENDER_MODE

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | type was not an accepted value.
GL_INVALID_VALUE | size was negative.
GL_INVALID_OPERATION | glFeedbackBuffer was called while the render mode was GL_FEEDBACK, or glRenderMode was called with argument GL_FEEDBACK before glFeedbackBuffer was called at least once.
GL_INVALID_OPERATION | glFeedbackBuffer was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glGet, glLineStipple, glPassThrough, glPolygonMode, glRenderMode, glSelectBuffer


glFinish

The glFinish function blocks until all OpenGL execution is complete.

void glFinish(
  void
);

Remarks

The glFinish function does not return until the effects of all previously called OpenGL functions are complete. Such effects include all changes to the OpenGL state, all changes to the connection state, and all changes to the frame buffer contents.

The glFinish function requires a round trip to the server.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glFinish was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glFlush


glFlush

The glFlush function forces execution of OpenGL functions in finite time.

void glFlush(
  void
);

Remarks

Different OpenGL implementations buffer commands in several different locations, including network buffers and the graphics accelerator itself. The glFlush function empties all these buffers, causing all issued commands to be executed as quickly as they are accepted by the actual rendering engine. Though this execution may not be completed in any particular time period, it does complete in finite time.

Because any OpenGL program might be executed over a network, or on an accelerator that buffers commands, be sure to call glFlush in all programs whenever they require that all of their previously issued commands have been completed. For example, call glFlush before waiting for user input that depends on the generated image.

The glFlush function can return at any time. It does not wait until the execution of all previously issued OpenGL functions is complete.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glFlush was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glFinish


glFog

glFogf, glFogi, glFogfv, glFogiv

These functions specify fog parameters.

void glFogf(
  GLenum pname,
  GLfloat param
);

void glFogi(
  GLenum pname,
  GLint param
);

void glFogfv(
  GLenum pname,
  const GLfloat * params
);

void glFogiv(
  GLenum pname,
  const GLint * params
);

Parameters

pname
In glFogf and glFogi, specifies a single-valued fog parameter.

In glFogfv and glFogiv, specifies a fog parameter.

The glFogf, glFogi, glFogfv, and glFogiv functions accept the following values:

GL_FOG_MODE
The params parameter is a single integer or floating-point value that specifies the equation to be used to compute the fog blend factor, f. Three symbolic constants are accepted: GL_LINEAR, GL_EXP, and GL_EXP2. The equations corresponding to these symbolic constants are defined in the following Remarks section. The default fog mode is GL_EXP.
GL_FOG_DENSITY
The params parameter is a single integer or floating-point value that specifies density, the fog density used in both exponential fog equations. Only nonnegative densities are accepted. The default fog density is 1.0.
GL_FOG_START
The params parameter is a single integer or floating-point value that specifies start, the near distance used in the linear fog equation. The default near distance is 0.0.
GL_FOG_END
The params parameter is a single integer or floating-point value that specifies end, the far distance used in the linear fog equation. The default far distance is 1.0.
GL_FOG_INDEX
The params parameter is a single integer or floating-point value that specifies i (f) , the fog color index. The default fog index is 0.0.

The glFogfv and glFogiv functions also accept GL_FOG_COLOR:

GL_FOG_COLOR
The params parameter contains four integer or floating-point values that specify C (f) , the fog color. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to –1.0. Floating-point values are mapped directly. After conversion, all color components are clamped to the range [0,1]. The default fog color is (0,0,0,0).
param
In glFogf and glFogi, specifies the value that pname will be set to.
params
In glFogfv and glFogiv, specifies the value or values to be assigned to pname. GL_FOG_COLOR requires an array of four values. All other parameters accept an array containing only a single value.

Remarks

You enable and disable fog with glEnable and glDisable, using the argument GL_FOG. While enabled, fog affects rasterized geometry, bitmaps, and pixel blocks, but not buffer-clear operations.

The glFog function assigns the value or values in params to the fog parameter specified by pname.

Fog blends a fog color with each rasterized pixel fragment's posttexturing color using a blending factor f. Factor f is computed in one of three ways, depending on the fog mode. Let z be the distance in eye coordinates from the origin to the fragment being fogged. The equation for GL_LINEAR fog is:

The equation for GL_EXP fog is:

The equation for GL_EXP2 fog is:

Regardless of the fog mode, f is clamped to the range [0,1] after it is computed. Then, if OpenGL is in RGBA color mode, the fragment's color C (r) is replaced by

In color-index mode, the fragment's color index i (r) is replaced by

The following functions retrieve information related to the glFog functions:

glGet with argument GL_FOG_COLOR

glGet with argument GL_FOG_INDEX

glGet with argument GL_FOG_DENSITY

glGet with argument GL_FOG_START

glGet with argument GL_FOG_END

glGet with argument GL_FOG_MODE

glIsEnabled with argument GL_FOG

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | pname was not an accepted value.
GL_INVALID_OPERATION | glFinish was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glDisable, glEnable, glEnd, glGet, glIsEnabled


glFrontFace

The glFrontFace function defines front- and back-facing polygons.

void glFrontFace(
  GLenum mode
);

Parameters

mode
The orientation of front-facing polygons. GL_CW and GL_CCW are accepted. The default value is GL_CCW.

Remarks

In a scene composed entirely of opaque closed surfaces, back-facing polygons are never visible. Eliminating these invisible polygons has the obvious benefit of speeding up the rendering of the image. You enable and disable elimination of back-facing polygons with glEnable and glDisable using argument GL_CULL_FACE.

The projection of a polygon to window coordinates is said to have clockwise winding if an imaginary object following the path from its first vertex, its second vertex, and so on, to its last vertex, and finally back to its first vertex, moves in a clockwise direction about the interior of the polygon. The polygon's winding is said to be counterclockwise if the imaginary object following the same path moves in a counterclockwise direction about the interior of the polygon. The glFrontFace function specifies whether polygons with clockwise winding in window coordinates, or counterclockwise winding in window coordinates, are taken to be front-facing. Passing GL_CCW to mode selects counterclockwise polygons as front-facing; GL_CW selects clockwise polygons as front-facing. By default, counterclockwise polygons are taken to be front-facing.

The following function retrieves information about glFrontface:

glGet with argument GL_FRONT_FACE

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | mode was not an accepted value.
GL_INVALID_OPERATION | glFrontFace was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glCullFace, glDisable, glEnable, glEnd, glGet, glLightModel


glFrustum

The glFrustum function multiplies the current matrix by a perspective matrix.

void glFrustum(
  GLdouble left,
  GLdouble right,
  GLdouble bottom,
  GLdouble top,
  GLdouble znear,
  GLdouble zfar
);

Parameters

left, right
The coordinates for the left and right vertical clipping planes.
bottom, top
The coordinates for the bottom and top horizontal clipping planes.
znear, zfar
The distances to the near and far depth clipping planes. Both distances must be positive.

Remarks

The glFrustum function describes a perspective matrix that produces a perspective projection. The (left, bottom, znear) and (right, top, znear) parameters specify the points on the near clipping plane that are mapped to the lower-left and upper-right corners of the window, respectively, assuming that the eye is located at (0, 0, 0). The zfar parameter specifies the location of the far clipping plane. Both znear and zfar must be positive. The corresponding matrix is:

The glFrustum function multiplies the current matrix by this matrix, with the result replacing the current matrix. That is, if M is the current matrix and F is the frustum perspective matrix, then glFrustum replaces M with M · F.

Use glPushMatrix and glPopMatrix to save and restore the current matrix stack.

Depth-buffer precision is affected by the values specified for znear and zfar. The greater the ratio of zfar to znear is, the less effective the depth buffer will be at distinguishing between surfaces that are near each other. If

roughly log (2) r bits of depth buffer precision are lost. Because r approaches infinity as znear approaches zero, you should never set znear to zero.

The following functions retrieve information about glFrustum:

glGet with argument GL_MATRIX_MODE

glGet with argument GL_MODELVIEW_MATRIX

glGet with argument GL_PROJECTION_MATRIX

glGet with argument GL_TEXTURE_MATRIX

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_VALUE | znear or zfar was not positive.
GL_INVALID_OPERATION | glFrustum was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glGet, glMatrixMode, glMultMatrix, glOrtho, glPopMatrix, glPushMatrix, glViewport


glGenLists

The glGenLists function generates a contiguous set of empty display lists.

GLuint glGenLists(
  GLsizei range
);

Parameters

range
The number of contiguous empty display lists to be generated.

Remarks

The glGenLists function has one argument, range. It returns an integer n such that range contiguous empty display lists, named n, n+1, . . ., n+range – 1, are created. If range is zero, if there is no group of range contiguous names available, or if any error is generated, then no display lists are generated and zero is returned.

The following function retrieves information related to glGenLists:

glIsList

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_VALUE | range is negative.
GL_INVALID_OPERATION | glGenLists was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glCallList, glCallLists, glDeleteLists, glEnd, glIsList, glNewList


glGenTextures

The glGenTextures function generates texture names.

void glGenTextures(
  GLsizei n,
  GLuint * textures
);

Parameters

n
The number of texture names to be generated.
textures
A pointer to the first element of an array in which the generated texture names are stored.

Remarks

The glGenTextures function returns n texture names in the textures parameter. The texture names are not necessarily a contiguous set of integers, however, none of the returned names can have been in use immediately prior to calling the glGenTextures function. The generated textures assume the dimensionality of the texture target to which they are first bound with the glBindTexture function. Texture names returned by glGenTextures are not returned by subsequent calls to glGenTextures unless they are first deleted by calling glDeleteTextures.

You cannot include glGenTextures in display lists.

Note The glGenTextures function is only available in OpenGL version 1.1 or later.

The following function retrieves information related to glGenTextures:

glIsTexture

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_VALUE | n was a negative value.
GL_INVALID_OPERATION | glGenTextures was called between a call to glBegin and the corresponding call to glEnd.

See Also

glBegin, glBindTexture, glDeleteTextures, glEnd, glGet, glGetTexParameter, glIsTexture, glTexImage1D, glTexImage2D, glTexParameter


glGetBooleanv, glGetDoublev, glGetFloatv, glGetIntegerv

These functions return the value or values of a selected parameter.

void glGetBooleanv(
  GLenum pname,
  GLboolean * params
);

void glGetDoublev(
  GLenum pname,
  GLdouble * params
);

void glGetFloatv(
  GLenum pname,
  GLfloat * params
);

void glGetIntegerv(
  GLenum pname,
  GLint * params
);

Parameters

pname
The parameter value to be returned. The following symbolic constants are accepted:
GL_ACCUM_ALPHA_BITS
The params parameter returns one value: the number of alpha bitplanes in the accumulation buffer.
GL_ACCUM_BLUE_BITS
The params parameter returns one value: the number of blue bitplanes in the accumulation buffer.
GL_ACCUM_CLEAR_VALUE
The params parameter returns four values: the red, green, blue, and alpha values used to clear the accumulation buffer. Integer values, if requested, are linearly mapped from the internal floating-point representation such that 1.0 returns the most positive representable integer value, and –1.0 returns the most negative representable integer value. See glClearAccum.
GL_ACCUM_GREEN_BITS
The params parameter returns one value: the number of green bitplanes in the accumulation buffer.
GL_ACCUM_RED_BITS
The params parameter returns one value: the number of red bitplanes in the accumulation buffer.
GL_ALPHA_BIAS
The params parameter returns one value: the alpha bias factor used during pixel transfers. See glPixelTransfer.
GL_ALPHA_BITS
The params parameter returns one value: the number of alpha bitplanes in each color buffer.
GL_ALPHA_SCALE
The params parameter returns one value: the alpha scale factor used during pixel transfers. See glPixelTransfer.
GL_ALPHA_TEST
The params parameter returns a single Boolean value indicating whether alpha testing of fragments is enabled. See glAlphaFunc.
GL_ALPHA_TEST_FUNC
The params parameter returns one value: the symbolic name of the alpha test function. See glAlphaFunc.
GL_ALPHA_TEST_REF
The params parameter returns one value: the reference value for the alpha test. See glAlphaFunc. An integer value, if requested, is linearly mapped from the internal floating-point representation such that 1.0 returns the most positive representable integer value, and –1.0 returns the most negative representable integer value.
GL_ATTRIB_STACK_DEPTH
The params parameter returns one value: the depth of the attribute stack. If the stack is empty, zero is returned. See glPushAttrib.
GL_AUTO_NORMAL
The params parameter returns a single Boolean value indicating whether 2-D map evaluation automatically generates surface normals. See glMap2.
GL_AUX_BUFFERS
The params parameter returns one value: the number of auxiliary color buffers.
GL_BLEND
The params parameter returns a single Boolean value indicating whether blending is enabled. See glBlendFunc.
GL_BLEND_DST
The params parameter returns one value: the symbolic constant identifying the destination blend function. See glBlendFunc.
GL_BLEND_SRC
The params parameter returns one value: the symbolic constant identifying the source blend function. See glBlendFunc.
GL_BLUE_BIAS
The params parameter returns one value: the blue bias factor used during pixel transfers. See glPixelTransfer.
GL_BLUE_BITS
The params parameter returns one value: the number of blue bitplanes in each color buffer.
GL_BLUE_SCALE
The params parameter returns one value: the blue scale factor used during pixel transfers. See glPixelTransfer.
GL_CLIP_PLANEi
The params parameter returns a single Boolean value indicating whether the specified clipping plane is enabled. See glClipPlane.
GL_COLOR_CLEAR_VALUE
The params parameter returns four values: the red, green, blue, and alpha values used to clear the color buffers. Integer values, if requested, are linearly mapped from the internal floating-point representation such that 1.0 returns the most positive representable integer value, and –1.0 returns the most negative representable integer value. See glClearColor.
GL_COLOR_MATERIAL
The params parameter returns a single Boolean value indicating whether one or more material parameters are tracking the current color. See glColorMaterial.
GL_COLOR_MATERIAL_FACE
The params parameter returns one value: a symbolic constant indicating which materials have a parameter that is tracking the current color. See glColorMaterial.
GL_COLOR_MATERIAL_PARAMETER
The params parameter returns one value: a symbolic constant indicating which material parameters are tracking the current color. See glColorMaterial.
GL_COLOR_WRITEMASK
The params parameter returns four Boolean values: the red, green, blue, and alpha write enables for the color buffers. See glColorMask.
GL_CULL_FACE
The params parameter returns a single Boolean value indicating whether polygon culling is enabled. See glCullFace.
GL_CULL_FACE_MODE
The params parameter returns one value: a symbolic constant indicating which polygon faces are to be culled. See glCullFace.
GL_CURRENT_COLOR
The params parameter returns four values: the red, green, blue, and alpha values of the current color. Integer values, if requested, are linearly mapped from the internal floating-point representation such that 1.0 returns the most positive representable integer value, and –1.0 returns the most negative representable integer value. See glColor.
GL_CURRENT_INDEX
The params parameter returns one value: the current color index. See glIndex.
GL_CURRENT_NORMAL
The params parameter returns three values: the x, y, and z values of the current normal. Integer values, if requested, are linearly mapped from the internal floating-point representation such that 1.0 returns the most positive representable integer value, and –1.0 returns the most negative representable integer value. See glNormal.
GL_CURRENT_RASTER_COLOR
The params parameter returns four values: the red, green, blue, and alpha values of the current raster position. Integer values, if requested, are linearly mapped from the internal floating-point representation such that 1.0 returns the most positive representable integer value, and –1.0 returns the most negative representable integer value. See glRasterPos.
GL_CURRENT_RASTER_DISTANCE
The params parameter returns one value: the distance from the eye to the current raster position. See glRasterPos.
GL_CURRENT_RASTER_INDEX
The params parameter returns one value: the color index of the current raster position. See glRasterPos.
GL_CURRENT_RASTER_POSITION
The params parameter returns four values: the x, y, z, and w components of the current raster position. The x, y, and z components are in window coordinates, and w is in clip coordinates. See glRasterPos.
GL_CURRENT_RASTER_TEXTURE_COORDS
The params parameter returns four values: the s, t, r, and q current raster texture coordinates. See glRasterPos and glTexCoord.
GL_CURRENT_RASTER_POSITION_VALID
The params parameter returns a single Boolean value indicating whether the current raster position is valid. See glRasterPos.
GL_CURRENT_TEXTURE_COORDS
The params parameter returns four values: the s, t, r, and q current texture coordinates. See glTexCoord.
GL_DEPTH_BIAS
The params parameter returns one value: the depth bias factor used during pixel transfers. See glPixelTransfer.
GL_DEPTH_BITS
The params parameter returns one value: the number of bitplanes in the depth buffer.
GL_DEPTH_CLEAR_VALUE
The params parameter returns one value: the value that is used to clear the depth buffer. Integer values, if requested, are linearly mapped from the internal floating-point representation such that 1.0 returns the most positive representable integer value, and –1.0 returns the most negative representable integer value. See glClearDepth.
GL_DEPTH_FUNC
The params parameter returns one value: the symbolic constant that indicates the depth comparison function. See glDepthFunc.
GL_DEPTH_RANGE
The params parameter returns two values: the near and far mapping limits for the depth buffer. Integer values, if requested, are linearly mapped from the internal floating-point representation such that 1.0 returns the most positive representable integer value, and –1.0 returns the most negative representable integer value. See glDepthRange.
GL_DEPTH_SCALE
The params parameter returns one value: the depth scale factor used during pixel transfers. See glPixelTransfer.
GL_DEPTH_TEST
The params parameter returns a single Boolean value indicating whether depth testing of fragments is enabled. See glDepthFunc and glDepthRange.
GL_DEPTH_WRITEMASK
The params parameter returns a single Boolean value indicating if the depth buffer is enabled for writing. See glDepthMask.
GL_DITHER
The params parameter returns a single Boolean value indicating whether dithering of fragment colors and indexes is enabled.
GL_DOUBLEBUFFER
The params parameter returns a single Boolean value indicating whether double buffering is supported.
GL_DRAW_BUFFER
The params parameter returns one value: a symbolic constant indicating which buffers are being drawn to. See glDrawBuffer.
GL_EDGE_FLAG
The params parameter returns a single Boolean value indicating whether the current edge flag is true or false. See glEdgeFlag.
GL_FOG
The params parameter returns a single Boolean value indicating whether fogging is enabled. See glFog.
GL_FOG_COLOR
The params parameter returns four values: the red, green, blue, and alpha components of the fog color. Integer values, if requested, are linearly mapped from the internal floating-point representation such that 1.0 returns the most positive representable integer value, and –1.0 returns the most negative representable integer value. See glFog.
GL_FOG_DENSITY
The params parameter returns one value: the fog density parameter. See glFog.
GL_FOG_END
The params parameter returns one value: the end factor for the linear fog equation. See glFog.
GL_FOG_HINT
The params parameter returns one value: a symbolic constant indicating the mode of the fog hint. See glHint.
GL_FOG_INDEX
The params parameter returns one value: the fog color index. See glFog.
GL_FOG_MODE
The params parameter returns one value: a symbolic constant indicating which fog equation is selected. See glFog.
GL_FOG_START
The params parameter returns one value: the start factor for the linear fog equation. See glFog.
GL_FRONT_FACE
The params parameter returns one value: a symbolic constant indicating whether clockwise or counterclockwise polygon winding is treated as front-facing. See glFrontFace.
GL_GREEN_BIAS
The params parameter returns one value: the green bias factor used during pixel transfers.
GL_GREEN_BITS
The params parameter returns one value: the number of green bitplanes in each color buffer.
GL_GREEN_SCALE
The params parameter returns one value: the green scale factor used during pixel transfers. See glPixelTransfer.
GL_INDEX_BITS
The params parameter returns one value: the number of bitplanes in each color-index buffer.
GL_INDEX_CLEAR_VALUE
The params parameter returns one value: the color index used to clear the color-index buffers. See glClearIndex.
GL_INDEX_MODE
The params parameter returns a single Boolean value indicating whether OpenGL is in color-index mode (TRUE) or RGBA mode (FALSE).
GL_INDEX_OFFSET
The params parameter returns one value: the offset added to color and stencil indexes during pixel transfers. See glPixelTransfer.
GL_INDEX_SHIFT
The params parameter returns one value: the amount that color and stencil indexes are shifted during pixel transfers. See glPixelTransfer.
GL_INDEX_WRITEMASK
The params parameter returns one value: a mask indicating which bitplanes of each color-index buffer can be written. See glIndexMask.
GL_LIGHTi
The params parameter returns a single Boolean value indicating whether the specified light is enabled. See glLight and glLightModel.
GL_LIGHTING
The params parameter returns a single Boolean value indicating whether lighting is enabled. See glLightModel.
GL_LIGHT_MODEL_AMBIENT
The params parameter returns four values: the red, green, blue, and alpha components of the ambient intensity of the entire scene. Integer values, if requested, are linearly mapped from the internal floating-point representation such that 1.0 returns the most positive representable integer value, and –1.0 returns the most negative representable integer value. See glLightModel.
GL_LIGHT_MODEL_LOCAL_VIEWER
The params parameter returns a single Boolean value indicating whether specular reflection calculations treat the viewer as being local to the scene. See glLightModel.
GL_LIGHT_MODEL_TWO_SIDE
The params parameter returns a single Boolean value indicating whether separate materials are used to compute lighting for front- and back-facing polygons. See glLightModel.
GL_LINE_SMOOTH
The params parameter returns a single Boolean value indicating whether antialiasing of lines is enabled. See glLineWidth.
GL_LINE_SMOOTH_HINT
The params parameter returns one value: a symbolic constant indicating the mode of the line antialiasing hint. See glHint.
GL_LINE_STIPPLE
The params parameter returns a single Boolean value indicating whether stippling of lines is enabled. See glLineStipple.
GL_LINE_STIPPLE_PATTERN
The params parameter returns one value: the 16-bit line stipple pattern. See glLineStipple.
GL_LINE_STIPPLE_REPEAT
The params parameter returns one value: the line stipple repeat factor. See glLineStipple.
GL_LINE_WIDTH
The params parameter returns one value: the line width as specified with glLineWidth.
GL_LINE_WIDTH_GRANULARITY
The params parameter returns one value: the width difference between adjacent supported widths for antialiased lines. See glLineWidth.
GL_LINE_WIDTH_RANGE
The params parameter returns two values: the smallest and largest supported widths for antialiased lines. See glLineWidth.
GL_LIST_BASE
The params parameter returns one value: the base offset added to all names in arrays presented to glCallLists. See glListBase.
GL_LIST_INDEX
The params parameter returns one value: the name of the display list currently under construction. Zero is returned if no display list is currently under construction. See glNewList.
GL_LIST_MODE
The params parameter returns one value: a symbolic constant indicating the construction mode of the display list currently being constructed. See glNewList.
GL_LOGIC_OP
The params parameter returns a single Boolean value indicating whether fragment indexes are merged into the frame buffer using a logical operation. See glLogicOp.
GL_LOGIC_OP_MODE
The params parameter returns one value: a symbolic constant indicating the selected logic operational mode. See glLogicOp.
GL_MAP1_COLOR_4
The params parameter returns a single Boolean value indicating whether 1-D evaluation generates colors. See glMap1.
GL_MAP1_GRID_DOMAIN
The params parameter returns two values: the endpoints of the 1-D maps grid domain. See glMapGrid.
GL_MAP1_GRID_SEGMENTS
The params parameter returns one value: the number of partitions in the 1-D maps grid domain. See glMapGrid.
GL_MAP1_INDEX
The params parameter returns a single Boolean value indicating whether 1-D evaluation generates color indexes. See glMap1.
GL_MAP1_NORMAL
The params parameter returns a single Boolean value indicating whether 1-D evaluation generates normals. See glMap1.
GL_MAP1_TEXTURE_COORD_1
The params parameter returns a single Boolean value indicating whether 1-D evaluation generates 1-D texture coordinates. See glMap1.
GL_MAP1_TEXTURE_COORD_2
The params parameter returns a single Boolean value indicating whether 1-D evaluation generates 2-D texture coordinates. See glMap1.
GL_MAP1_TEXTURE_COORD_3
The params parameter returns a single Boolean value indicating whether 1-D evaluation generates 3-D texture coordinates. See glMap1.
GL_MAP1_TEXTURE_COORD_4
The params parameter returns a single Boolean value indicating whether 1-D evaluation generates 4-D texture coordinates. See glMap1.
GL_MAP1_VERTEX_3
The params parameter returns a single Boolean value indicating whether 1-D evaluation generates 3-D vertex coordinates. See glMap1.
GL_MAP1_VERTEX_4
The params parameter returns a single Boolean value indicating whether 1-D evaluation generates 4-D vertex coordinates. See glMap1.
GL_MAP2_COLOR_4
The params parameter returns a single Boolean value indicating whether 2-D evaluation generates colors. See glMap2.
GL_MAP2_GRID_DOMAIN
The params parameter returns four values: the endpoints of the 2-D maps i and j grid domains. See glMapGrid.
GL_MAP2_GRID_SEGMENTS
The params parameter returns two values: the number of partitions in the 2-D maps i and j grid domains. See glMapGrid.
GL_MAP2_INDEX
The params parameter returns a single Boolean value indicating whether 2-D evaluation generates color indexes. See glMap2.
GL_MAP2_NORMAL
The params parameter returns a single Boolean value indicating whether 2-D evaluation generates normals. See glMap2.
GL_MAP2_TEXTURE_COORD_1
The params parameter returns a single Boolean value indicating whether 2-D evaluation generates 1-D texture coordinates. See glMap2.
GL_MAP2_TEXTURE_COORD_2
The params parameter returns a single Boolean value indicating whether 2-D evaluation generates 2-D texture coordinates. See glMap2.
GL_MAP2_TEXTURE_COORD_3
The params parameter returns a single Boolean value indicating whether 2-D evaluation generates 3-D texture coordinates. See glMap2.
GL_MAP2_TEXTURE_COORD_4
The params parameter returns a single Boolean value indicating whether 2-D evaluation generates 4-D texture coordinates. See glMap2.
GL_MAP2_VERTEX_3
The params parameter returns a single Boolean value indicating whether 2-D evaluation generates 3-D vertex coordinates. See glMap2.
GL_MAP2_VERTEX_4
The params parameter returns a single Boolean value indicating whether 2-D evaluation generates 4-D vertex coordinates. See glMap2.
GL_MAP_COLOR
The params parameter returns a single Boolean value indicating whether colors and color indexes are to be replaced by table lookup during pixel transfers. See glPixelTransfer.
GL_MAP_STENCIL
The params parameter returns a single Boolean value indicating whether stencil indexes are to be replaced by table lookup during pixel transfers. See glPixelTransfer.
GL_MATRIX_MODE
The params parameter returns one value: a symbolic constant indicating which matrix stack is currently the target of all matrix operations. See glMatrixMode.
GL_MAX_ATTRIB_STACK_DEPTH
The params parameter returns one value: the maximum supported depth of the attribute stack. See glPushAttrib.
GL_MAX_CLIP_PLANES
The params parameter returns one value: the maximum number of application-defined clipping planes. See glClipPlane.
GL_MAX_EVAL_ORDER
The params parameter returns one value: the maximum equation order supported by 1-D and 2-D evaluators. See glMap1 and glMap2.
GL_MAX_LIGHTS
The params parameter returns one value: the maximum number of lights. See glLight.
GL_MAX_LIST_NESTING
The params parameter returns one value: the maximum recursion depth allowed during display-list traversal. See glCallList.
GL_MAX_MODELVIEW_STACK_DEPTH
The params parameter returns one value: the maximum supported depth of the modelview matrix stack. See glPushMatrix.
GL_MAX_NAME_STACK_DEPTH
The params parameter returns one value: the maximum supported depth of the selection name stack. See glPushName.
GL_MAX_PIXEL_MAP_TABLE
The params parameter returns one value: the maximum supported size of a glPixelMap lookup table. See glPixelMap.
GL_MAX_PROJECTION_STACK_DEPTH
The params parameter returns one value: the maximum supported depth of the projection matrix stack. See glPushMatrix.
GL_MAX_TEXTURE_SIZE
The params parameter returns one value: the maximum width or height of any texture image (without borders). See glTexImage1D and glTexImage2D.
GL_MAX_TEXTURE_STACK_DEPTH
The params parameter returns one value: the maximum supported depth of the texture matrix stack. See glPushMatrix.
GL_MAX_VIEWPORT_DIMS
The params parameter returns two values: the maximum supported width and height of the viewport. See glViewport.
GL_MODELVIEW_MATRIX
The params parameter returns 16 values: the modelview matrix on the top of the modelview matrix stack. See glPushMatrix.
GL_MODELVIEW_STACK_DEPTH
The params parameter returns one value: the number of matrices on the modelview matrix stack. See glPushMatrix.
GL_NAME_STACK_DEPTH
The params parameter returns one value: the number of names on the selection name stack. See glPushMatrix.
GL_NORMALIZE
The params parameter returns a single Boolean value indicating whether normals are automatically scaled to unit length after they have been transformed to eye coordinates. See glNormal.
GL_PACK_ALIGNMENT
The params parameter returns one value: the byte alignment used for writing pixel data to memory. See glPixelStore.
GL_PACK_LSB_FIRST
The params parameter returns a single Boolean value indicating whether single-bit pixels being written to memory are written first to the least significant bit of each unsigned byte. See glPixelStore.
GL_PACK_ROW_LENGTH
The params parameter returns one value: the row length used for writing pixel data to memory. See glPixelStore.
GL_PACK_SKIP_PIXELS
The params parameter returns one value: the number of pixel locations skipped before the first pixel is written into memory. See glPixelStore.
GL_PACK_SKIP_ROWS
The params parameter returns one value: the number of rows of pixel locations skipped before the first pixel is written into memory. See glPixelStore.
GL_PACK_SWAP_BYTES
The params parameter returns a single Boolean value indicating whether the bytes of 2-byte and 4-byte pixel indexes and components are swapped before being written to memory. See glPixelStore.
GL_PERSPECTIVE_CORRECTION_HINT
The params parameter returns one value: a symbolic constant indicating the mode of the perspective correction hint. See glHint.
GL_PIXEL_MAP_A_TO_A_SIZE
The params parameter returns one value: the size of the alpha-to-alpha pixel-translation table. See glPixelMap.
GL_PIXEL_MAP_B_TO_B_SIZE
The params parameter returns one value: the size of the blue-to-blue pixel-translation table. See glPixelMap.
GL_PIXEL_MAP_G_TO_G_SIZE
The params parameter returns one value: the size of the green-to-green pixel-translation table. See glPixelMap.
GL_PIXEL_MAP_I_TO_A_SIZE
The params parameter returns one value: the size of the index-to-alpha pixel translation table. See glPixelMap.
GL_PIXEL_MAP_I_TO_B_SIZE
The params parameter returns one value: the size of the index-to-blue pixel translation table. See glPixelMap.
GL_PIXEL_MAP_I_TO_G_SIZE
The params parameter returns one value: the size of the index-to-green pixel-translation table. See glPixelMap.
GL_PIXEL_MAP_I_TO_I_SIZE
The params parameter returns one value: the size of the index-to-index pixel-translation table. See glPixelMap.
GL_PIXEL_MAP_I_TO_R_SIZE
The params parameter returns one value: the size of the index-to-red pixel-translation table. See glPixelMap.
GL_PIXEL_MAP_R_TO_R_SIZE
The params parameter returns one value: the size of the red-to-red pixel-translation table. See glPixelMap.
GL_PIXEL_MAP_S_TO_S_SIZE
The params parameter returns one value: the size of the stencil-to-stencil pixel translation table. See glPixelMap.
GL_POINT_SIZE
The params parameter returns one value: the point size as specified by glPointSize.
GL_POINT_SIZE_GRANULARITY
The params parameter returns one value: the size difference between adjacent supported sizes for antialiased points. See glPointSize.
GL_POINT_SIZE_RANGE
The params parameter returns two values: the smallest and largest supported sizes for antialiased points. See glPointSize.
GL_POINT_SMOOTH
The params parameter returns a single Boolean value indicating whether antialiasing of points is enabled. See glPointSize.
GL_POINT_SMOOTH_HINT
The params parameter returns one value: a symbolic constant indicating the mode of the point antialiasing hint. See glHint.
GL_POLYGON_MODE
The params parameter returns two values: symbolic constants indicating whether front-facing and back-facing polygons are rasterized as points, lines, or filled polygons. See glPolygonMode.
GL_POLYGON_SMOOTH
The params parameter returns a single Boolean value indicating whether antialiasing of polygons is enabled. See glPolygonMode.
GL_POLYGON_SMOOTH_HINT
The params parameter returns one value: a symbolic constant indicating the mode of the polygon antialiasing hint. See glHint.
GL_POLYGON_STIPPLE
The params parameter returns a single Boolean value indicating whether stippling of polygons is enabled. See glPolygonStipple.
GL_PROJECTION_MATRIX
The params parameter returns 16 values: the projection matrix on the top of the projection matrix stack. See glPushMatrix.
GL_PROJECTION_STACK_DEPTH
The params parameter returns one value: the number of matrices on the projection matrix stack. See glPushMatrix.
GL_READ_BUFFER
The params parameter returns one value: a symbolic constant indicating which color buffer is selected for reading. See glReadPixels and glAccum.
GL_RED_BIAS
The params parameter returns one value: the red bias factor used during pixel transfers. See glPixelTransfer.
GL_RED_BITS
The params parameter returns one value: the number of red bitplanes in each color buffer.
GL_RED_SCALE
The params parameter returns one value: the red scale factor used during pixel transfers. See glPixelTransfer.
GL_RENDER_MODE
The params parameter returns one value: a symbolic constant indicating whether OpenGL is in render, select, or feedback mode. See glRenderMode.
GL_RGBA_MODE
The params parameter returns a single Boolean value indicating whether OpenGL is in RGBA mode (TRUE) or color-index mode (FALSE). See glColor.
GL_SCISSOR_BOX
The params parameter returns four values: the x and y window coordinates of the scissor box, followed by its width and height. See glScissor.
GL_SCISSOR_TEST
The params parameter returns a single Boolean value indicating whether scissoring is enabled. See glScissor.
GL_SHADE_MODEL
The params parameter returns one value: a symbolic constant indicating whether the shading mode is flat or smooth. See glShadeModel.
GL_STENCIL_BITS
The params parameter returns one value: the number of bitplanes in the stencil buffer.
GL_STENCIL_CLEAR_VALUE
The params parameter returns one value: the index to which the stencil bitplanes are cleared. See glClearStencil.
GL_STENCIL_FAIL
The params parameter returns one value: a symbolic constant indicating what action is taken when the stencil test fails. See glStencilOp.
GL_STENCIL_FUNC
The params parameter returns one value: a symbolic constant indicating what function is used to compare the stencil reference value with the stencil buffer value. See glStencilFunc.
GL_STENCIL_PASS_DEPTH_FAIL
The params parameter returns one value: a symbolic constant indicating what action is taken when the stencil test passes, but the depth test fails. See glStencilOp.
GL_STENCIL_PASS_DEPTH_PASS
The params parameter returns one value: a symbolic constant indicating what action is taken when the stencil test passes and the depth test passes. See glStencilOp.
GL_STENCIL_REF
The params parameter returns one value: the reference value that is compared with the contents of the stencil buffer. See glStencilFunc.
GL_STENCIL_TEST
The params parameter returns a single Boolean value indicating whether stencil testing of fragments is enabled. See glStencilFunc and glStencilOp.
GL_STENCIL_VALUE_MASK
The params parameter returns one value: the mask that is used to mask both the stencil reference value and the stencil buffer value before they are compared. See glStencilFunc.
GL_STENCIL_WRITEMASK
The params parameter returns one value: the mask that controls writing of the stencil bitplanes. See glStencilMask.
GL_STEREO
The params parameter returns a single Boolean value indicating whether stereo buffers (left and right) are supported.
GL_SUBPIXEL_BITS
The params parameter returns one value: an estimate of the number of bits of subpixel resolution that are used to position rasterized geometry in window coordinates.
GL_TEXTURE_1D
The params parameter returns a single Boolean value indicating whether 1-D texture mapping is enabled. See glTexImage1D.
GL_TEXTURE_2D
The params parameter returns a single Boolean value indicating whether 2-D texture mapping is enabled. See glTexImage2D.
GL_TEXTURE_ENV_COLOR
The params parameter returns four values: the red, green, blue, and alpha values of the texture environment color. Integer values, if requested, are linearly mapped from the internal floating-point representation such that 1.0 returns the most positive representable integer value, and –1.0 returns the most negative representable integer value. See glTexEnv.
GL_TEXTURE_ENV_MODE
The params parameter returns one value: a symbolic constant indicating which texture environment function is currently selected. See glTexEnv.
GL_TEXTURE_GEN_S
The params parameter returns a single Boolean value indicating whether automatic generation of the S texture coordinate is enabled. See glTexGen.
GL_TEXTURE_GEN_T
The params parameter returns a single Boolean value indicating whether automatic generation of the T texture coordinate is enabled. See glTexGen.
GL_TEXTURE_GEN_R
The params parameter returns a single Boolean value indicating whether automatic generation of the R texture coordinate is enabled. See glTexGen.
GL_TEXTURE_GEN_Q
The params parameter returns a single Boolean value indicating whether automatic generation of the Q texture coordinate is enabled. See glTexGen.
GL_TEXTURE_MATRIX
The params parameter returns 16 values: the texture matrix on the top of the texture matrix stack. See glPushMatrix.
GL_TEXTURE_STACK_DEPTH
The params parameter returns one value: the number of matrices on the texture matrix stack. See glPushMatrix.
GL_UNPACK_ALIGNMENT
The params parameter returns one value: the byte alignment used for reading pixel data from memory. See glPixelStore.
GL_UNPACK_LSB_FIRST
The params parameter returns a single Boolean value indicating whether single-bit pixels being read from memory are read first from the least significant bit of each unsigned byte. See glPixelStore.
GL_UNPACK_ROW_LENGTH
The params parameter returns one value: the row length used for reading pixel data from memory. See glPixelStore.
GL_UNPACK_SKIP_PIXELS
The params parameter returns one value: the number of pixel locations skipped before the first pixel is read from memory. See glPixelStore.
GL_UNPACK_SKIP_ROWS
The params parameter returns one value: the number of rows of pixel locations skipped before the first pixel is read from memory. See glPixelStore.
GL_UNPACK_SWAP_BYTES
The params parameter returns a single Boolean value indicating whether the bytes of 2-byte and 4-byte pixel indexes and components are swapped after being read from memory. See glPixelStore.
GL_VIEWPORT
The params parameter returns four values: the x and y window coordinates of the viewport, followed by its width and height. See glViewport.
GL_ZOOM_X
The params parameter returns one value: the x pixel zoom factor. See glPixelZoom.
GL_ZOOM_Y
The params parameter returns one value: the y pixel zoom factor. See glPixelZoom.
params
Returns the value or values of the specified parameter.

Remarks

These four functions return values for simple state variables in OpenGL. The pname parameter is a symbolic constant indicating the state variable to be returned, and params is a pointer to an array of the indicated type in which to place the returned data.

Type conversion is performed if params has a different type from the state variable value being requested. If you call glGetBooleanv, a floating-point or integer value is converted to GL_FALSE if and only if it is zero. Otherwise, it is converted to GL_TRUE.

If you call glGetIntegerv, Boolean values are returned as GL_TRUE or GL_FALSE, and most floating-point values are rounded to the nearest integer value. Floating-point colors and normals, however, are returned with a linear mapping that maps 1.0 to the most positive representable integer value and –1.0 to the most negative representable integer value.

If you call glGetFloatv or glGetDoublev, Boolean values are returned as GL_TRUE or GL_FALSE, and integer values are converted to floating-point values.

You can query many of the Boolean parameters more easily with glIsEnabled.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | pname was not an accepted value.
GL_INVALID_OPERATION | glGet was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glAccum, glAlphaFunc, glBegin, glBlendFunc, glCallList, glClearAccum, glClearColor, glClearDepth, glClearIndex, glClearStencil, glClipPlane, glColor, glColorMask, glColorMaterial, glCullFace, glDepthFunc, glDepthMask, glDepthRange, glDrawBuffer, glEdgeFlag, glEnd, glFog, glFrontFace, glGetClipPlane, glGetError, glGetLight, glGetMap, glGetMaterial, glGetPixelMap, glGetPolygonStipple, glGetString, glGetTexEnv, glGetTexGen, glGetTexImage, glGetTexLevelParameter, glGetTexParameter, glHint, glIndex, glIndexMask, glIsEnabled, glLight, glLightModel, glLineStipple, glLineWidth, glListBase, glLogicOp, glMap1, glMap2, glMapGrid, glMatrixMode, glNewList, glNormal, glPixelMap, glPixelStore, glPixelTransfer, glPixelZoom, glPointSize, glPolygonMode, glPolygonStipple, glPushAttrib, glPushMatrix, glPushName, glRasterPos, glReadPixels, glScissor, glShadeModel, glStencilFunc, glStencilMask, glStencilOp, glTexCoord, glTexEnv, glTexGen, glTexImage1D, glTexImage2D, glViewport


glGetClipPlane

The glGetClipPlane function returns the coefficients of the specified clipping plane.

void glGetClipPlane(
  GLenum plane,
  GLdouble * equation
);

Parameters

plane
A clipping plane. The number of clipping planes depends on the implementation, but at least six clipping planes are supported. They are identified by symbolic names of the form GL_CLIP_PLANEi where 0 ≤ i < GL_MAX_CLIP_PLANES.
equation
Returns four double-precision values that are the coefficients of the plane equation of plane in eye coordinates.

Remarks

The glGetClipPlane function returns in equation the four coefficients of the plane equation for plane.

It is always the case that GL_CLIP_PLANEi = GL_CLIP_PLANE0 + i.

If an error is generated, no change is made to the contents of equation.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | plane was not an accepted value.
GL_INVALID_OPERATION | glGetClipPlane was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glClipPlane, glEnd


glGetColorTableEXT

The glGetColorTableEXT function gets the color table data of the current targeted texture palette.

void glGetColorTableEXT(
  GLenum target,
  GLenum format,
  GLenum type,
  const GLvoid * data
);

Parameters

target
The target texture that is to have its palette changed. Must be TEXTURE_1D or TEXTURE_2D.
format
The format of the pixel data. The following symbolic constants are accepted:
GL_RGBA
Each pixel is a group of four components in the following order: red, green, blue, alpha. The RGBA format is determined in this way:

1.The glGetColorTableEXT function converts floating-point values directly to an internal format with unspecified precision. Signed integer values are mapped linearly to the internal format such that the most positive representable integer value maps to 1.0, and the most negative representable integer value maps to – 1.0. Unsigned integer data is mapped similarly: the largest integer value maps to 1.0, and zero maps to 0.0.

2.The glGetColorTableEXT function multiplies the resulting color values by GL_c_SCALE and adds them to GL_c_BIAS, where c is RED, GREEN, BLUE, and ALPHA for the respective color components. The results are clamped to the range [0,1].

3.If GL_MAP_COLOR is TRUE, glGetColorTableEXT scales each color component by the size of lookup table GL_PIXEL_MAP_c_TO_c, then replaces the component by the value that it references in that table; c is R, G, B, or A, respectively.

4.The glGetColorTableEXT function converts the resulting RGBA colors to fragments by attaching the current raster position z-coordinate and texture coordinates to each pixel, then assigning x and y window coordinates to the nth fragment such that

x (n) = x (r) + n mod width

y (n) = y (r) + ë n/width û

where (x (r) , y (r) ) is the current raster position.

5.These pixel fragments are then treated just like the fragments generated by rasterizing points, lines, or polygons. The glGetColorTableEXT function applies texture mapping, fog, and all the fragment operations before writing the fragments to the frame buffer.

GL_RED
Each pixel is a single red component.

The glGetColorTableEXT function converts this component to the internal format in the same way that the red component of an RGBA pixel is, then converts it to an RGBA pixel with green and blue set to 0.0, and alpha set to 1.0. After this conversion, the pixel is treated just as if it had been read as an RGBA pixel.

GL_GREEN
Each pixel is a single green component.

The glGetColorTableEXT function converts this component to the internal format in the same way that the green component of an RGBA pixel is, and then converts it to an RGBA pixel with red and blue set to 0.0, and alpha set to 1.0. After this conversion, the pixel is treated just as if it had been read as an RGBA pixel.

GL_BLUE
Each pixel is a single blue component.

The glGetColorTableEXT function converts this component to the internal format in the same way that the blue component of an RGBA pixel is, and then converts it to an RGBA pixel with red and green set to 0.0, and alpha set to 1.0. After this conversion, the pixel is treated just as if it had been read as an RGBA pixel.

GL_ALPHA
Each pixel is a single alpha component.

The glGetColorTableEXT function converts this component to the internal format in the same way that the alpha component of an RGBA pixel is, and then converts it to an RGBA pixel with red, green, and blue set to 0.0. After this conversion, the pixel is treated just as if it had been read as an RGBA pixel.

GL_RGB
Each pixel is a group of three components in this order: red, green, blue.

The glGetColorTableEXT function converts each component to the internal format in the same way that the red, green, and blue components of an RGBA pixel are. The color triple is converted to an RGBA pixel with alpha set to 1.0. After this conversion, the pixel is treated just as if it had been read as an RGBA pixel.

GL_BGR_EXT
Each pixel is a group of three components in this order: blue, green, red.

GL_BGR_EXT provides a format that matches the memory layout of Windows device-independent bitmaps (DIBs). Thus your applications can use the same data with Win32 function calls and OpenGL pixel function calls.

GL_BGRA_EXT
Each pixel is a group of four components in this order: blue, green, red, alpha.

GL_BGRA_EXT provides a format that matches the memory layout of Windows device-independent bitmaps (DIBs). Thus your applications can use the same data with Win32 function calls and OpenGL pixel function calls.

type
The data type for data. The following symbolic constants are accepted: GL_UNSIGNED_BYTE, GL_BYTE, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, and GL_FLOAT.

The following table summarizes the meaning of the valid constants for the type parameter.
Constant | Meaning
GL_UNSIGNED_BYTE | Unsigned 8-bit integer
GL_BYTE | Signed 8-bit integer
GL_UNSIGNED_SHORT | Unsigned 16-bit integer
GL_SHORT | Signed 16-bit integer
GL_UNSIGNED_INT | Unsigned 32-bit integer
GL_INT | 32-bit integer
GL_FLOAT | Single-precision floating-point value

data
Points to the location where returned color table information is to be stored. Each color table entry is stored as if it was a single pixel of a 1-D texture. Because all textures have a default palette, glGetColorTableEXT always returns palette information even if the texture data is not in a paletted format.

Remarks

The glGetColorTableEXT function gets the actual color table data specified by glColorTableEXT and glColorSubTableEXT.

Note The glGetColorTableEXT function is an extension function that is not part of the standard OpenGL library but is part of the GL_EXT_paletted_texture extension. To check whether your implementation of OpenGL supports glGetColorTableEXT, call glGetString (GL_EXTENSIONS). If it returns GL_EXT_paletted_texture, glGetColorTableEXT is supported. To obtain the function address of an extension function, call wglGetProcAddress.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | target, format, or type was not an accepted value.
GL_INVALID_OPERATION | glGetColorTableEXT was called between glBegin and glEnd pairs.

See Also

glColorSubTableEXT, glColorTableEXT, glGetColorTableParameterfvEXT, glGetColorTableParameterivEXT, wglGetProcAddress


glGetColorTableParameterfvEXT, glGetColorTableParameterivEXT

The glGetColorTableParameterfvEXT and glGetColorTableParameterivEXT functions get palette parameters from color tables.

void glGetColorTableParameterfvEXT(
  GLenum target,
  GLenum pname,
  GLint * params
);

void glGetColorTableParameterivEXT(
  GLenum target,
  GLenum pname,
  GLint * params
);

Parameters

target
The target texture of the palette for which you want parameter data. Must be TEXTURE_1D, TEXTURE_2D, PROXY_TEXTURE_1D, or PROXY_TEXTURE_2D.
pname
A symbolic constant for the type of palette parameter data pointed to by params.

The following table summarizes the meaning of the valid constants for the pname parameter.
Constant | Meaning
GL_COLOR_TABLE_FORMAT_EXT | Return the internal format specified by the most recent call to glColorTableEXT or the default value.
GL_COLOR_TABLE_WIDTH_EXT | Return the width of the current palette.
GL_COLOR_TABLE_RED_SIZE_EXT | Return the actual size used internally to store the red component of the palette data.
GL_COLOR_TABLE_GREEN_SIZE_EXT | Return the actual size used internally to store the green component of the palette data.
GL_COLOR_TABLE_BLUE_SIZE_EXT | Return the actual size used internally to store the blue component of the palette data.
GL_COLOR_TABLE_ALPHA_SIZE_EXT | Return the actual size used internally to store the alpha component of the palette data.

params
Points to the color table parameter data specified by the pname parameter.

Remarks

You use the glGetColorTableParameterivEXT and glGetColorTableParameterfvEXT functions to retrieve specific parameter data from color tables set with glColorTableEXT for targeted texture palettes. Also you can use these functions to determine the number of color table entries that glGetColorTableEXT returns.

When the target parameter is GL_PROXY_TEXTURE_1D or GL_PROXY_TEXTURE_2D, and the implementation does not support the values specified for either format or width, glColorTableEXT can fail to create the requested color table. In this case, the color table is empty and all parameters retrieved will be zero. You can determine whether OpenGL supports a particular color table format and size by calling glColorTableEXT with a proxy target, and then calling glGetColorTableParameterivEXT or glGetColorTableParameterfvEXT to determine whether the width parameter matches that set by glColorTableEXT. If the retrieved width is zero, the color table request by glColorTable failed. If the retrieved width is not zero, you can call glColorTable with the real target with TEXTURE_1D or TEXTURE_2D to set the color table.

Note The glGetColorTableParameterivEXT and glGetColorTableParameterfvEXT functions are extension functions that are not part of the standard OpenGL library but are part of the GL_EXT_paletted_texture extension. To check whether your implementation of OpenGL supports glGetColorTableParameterivEXT and glGetColorTableParameterfvEXT, call glGetString (GL_EXTENSIONS). If it returns GL_EXT_paletted_texture, glGetColorTableParameterivEXT and glGetColorTableParameterfvEXT are supported. To obtain the function address of an extension function, call wglGetProcAddress.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | target or pname was not an accepted value.
GL_INVALID_OPERATION | glGetColorTableParameterivEXT or glGetColorTableParameterfvEXT was called between glBegin and glEnd pairs.

See Also

glColorSubTableEXT, glColorTableEXT, glGetColorTableEXT, wglGetProcAddress


glGetError

The glGetError function returns error information.

GLenum glGetError(
  void
);

Remarks

The glGetError function returns the value of the error flag. Each detectable error is assigned a numeric code and symbolic name. When an error occurs, the error flag is set to the appropriate error code value. No other errors are recorded until glGetError is called, the error code is returned, and the flag is reset to GL_NO_ERROR. If a call to glGetError returns GL_NO_ERROR, there has been no detectable error since the last call to glGetError, or since OpenGL was initialized.

To allow for distributed implementations, there may be several error flags. If any single error flag has recorded an error, the value of that flag is returned and that flag is reset to GL_NO_ERROR when glGetError is called. If more than one flag has recorded an error, glGetError returns and clears an arbitrary error flag value. If all error flags are to be reset, you should always call glGetError in a loop until it returns GL_NO_ERROR.

Initially, all error flags are set to GL_NO_ERROR.

The following are the currently defined errors:

GL_NO_ERROR
No error has been recorded. The value of this symbolic constant is guaranteed to be zero.
GL_INVALID_ENUM
An unacceptable value is specified for an enumerated argument. The offending function is ignored, having no side effect other than to set the error flag.
GL_INVALID_VALUE
A numeric argument is out of range. The offending function is ignored, having no side effect other than to set the error flag.
GL_INVALID_OPERATION
The specified operation is not allowed in the current state. The offending function is ignored, having no side effect other than to set the error flag.
GL_STACK_OVERFLOW
This function would cause a stack overflow. The offending function is ignored, having no side effect other than to set the error flag.
GL_STACK_UNDERFLOW
This function would cause a stack underflow. The offending function is ignored, having no side effect other than to set the error flag.
GL_OUT_OF_MEMORY
There is not enough memory left to execute the function. The state of OpenGL is undefined, except for the state of the error flags, after this error is recorded.

When an error flag is set, results of an OpenGL operation are undefined only if GL_OUT_OF_MEMORY has occurred. In all other cases, the function generating the error is ignored and has no effect on the OpenGL state or frame buffer contents.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glGetError was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd


glGetLight

glGetLightfv, glGetLightiv

The glGetLightfv and glGetLightiv functions return light source parameter values.

void glGetLightfv(
  GLenum light,
  GLenum pname,
  GLfloat * params
);

void glGetLightiv(
  GLenum light,
  GLenum pname,
  GLint * params
);

Parameters

light
A light source. The number of possible lights depends on the implementation, but at least eight lights are supported. They are identified by symbolic names of the form GL_LIGHTi where 0 ≤ i < GL_MAX_LIGHTS.
pname
A light source parameter for light. The following symbolic names are accepted:
GL_AMBIENT
The params parameter returns four integer or floating-point values representing the ambient intensity of the light source. Integer values, when requested, are linearly mapped from the internal floating-point representation such that 1.0 maps to the most positive representable integer value, and –1.0 maps to the most negative representable integer value. If the internal value is outside the range [–1,1], the corresponding integer return value is undefined.
GL_DIFFUSE
The params parameter returns four integer or floating-point values representing the diffuse intensity of the light source. Integer values, when requested, are linearly mapped from the internal floating-point representation such that 1.0 maps to the most positive representable integer value, and –1.0 maps to the most negative representable integer value. If the internal value is outside the range [–1,1], the corresponding integer return value is undefined.
GL_SPECULAR
The params parameter returns four integer or floating-point values representing the specular intensity of the light source. Integer values, when requested, are linearly mapped from the internal floating-point representation such that 1.0 maps to the most positive representable integer value, and –1.0 maps to the most negative representable integer value. If the internal value is outside the range [–1,1], the corresponding integer return value is undefined.
GL_POSITION
The params parameter returns four integer or floating-point values representing the position of the light source. Integer values, when requested, are computed by rounding the internal floating-point values to the nearest integer value. The returned values are those maintained in eye coordinates. They will not be equal to the values specified using glLight, unless the modelview matrix was identified at the time glLight was called.
GL_SPOT_DIRECTION
The params parameter returns three integer or floating-point values representing the direction of the light source. Integer values, when requested, are computed by rounding the internal floating-point values to the nearest integer value. The returned values are those maintained in eye coordinates. They will not be equal to the values specified using glLight, unless the modelview matrix was identified at the time glLight was called. Although spot direction is normalized before being used in the lighting equation, the returned values are the transformed versions of the specified values prior to normalization.
GL_SPOT_EXPONENT
The params parameter returns a single integer or floating-point value representing the spot exponent of the light. An integer value, when requested, is computed by rounding the internal floating-point representation to the nearest integer.
GL_SPOT_CUTOFF
The params parameter returns a single integer or floating-point value representing the spot cutoff angle of the light. An integer value, when requested, is computed by rounding the internal floating-point representation to the nearest integer.
GL_CONSTANT_ATTENUATION
The params parameter returns a single integer or floating-point value representing the constant (not distance-related) attenuation of the light. An integer value, when requested, is computed by rounding the internal floating-point representation to the nearest integer.
GL_LINEAR_ATTENUATION
The params parameter returns a single integer or floating-point value representing the linear attenuation of the light. An integer value, when requested, is computed by rounding the internal floating-point representation to the nearest integer.
GL_QUADRATIC_ATTENUATION
The params parameter returns a single integer or floating-point value representing the quadratic attenuation of the light. An integer value, when requested, is computed by rounding the internal floating-point representation to the nearest integer.
params
Returns the requested data.

Remarks

The glGetLight function returns in params the value or values of a light source parameter. The light parameter names the light and is a symbolic name of the form GL_LIGHTi for 0 ≤ i < GL_MAX_LIGHTS, where GL_MAX_LIGHTS is an implementation-dependent constant that is greater than or equal to eight. The pname parameter specifies one of ten light source parameters, again by symbolic name.

It is always the case that GL_LIGHTi = GL_LIGHT0 + i.

If an error is generated, no change is made to the contents of params.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | light or pname was not an accepted value.
GL_INVALID_OPERATION | glGetLight was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glLight


glGetMap

glGetMapdv, glGetMapfv, glGetMapiv

These functions return evaluator parameters.

void glGetMapdv(
  GLenum target,
  GLenum query,
  GLdouble * v
);

void glGetMapfv(
  GLenum target,
  GLenum query,
  GLfloat * v
);

void glGetMapiv(
  GLenum target,
  GLenum query,
  GLint * v
);

Parameters

target
The symbolic name of a map. The following are accepted values: GL_MAP1_COLOR_4, GL_MAP1_INDEX, GL_MAP1_NORMAL, GL_MAP1_TEXTURE_COORD_1, GL_MAP1_TEXTURE_COORD_2, GL_MAP1_TEXTURE_COORD_3, GL_MAP1_TEXTURE_COORD_4, GL_MAP1_VERTEX_3, GL_MAP1_VERTEX_4, GL_MAP2_COLOR_4, GL_MAP2_INDEX, GL_MAP2_NORMAL, GL_MAP2_TEXTURE_COORD_1, GL_MAP2_TEXTURE_COORD_2, GL_MAP2_TEXTURE_COORD_3, GL_MAP2_TEXTURE_COORD_4, GL_MAP2_VERTEX_3, and GL_MAP2_VERTEX_4.
query
Specifies which parameter to return. The following symbolic names are accepted:
GL_COEFF
The v parameter returns the control points for the evaluator function. One-dimensional evaluators return order control points, and two-dimensional evaluators return uorderxvorder control points. Each control point consists of one, two, three, or four integer, single-precision floating-point, or double-precision floating-point values, depending on the type of the evaluator. Two-dimensional control points are returned in row-major order, incrementing the uorder index quickly, and the vorder index after each row. Integer values, when requested, are computed by rounding the internal floating-point values to the nearest integer values.
GL_ORDER
The v parameter returns the order of the evaluator function. One-dimensional evaluators return a single value, order. Two-dimensional evaluators return two values, uorder and vorder.
GL_DOMAIN
The v parameter returns the linear u and v mapping parameters. One-dimensional evaluators return two values, u1 and u2, as specified by glMap1. Two-dimensional evaluators return four values (u1, u2, v1, and v2) as specified by glMap2. Integer values, when requested, are computed by rounding the internal floating-point values to the nearest integer values.
v
Returns the requested data.

Remarks

The glGetMap function returns evaluator parameters. (The glMap1 and glMap2 functions define evaluators.) The target parameter specifies a map, query selects a specific parameter, and v points to storage where the values will be returned.

The acceptable values for the target parameter are described in glMap1 and glMap2

If an error is generated, no change is made to the contents of v.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | target or query was not an accepted value.
GL_INVALID_OPERATION | glGetMap was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glEvalCoord, glMap1, glMap2


glGetMaterial

glGetMaterialfv, glGetMaterialiv

The glGetMaterialfv and glGetMaterialiv functions return material parameters.

void glGetMaterialfv(
  GLenum face,
  GLenum pname,
  GLfloat * params
);

void glGetMaterialiv(
  GLenum face,
  GLenum pname,
  GLint * params
);

Parameters

face
Specifies which of the two materials is being queried. GL_FRONT or GL_BACK are accepted, representing the front and back materials, respectively.
pname
The material parameter to return. The following values are accepted:
GL_AMBIENT
The params parameter returns four integer or floating-point values representing the ambient reflectance of the material. Integer values, when requested, are linearly mapped from the internal floating-point representation such that 1.0 maps to the most positive representable integer value, and –1.0 maps to the most negative representable integer value. If the internal value is outside the range [–1,1], the corresponding integer return value is undefined.
GL_DIFFUSE
The params parameter returns four integer or floating-point values representing the diffuse reflectance of the material. Integer values, when requested, are linearly mapped from the internal floating-point representation such that 1.0 maps to the most positive representable integer value, and –1.0 maps to the most negative representable integer value. If the internal value is outside the range [–1,1], the corresponding integer return value is undefined.
GL_SPECULAR
The params parameter returns four integer or floating-point values representing the specular reflectance of the material. Integer values, when requested, are linearly mapped from the internal floating-point representation such that 1.0 maps to the most positive representable integer value, and –1.0 maps to the most negative representable integer value. If the internal value is outside the range [–1,1], the corresponding integer return value is undefined.
GL_EMISSION
The params parameter returns four integer or floating-point values representing the emitted light intensity of the material. Integer values, when requested, are linearly mapped from the internal floating-point representation such that 1.0 maps to the most positive representable integer value, and –1.0 maps to the most negative representable integer value. If the internal value is outside the range [–1,1], the corresponding integer return value is undefined.
GL_SHININESS
The params parameter returns one integer or floating-point value representing the specular exponent of the material. Integer values, when requested, are computed by rounding the internal floating-point value to the nearest integer value.
GL_COLOR_INDEXES
The params parameter returns three integer or floating-point values representing the ambient, diffuse, and specular indexes of the material. Use these indexes only for color-index lighting. (The other parameters are all used only for RGBA lighting.) Integer values, when requested, are computed by rounding the internal floating-point values to the nearest integer values.
params
Returns the requested data.

Remarks

The glGetMaterial function returns in params the value or values of parameter pname of material face.

If an error is generated, no change is made to the contents of params.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | face or pname was not an accepted value.
GL_INVALID_OPERATION | glGetMaterial was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glMaterial


glGetPixelMap

glGetPixelMapfv, glGetPixelMapuiv, glGetPixelMapusv

These functions return the specified pixel map.

void glGetPixelMapfv(
  GLenum map,
  GLfloat * values
);

void glGetPixelMapuiv(
  GLenum map,
  GLuint * values
);

void glGetPixelMapusv(
  GLenum map,
  GLushort * values
);

Parameters

map
The name of the pixel map to return. Accepted values are GL_PIXEL_MAP_I_TO_I, GL_PIXEL_MAP_S_TO_S, GL_PIXEL_MAP_I_TO_R, GL_PIXEL_MAP_I_TO_G, GL_PIXEL_MAP_I_TO_B, GL_PIXEL_MAP_I_TO_A, GL_PIXEL_MAP_R_TO_R, GL_PIXEL_MAP_G_TO_G, GL_PIXEL_MAP_B_TO_B, and GL_PIXEL_MAP_A_TO_A.
values
Returns the pixel map contents.

Remarks

See glPixelMap for a description of the acceptable values for the map parameter. The glGetPixelMap function returns in values the contents of the pixel map specified in map. Use pixel maps during the execution of glReadPixels, glDrawPixels, glCopyPixels, glTexImage1D, and glTexImage2D to map color indexes, stencil indexes, color components, and depth components to other values.

Unsigned integer values, if requested, are linearly mapped from the internal fixed or floating-point representation such that 1.0 maps to the largest representable integer value, and 0.0 maps to zero. Return unsigned integer values are undefined if the map value was not in the range [0,1].

To determine the required size of map, call glGet with the appropriate symbolic constant.

If an error is generated, no change is made to the contents of values.

The following functions retrieve information related to glGetPixelMap:

glGet with argument GL_PIXEL_MAP_I_TO_I_SIZE

glGet with argument GL_PIXEL_MAP_S_TO_S_SIZE

glGet with argument GL_PIXEL_MAP_I_TO_R_SIZE

glGet with argument GL_PIXEL_MAP_I_TO_G_SIZE

glGet with argument GL_PIXEL_MAP_I_TO_B_SIZE

glGet with argument GL_PIXEL_MAP_I_TO_A_SIZE

glGet with argument GL_PIXEL_MAP_R_TO_R_SIZE

glGet with argument GL_PIXEL_MAP_G_TO_G_SIZE

glGet with argument GL_PIXEL_MAP_B_TO_B_SIZE

glGet with argument GL_PIXEL_MAP_A_TO_A_SIZE

glGet with argument GL_MAX_PIXEL_MAP_TABLE

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | map was not an accepted value.
GL_INVALID_OPERATION | glGetPixelMap was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glCopyPixels, glDrawPixels, glEnd, glGet, glPixelMap, glPixelTransfer, glReadPixels, glTexImage1D, glTexImage2D


glGetPointerv

The glGetPointerv function returns the address of a vertex data array.

void glGetPointerv(
  GLenum pname,
  GLvoid * * params
);

Parameters

pname
The type of array pointer to return from the following symbolic constants: GL_VERTEX_ARRAY_POINTER, GL_NORMAL_ARRAY_POINTER, GL_COLOR_ARRAY_POINTER, GL_INDEX_ARRAY_POINTER, GL_TEXTURE_COORD_ARRAY_POINTER, and GL_EDGE_FLAG_ARRAY_POINTER.
params
Returns the value of the array pointer specified by pname.

Remarks

The glGetPointerv function returns array pointer information. The pname parameter is a symbolic constant specifying the kind of array pointer to return, and params is a pointer to a location to place the returned data.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | pname was not an accepted value.

See Also

glArrayElement, glColorPointer, glDrawArrays, glEdgeFlagPointer, glGetString, glIndexPointer, glNormalPointer, glTexCoordPointer, glVertexPointer


glGetPolygonStipple

The glGetPolygonStipple function returns the polygon stipple pattern.

void glGetPolygonStipple(
  GLubyte * mask
);

Parameters

mask
Returns the stipple pattern.

Remarks

The glGetPolygonStipple function returns to mask a 32x32 polygon stipple pattern. The pattern is packed into memory as if glReadPixels with both height and width of 32, type of GL_BITMAP, and format of GL_COLOR_INDEX were called, and the stipple pattern were stored in an internal 32x32 color-index buffer. Unlike glReadPixels, however, pixel-transfer operations (shift, offset, and pixel map) are not applied to the returned stipple image.

If an error is generated, no change is made to the contents of mask.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glGetPolygonStipple was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glPixelStore, glPixelTransfer, glPolygonStipple, glReadPixels


glGetString

The glGetString function returns a string describing the current OpenGL connection.

const GLubyte * glGetString(
  GLenum name
);

Parameters

name
One of the following symbolic constants:
GL_VENDOR
Returns the company responsible for this OpenGL implementation. This name does not change from release to release.
GL_RENDERER
Returns the name of the renderer. This name is typically specific to a particular configuration of a hardware platform. It does not change from release to release.
GL_VERSION
Returns a version or release number.
GL_EXTENSIONS
Returns a space-separated list of supported extensions to OpenGL.

Remarks

The glGetString function returns a pointer to a static string describing some aspect of the current OpenGL connection.

Because OpenGL does not include queries for the performance characteristics of an implementation, it is expected that some applications will be written to recognize known platforms and will modify their OpenGL usage based on known performance characteristics of these platforms. The strings GL_VENDOR and GL_RENDERER together uniquely specify a platform, and will not change from release to release. They should be used by such platform recognition algorithms.

The format and contents of the string that glGetString returns depend on the implementation, except that:

  • Extension names will not include space characters and will be separated by space characters in the GL_EXTENSIONS string.
  • All strings are null-terminated.

If an error is generated, glGetString returns zero.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | name was not an accepted value.
GL_INVALID_OPERATION | glGetString was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd


glGetTexEnv

glGetTexEnvfv

The glGetTexEnvfv and glGetTexEnviv functions return texture environment parameters.

void glGetTexEnvfv(
  GLenum target,
  GLenum pname,
  GLfloat * params
);

void glGetTexEnviv(
  GLenum target,
  GLenum pname,
  GLint * params
);

Parameters

target
A texture environment. Must be GL_TEXTURE_ENV.
pname
The symbolic name of a texture environment parameter. The following values are accepted:
GL_TEXTURE_ENV_MODE
The params parameter returns the single-valued texture environment mode, a symbolic constant.
GL_TEXTURE_ENV_COLOR
The params parameter returns four integer or floating-point values that are the texture environment color. Integer values, when requested, are linearly mapped from the internal floating-point representation such that 1.0 maps to the most positive representable integer, and –1.0 maps to the most negative representable integer.
params
Returns the requested data.

Remarks

The glGetTexEnv function returns in params selected values of a texture environment that was specified with glTexEnv. The target parameter specifies a texture environment. Currently, only one texture environment is defined and supported: GL_TEXTURE_ENV.

The pname parameter names a specific texture environment parameter.

If an error is generated, no change is made to the contents of params.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | target or pname was not an accepted value.
GL_INVALID_OPERATION | glGetTexEnv was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glTexEnv


glGetTexGen

glGetTexGendv, glGetTexGenfv, glGetTexGeniv

These functions return texture coordinate generation parameters.

void glGetTexGendv(
  GLenum coord,
  GLenum pname,
  GLdouble * params
);

void glGetTexGenfv(
  GLenum coord,
  GLenum pname,
  GLfloat * params
);

void glGetTexGeniv(
  GLenum coord,
  GLenum pname,
  GLint * params
);

Parameters

coord
A texture coordinate. Must be GL_S, GL_T, GL_R, or GL_Q.
pname
The symbolic name of the value(s) to be returned. Must be either GL_TEXTURE_GEN_MODE or the name of one of the texture generation plane equations: GL_OBJECT_PLANE or GL_EYE_PLANE. These values are as follows:
GL_TEXTURE_GEN_MODE
The params parameter returns the single-valued texture-generation function, a symbolic constant.
GL_OBJECT_PLANE
The params parameter returns the four plane equation coefficients that specify object linear-coordinate generation. Integer values, when requested, are mapped directly from the internal floating-point representation.
GL_EYE_PLANE
The params parameter returns the four plane equation coefficients that specify eye linear-coordinate generation. Integer values, when requested, are mapped directly from the internal floating-point representation. The returned values are those maintained in eye coordinates. They are not equal to the values specified using glTexGen, unless the modelview matrix was identified at the time glTexGen was called.
params
Returns the requested data.

Remarks

The glGetTexGen function returns in params selected parameters of a texture-coordinate generation function that you specified with glTexGen. The coord parameter names one of the (s,t,r,q) texture coordinates, using the symbolic constant GL_S, GL_T, GL_R, or GL_Q.

If an error is generated, no change is made to the contents of params.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | coord or pname was not an accepted value.
GL_INVALID_OPERATION | glGetTexGen was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glTexGen


glGetTexImage

The glGetTexImage function returns a texture image.

void glGetTexImage(
  GLenum target,
  GLint level,
  GLenum format,
  GLenum type,
  GLvoid * pixels
);

Parameters

target
Specifies which texture is to be obtained. GL_TEXTURE_1D and GL_TEXTURE_2D are accepted.
level
The level-of-detail number of the desired image. Level 0 is the base image level. Level n is the nth mipmap reduction image.
format
A pixel format for the returned data. The supported formats are GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA, GL_LUMINANCE, GL_BGR_EXT, GL_BGRA_EXT, and GL_LUMINANCE_ALPHA.
type
A pixel type for the returned data. The supported types are GL_UNSIGNED_BYTE, GL_BYTE, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, and GL_FLOAT.
pixels
Returns the texture image. Should be a pointer to an array of the type specified by type.

Remarks

The glGetTexImage function returns a texture image into pixels. The target parameter specifies whether the desired texture image is one specified by glTexImage1D(GL_TEXTURE_1D) or by glTexImage2D (GL_TEXTURE_2D). The level parameter specifies the level-of-detail number of the desired image. The format and type parameters specify the format and type of the desired image array. For a description of the acceptable values for the format and type parameters, respectively, see glTexImage1D and glDrawPixels.

Operation of glGetTexImage is best understood by considering the selected internal four-component texture image to be an RGBA color buffer the size of the image. The semantics of glGetTexImage are then identical to those of glReadPixels called with the same format and type, with x and y set to zero, width set to the width of the texture image (including border if one was specified), and height set to one for 1-D images, or to the height of the texture image (including border, if one was specified) for 2-D images.

Because the internal texture image is an RGBA image, pixel formats GL_COLOR_INDEX, GL_STENCIL_INDEX, and GL_DEPTH_COMPONENT are not accepted, and pixel type GL_BITMAP is not accepted.

If the selected texture image does not contain four components, the following mappings are applied. Single-component textures are treated as RGBA buffers with red set to the single-component value, and green, blue, and alpha set to zero.

Two-component textures are treated as RGBA buffers, with red set to the value of component zero, alpha set to the value of component one, and green and blue set to zero. Finally, three-component textures are treated as RGBA buffers with red set to component zero, green set to component one, blue set to component two, and alpha set to zero.

To determine the required size of pixels, use glGetTexLevelParameter to ascertain the dimensions of the internal texture image, and then scale the required number of pixels by the storage required for each pixel, based on format and type. Be sure to take the pixel-storage parameters into account, especially GL_PACK_ALIGNMENT.

If an error is generated, no change is made to the contents of pixels.

The following functions retrieve information related to glGetTexImage:

glGetTexLevelParameter with argument GL_TEXTURE_WIDTH

glGetTexLevelParameter with argument GL_TEXTURE_HEIGHT

glGetTexLevelParameter with argument GL_TEXTURE_BORDER

glGetTexLevelParameter with argument GL_TEXTURE_COMPONENTS

glGet with argument GL_PACK_ALIGNMENT and others

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | target, format, or type was not an accepted value.
GL_INVALID_VALUE | level is less than zero or greater than log (2) max, where max is the returned value of GL_MAX_TEXTURE_SIZE.
GL_INVALID_OPERATION | glGetTexImage was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glDrawPixels, glEnd, glGetTexLevelParameter, glReadPixels, glTexImage1D, glTexImage2D


glGetTexLevelParameter

glGetTexLevelParameterfv, glGetTexLevelParameteriv

The glGetTexLevelParameterfv and glGetTexLevelParameteriv functions return texture parameter values for a specific level of detail.

void glGetTexLevelParameterfv(
  GLenum target,
  GLint level,
  GLenum pname,
  GLfloat * params
);

void glGetTexLevelParameteriv(
  GLenum target,
  GLint level,
  GLenum pname,
  GLint * params
);

Parameters

target
The symbolic name of the target texture: either GL_TEXTURE_1D or GL_TEXTURE_2D.
level
The level-of-detail number of the desired image. Level 0 is the base image level. Level n is the nth mipmap reduction image.
pname
The symbolic name of a texture parameter. The following parameter names are accepted:
GL_TEXTURE_WIDTH
The params parameter returns a single value: the width of the texture image. This value includes the border of the texture image.
GL_TEXTURE_HEIGHT
The params parameter returns a single value: the height of the texture image. This value includes the border of the texture image.
GL_TEXTURE_COMPONENTS
The params parameter returns a single value: the number of components in the texture image.
GL_TEXTURE_BORDER
The params parameter returns a single value: the width in pixels of the border of the texture image.
params
Returns the requested data.

Remarks

The glGetTexLevelParameter function returns in params texture parameter values for a specific level-of-detail value, specified as level. The target parameter defines the target texture—either GL_TEXTURE_1D or GL_TEXTURE_2D—to specify one- or two-dimensional texturing. The pname parameter specifies the texture parameter whose value or values will be returned.

If an error is generated, no change is made to the contents of params.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | target or pname was not an accepted value.
GL_INVALID_VALUE | level is less than zero or greater than log (2) max, where max is the returned value of GL_MAX_TEXTURE_SIZE.
GL_INVALID_OPERATION | glGetTexLevelParameter was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glGetTexParameter, glTexImage1D, glTexImage2D, glTexParameter


glGetTexParameter

glGetTexParameterfv, glGetTexParameteriv

The glGetTexParameterfv and glGetTexParameteriv functions return texture parameter values.

void glGetTexParameterfv(
  GLenum target,
  GLenum pname,
  GLfloat * params
);

void glGetTexParameteriv(
  GLenum target,
  GLenum pname,
  GLint * params
);

Parameters

target
The symbolic name of the target texture. GL_TEXTURE_1D and GL_TEXTURE_2D are accepted.
pname
The symbolic name of a texture parameter. The following values are accepted:
GL_TEXTURE_MAG_FILTER
Returns the single-valued texture magnification filter, a symbolic constant.
GL_TEXTURE_MIN_FILTER
Returns the single-valued texture minification filter, a symbolic constant.
GL_TEXTURE_WRAP_S
Returns the single-valued wrapping function for texture coordinate s, a symbolic constant.
GL_TEXTURE_WRAP_T
Returns the single-valued wrapping function for texture coordinate t, a symbolic constant.
GL_TEXTURE_BORDER_COLOR
Returns four integer or floating-point numbers that comprise the RGBA color of the texture border. Floating-point values are returned in the range [0,1]. Integer values are returned as a linear mapping of the internal floating-point representation such that 1.0 maps to the most positive representable integer and –1.0 maps to the most negative representable integer.
params
Returns the texture parameters.

Remarks

The glGetTexParameter function returns in params the value or values of the texture parameter specified as pname. The target parameter defines the target texture—either GL_TEXTURE_1D or GL_TEXTURE_2D—to specify one- or two-dimensional texturing. The pname parameter accepts the same symbols as glTexParameter, with the same interpretations.

If an error is generated, no change is made to the contents of params.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | target or pname was not an accepted value.
GL_INVALID_OPERATION | glGetTexParameter was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glTexParameter


glHint

The glHint function specifies implementation-specific hints.

void glHint(
  GLenum target,
  GLenum mode
);

Parameters

target
A symbolic constant indicating the behavior to be controlled. The following symbolic constants, along with suggested semantics, are accepted:
GL_FOG_HINT
Indicates the accuracy of fog calculation. If per-pixel fog calculation is not efficiently supported by the OpenGL implementation, hinting GL_DONT_CARE or GL_FASTEST can result in per-vertex calculation of fog effects.
GL_LINE_SMOOTH_HINT
Indicates the sampling quality of antialiased lines. Hinting GL_NICEST can result in more pixel fragments being generated during rasterization, if a larger filter function is applied.
GL_PERSPECTIVE_CORRECTION_HINT
Indicates the quality of color and texture coordinate interpolation. If perspective-corrected parameter interpolation is not efficiently supported by the OpenGL implementation, hinting GL_DONT_CARE or GL_FASTEST can result in simple linear interpolation of colors and/or texture coordinates.
GL_POINT_SMOOTH_HINT
Indicates the sampling quality of antialiased points. Hinting GL_NICEST can result in more pixel fragments being generated during rasterization, if a larger filter function is applied.
GL_POLYGON_SMOOTH_HINT
Indicates the sampling quality of antialiased polygons. Hinting GL_NICEST can result in more pixel fragments being generated during rasterization, if a larger filter function is applied.
mode
A symbolic constant indicating the desired behavior. The following symbolic constants are accepted:
GL_FASTEST
The most efficient option should be chosen.
GL_NICEST
The most correct, or highest quality, option should be chosen.
GL_DONT_CARE
The client doesn't have a preference.

Remarks

When there is room for interpretation, you can control certain aspects of OpenGL behavior with hints. You specify a hint with two arguments. The target parameter is a symbolic constant indicating the behavior to be controlled, and mode is another symbolic constant indicating the desired behavior.

Though the implementation aspects that can be hinted are well defined, the interpretation of the hints depends on the implementation.

The glHint function can be ignored.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | target or mode was not an accepted value.
GL_INVALID_OPERATION | glHint was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd


glIndex

glIndexd, glIndexf, glIndexi, glIndexs, glIndexdv, glIndexfv, glIndexiv, glIndexsv

These functions set the current color index.

void glIndexd(
  GLdouble c
);

void glIndexf(
  GLfloat c
);

void glIndexi(
  GLint c
);

void glIndexs(
  GLshort c
);

Parameters

c
The new value for the current color index.
void glIndexdv(
  const GLdouble *c
);

void glIndexfv(
  const GLfloat *c
);

void glIndexiv(
  const GLint *c
);

void glIndexsv(
  const GLshort *c
);

Parameters

c
A pointer to a one-element array that contains the new value for the current color index.

Remarks

The glIndex function updates the current (single-valued) color index. It takes one argument: the new value for the current color index.

The current index is stored as a floating-point value. Integer values are converted directly to floating-point values, with no special mapping.

Index values outside the representable range of the color-index buffer are not clamped. However, before an index is dithered (if enabled) and written to the frame buffer, it is converted to fixed-point format. Any bits in the integer portion of the resulting fixed-point value that do not correspond to bits in the frame buffer are masked out.

The current index can be updated at any time. In particular, glIndex can be called between a call to glBegin and the corresponding call to glEnd.

The following function retrieves information related to glIndex:

glGet with argument GL_CURRENT_INDEX

See Also

glBegin, glColor, glEnd, glGet


glIndexMask

The glIndexMask function controls the writing of individual bits in the color-index buffers.

void glIndexMask(
  GLuint mask
);

Parameters

mask
A bit mask to enable and disable the writing of individual bits in the color-index buffers. Initially, the mask is all ones.

Remarks

The glIndexMask function controls the writing of individual bits in the color-index buffers. The least significant n bits of mask, where n is the number of bits in a color-index buffer, specify a mask. Wherever a one appears in the mask, the corresponding bit in the color-index buffer (or buffers) is made writable. Where a zero appears, the bit is write-protected.

This mask is used only in color-index mode, and it affects only the buffers currently selected for writing (see glDrawBuffer). Initially, all bits are enabled for writing.

The following function retrieves information related to glIndexMask:

glGet with argument GL_INDEX_WRITEMASK

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glIndexMask was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glDepthMask, glDrawBuffer, glEnd, glIndex, glStencilMask


glIndexPointer

The glIndexPointer function defines an array of color indexes.

void glIndexPointer(
  GLenum type,
  GLsizei stride,
  GLsizei count,
  const GLvoid *pointer
);

Parameters

type
The data type of each color index in the array using the following symbolic constants: GL_SHORT, GL_INT, GL_FLOAT, GL_DOUBLE.
stride
The byte offset between consecutive color indexes. When stride is zero, the color indexes are tightly packed in the array.
count
The number of color indexes, counting from the first, that are static.
pointer
A pointer to the first color index in the array.

Remarks

The glIndexPointer function specifies the location and data of an array of color indexes to use when rendering. The type parameter specifies the data type of each color index and stride determines the byte offset from one color index to the next, enabling the packing of vertices and attributes in a single array or storage in separate arrays. In some implementations storing the vertices and attributes in a single array can be more efficient than using separate arrays. Starting from the first color-index element, count indicates the total number of static elements. Your application can modify static elements, but once the elements are modified, the application must explicitly specify the array again before using the array for any rendering. Non-static array elements are not accessed until you call glDrawArrays or glArrayElement.

A color-index array is enabled when you specify the GL_INDEX_ARRAY constant with glEnableClientState. When enabled, glDrawArrays and glArrayElement use the color-index array. By default the color-index array is disabled.

You cannot include glIndexPointer in display lists.

When you specify a color-index array using glIndexPointer, the values of all the function's color-index array parameters are saved in a client-side state and static array elements can be cached. Because the color-index array parameters are client-side state, their values are not saved or restored by glPushAttrib and glPopAttrib.

Although no error is generated when you call glIndexPointer within glBegin and glEnd pairs, the results are undefined.

The following functions retrieve information related to glIndexPointer:

glIsEnabled with argument GL_INDEX_ARRAY

glGet with argument GL_INDEX_ARRAY_STRIDE

glGet with argument GL_INDEX_ARRAY_COUNT

glGet with argument GL_INDEX_ARRAY_TYPE

glGet with argument GL_INDEX_ARRAY_SIZE

glGetPointerv with argument GL_INDEX_ARRAY_POINTER

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | type was not an accepted value.
GL_INVALID_VALUE | stride or count was negative.

See Also

glArrayElement, glColorPointer, glDrawArrays, glEdgeFlagPointer, glGetPointerv, glGetString, glNormalPointer, glPushAttrib, glTexCoordPointer, glVertexPointer


glInitNames

The glInitNames function initializes the name stack.

void glInitNames(
  void
);

Remarks

The glInitNames function causes the name stack to be initialized to its default empty state. The name stack is used during selection mode to allow sets of rendering commands to be uniquely identified. It consists of an ordered set of unsigned integers.

The name stack is always empty while the render mode is not GL_SELECT. Calls to glInitNames while the render mode is not GL_SELECT are ignored.

The following functions retrieve information related to glInitNames:

glGet with argument GL_NAME_STACK_DEPTH

glGet with argument GL_MAX_NAME_STACK_DEPTH

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glInitNames was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glLoadName, glPushName, glRenderMode, glSelectBuffer


glInterleavedArrays

The glInterleavedArrays function simultaneously specifies and enables several interleaved arrays in a larger aggregate array.

void glInterleavedArrays(
  GLenum format,
  GLsizei stride,
  const GLvoid * pointer
);

Parameters

format
The type of array to enable. The parameter can assume one of the following symbolic values: GL_V2F, GL_V3F, GL_C4UB_V2F, GL_C4UB_V3F, GL_C3F_V3F, GL_N3F_V3F, GL_C4F_N3F_V3F, GL_T2F_V3F, GL_T4F_V4F, GL_T2F_C4UB_V3F, GL_T2F_C3F_V3F, GL_T2F_N3F_V3F, GL_T2F_C4F_N3F_V3F, or GL_T4F_C4F_N3F_V4F.
stride
The offset in bytes between each aggregate array element.
pointer
A pointer to the first element of an aggregate array.

Remarks

With the glInterleavedArrays function you can simultaneously specify and enable several interleaved color, normal, texture, and vertex arrays whose elements are part of a larger aggregate array element. For some memory architectures this is more efficient than specifying the arrays separately.

If the stride parameter is zero then the aggregate array elements are stored consecutively; otherwise stride bytes occur between aggregate array elements.

The format parameter serves as a "key" that describes how to extract individual arrays from the aggregate array:

  • If format contains a T, then texture coordinates are extracted from the interleaved array.
  • If C is present, color values are extracted.
  • If N is present, normal coordinates are extracted.
  • Vertex coordinates are always extracted.
  • The digits 2, 3, and 4 denote how many values are extracted.
  • F indicates that values are extracted as floating point values.
  • If 4UB follows the C, colors may also be extracted as 4 unsigned bytes. If a color is extracted as 4 unsigned bytes the vertex array element that follows is located at the first possible floating point aligned address.

If you call glInterleavedArrays while compiling a display list, it is not compiled into the list but is executed immediately.

You cannot include calls to glInterleavedArrays in glDisableClientState between calls to glBegin and the corresponding call to glEnd.

Note The glInterleavedArrays function is only available in OpenGL version 1.1 or later.

The glInterleavedArrays function is implemented on the client side with no protocol. Because the vertex array parameters are client-side state, they are not saved or restored by glPushAttrib and glPopAttrib. Use glPushClientAttrib and glPopClientAttrib instead.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | format was not an accepted value.
GL_INVALID_VALUE | stride was a negative value.
GL_INVALID_OPERATION | glInterleavedArrays was called between a call to glBegin and the corresponding call to glEnd.

See Also

glArrayElement, glColorPointer, glDrawArrays, glDrawElements, glEdgeFlagPointer, glEnableClientState, glGetPointerv, glIndexPointer, glNormalPointer, glPushAttrib, glPushClientAttrib, glTexCoordPointer, glVertexPointer


glIsEnabled

The gllsEnabled function tests whether a capability is enabled.

GLboolean glIsEnabled(
  GLenum cap
);

Parameters

cap
A symbolic constant indicating an OpenGL capability. The following capabilities are accepted:
GL_ALPHA_TEST | See glAlphaFunc
GL_AUTO_NORMAL | See glEvalCoord
GL_BLEND | See glBlendFunc
GL_CLIP_PLANEi | See glClipPlane
GL_COLOR_MATERIAL | See glColorMaterial
GL_CULL_FACE | See glCullFace
GL_DEPTH_TEST | See glDepthFunc and glDepthRange
GL_DITHER | See glEnable
GL_FOG | See glFog
GL_LIGHTi | See glLightModel and glLight
GL_LIGHTING | See glMaterial, glLightModel, and glLight
GL_LINE_SMOOTH | See glLineWidth
GL_LINE_STIPPLE | See glLineStipple
GL_LOGIC_OP | See glLogicOp
GL_MAP1_COLOR_4 | See glMap1
GL_MAP1_INDEX | See glMap1
GL_MAP1_NORMAL | See glMap1
GL_MAP1_TEXTURE_COORD_1 | See glMap1
GL_MAP1_TEXTURE_COORD_2 | See glMap1
GL_MAP1_TEXTURE_COORD_3 | See glMap1
GL_MAP1_TEXTURE_COORD_4 | See glMap1
GL_MAP1_VERTEX_3 | See glMap1
GL_MAP1_VERTEX_4 | See glMap1
GL_MAP2_COLOR_4 | See glMap2
GL_MAP2_INDEX | See glMap2
GL_MAP2_NORMAL | See glMap2
GL_MAP2_TEXTURE_COORD_1 | See glMap2
GL_MAP2_TEXTURE_COORD_2 | See glMap2
GL_MAP2_TEXTURE_COORD_3 | See glMap2
GL_MAP2_TEXTURE_COORD_4 | See glMap2
GL_MAP2_VERTEX_3 | See glMap2
GL_MAP2_VERTEX_4 | See glMap2
GL_NORMALIZE | See glNormal
GL_POINT_SMOOTH | See glPointSize
GL_POLYGON_SMOOTH | See glPolygonMode
GL_POLYGON_STIPPLE | See glPolygonStipple
GL_SCISSOR_TEST | See glScissor
GL_STENCIL_TEST | See glStencilFunc and glStencilOp
GL_TEXTURE_1D | See glTexImage1D
GL_TEXTURE_2D | See glTexImage2D
GL_TEXTURE_GEN_Q | See glTexGen
GL_TEXTURE_GEN_R | See glTexGen
GL_TEXTURE_GEN_S | See glTexGen
GL_TEXTURE_GEN_T | See glTexGen

Remarks

The gllsEnabled function returns GL_TRUE if cap is an enabled capability and returns GL_FALSE otherwise.

Error Codes

If an error is generated, glIsEnabled returns zero.

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | cap was not an accepted value.
GL_INVALID_OPERATION | glIsEnabled was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnable, glEnd


glIsList

The gllsList function tests for display list existence.

GLboolean glIsList(
  GLuint list
);

Parameters

list
A potential display list name.

Remarks

The gllsList function returns GL_TRUE if list is the name of a display list and returns GL_FALSE otherwise.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glIsList was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glCallList, glCallLists, glDeleteLists, glEnd, glGenLists, glNewList


glIsTexture

The glIsTexture function determines if a name corresponds to a texture.

GLboolean glIsTexture(
  GLuint texture
);

Parameters

texture
A value that is the name of a texture.

Remarks

If the texture parameter is currently the name of a texture, the glIsTexture function returns GL_TRUE. If texture is zero, is a non-zero value that is not currently the name of a texture, or if an error occurs, glIsTexture returns GL_FALSE.

You cannot include calls to glIsTexture in display lists.

Note The glIsTexture function is only available in OpenGL version 1.1 or later.

Error Codes

The following is the error code generated and its condition.

Error Code | Condition
GL_INVALID_OPERATION | glIsTexture was called between a call to glBegin and the corresponding call to glEnd.

See Also

glBegin, glBindTexture, glEnd, glGenTextures, glGet, glGetTexParameter, glTexImage1D, glTexImage2D, glTexParameter


glLight

glLightf, glLighti, glLightfv, glLightiv

These functions set light source parameters.

void glLightf(
  GLenum light,
  GLenum pname,
  GLfloat param
);

void glLighti(
  GLenum light,
  GLenum pname,
  GLint param
);

Parameters

light
A light. The number of lights depends on the implementation, but at least eight lights are supported. They are identified by symbolic names of the form GL_LIGHTi where 0 ≤ i < GL_MAX_LIGHTS.
pname
A single-valued light source parameter for light. The following values are accepted.
GL_SPOT_EXPONENT
The params parameter is a single integer or floating-point value that specifies the intensity distribution of the light. Integer and floating-point values are mapped directly. Only values in the range [0,128] are accepted.

Effective light intensity is attenuated by the cosine of the angle between the direction of the light and the direction from the light to the vertex being lighted, raised to the power of the spot exponent. Thus, higher spot exponents result in a more focused light source, regardless of the spot cutoff angle (see the following paragraph). The default spot exponent is 0, resulting in uniform light distribution.

GL_SPOT_CUTOFF
The params parameter is a single integer or floating-point value that specifies the maximum spread angle of a light source. Integer and floating-point values are mapped directly. Only values in the range [0,90], and the special value 180, are accepted. If the angle between the direction of the light and the direction from the light to the vertex being lighted is greater than the spot cutoff angle, then the light is completely masked. Otherwise, its intensity is controlled by the spot exponent and the attenuation factors. The default spot cutoff is 180, resulting in uniform light distribution.
GL_CONSTANT_ATTENUATION
GL_LINEAR_ATTENUATION
GL_QUADRATIC_ATTENUATION
The params parameter is a single integer or floating-point value that specifies one of the three light attenuation factors. Integer and floating-point values are mapped directly. Only nonnegative values are accepted. If the light is positional, rather than directional, its intensity is attenuated by the reciprocal of the sum of: the constant factor, the linear factor multiplied by the distance between the light and the vertex being lighted, and the quadratic factor multiplied by the square of the same distance. The default attenuation factors are (1,0,0), resulting in no attenuation.
param
The value to which parameter pname of light source light will be set.
void glLightfv(
  GLenum light,
  GLenum pname,
  const GLfloat *params
);

void glLightiv(
  GLenum light,
  GLenum pname,
  const GLint *params
);

Parameters

light
A light. The number of lights depends on the implementation, but at least eight lights are supported. They are identified by symbolic names of the form GL_LIGHTi where 0 ≤ i < GL_MAX_LIGHTS.
pname
A light source parameter for light. The following values are accepted:
GL_AMBIENT
The params parameter contains four integer or floating-point values that specify the ambient RGBA intensity of the light. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to –1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default ambient light intensity is (0.0, 0.0, 0.0, 1.0).
GL_DIFFUSE
The params parameter contains four integer or floating-point values that specify the diffuse RGBA intensity of the light. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to –1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default diffuse intensity is (0.0, 0.0, 0.0, 1.0) for all lights other than light zero. The default diffuse intensity of light zero is (1.0, 1.0, 1.0, 1.0).
GL_SPECULAR
The params parameter contains four integer or floating-point values that specify the specular RGBA intensity of the light. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to –1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default specular intensity is (0.0, 0.0, 0.0, 1.0) for all lights other than light zero. The default specular intensity of light zero is (1.0, 1.0, 1.0, 1.0).
GL_POSITION
The params parameter contains four integer or floating-point values that specify the position of the light in homogeneous object coordinates. Both integer and floating-point values are mapped directly. Neither integer nor floating-point values are clamped.

The position is transformed by the modelview matrix when glLight is called (just as if it were a point), and it is stored in eye coordinates. If the w component of the position is 0.0, the light is treated as a directional source. Diffuse and specular lighting calculations take the lights direction, but not its actual position, into account, and attenuation is disabled. Otherwise, diffuse and specular lighting calculations are based on the actual location of the light in eye coordinates, and attenuation is enabled. The default position is (0,0,1,0); thus, the default light source is directional, parallel to, and in the direction of the –z axis.

GL_SPOT_DIRECTION
The params parameter contains three integer or floating-point values that specify the direction of the light in homogeneous object coordinates. Both integer and floating-point values are mapped directly. Neither integer nor floating-point values are clamped.

The spot direction is transformed by the inverse of the modelview matrix when glLight is called (just as if it were a normal), and it is stored in eye coordinates. It is significant only when GL_SPOT_CUTOFF is not 180, which it is by default. The default direction is (0,0,–1).

GL_SPOT_EXPONENT
The params parameter is a single integer or floating-point value that specifies the intensity distribution of the light. Integer and floating-point values are mapped directly. Only values in the range [0,128] are accepted.

Effective light intensity is attenuated by the cosine of the angle between the direction of the light and the direction from the light to the vertex being lighted, raised to the power of the spot exponent. Thus, higher spot exponents result in a more focused light source, regardless of the spot cutoff angle (see the following paragraph). The default spot exponent is 0, resulting in uniform light distribution.

GL_SPOT_CUTOFF
The params parameter is a single integer or floating-point value that specifies the maximum spread angle of a light source. Integer and floating-point values are mapped directly. Only values in the range [0,90], and the special value 180, are accepted. If the angle between the direction of the light and the direction from the light to the vertex being lighted is greater than the spot cutoff angle, then the light is completely masked. Otherwise, its intensity is controlled by the spot exponent and the attenuation factors. The default spot cutoff is 180, resulting in uniform light distribution.
GL_CONSTANT_ATTENUATION
GL_LINEAR_ATTENUATION
GL_QUADRATIC_ATTENUATION
The params parameter is a single integer or floating-point value that specifies one of the three light attenuation factors. Integer and floating-point values are mapped directly. Only nonnegative values are accepted. If the light is positional, rather than directional, its intensity is attenuated by the reciprocal of the sum of: the constant factor, the linear factor multiplied by the distance between the light and the vertex being lighted, and the quadratic factor multiplied by the square of the same distance. The default attenuation factors are (1,0,0), resulting in no attenuation.
params
A pointer to the value or values to which parameter pname of light source light will be set.

Remarks

The glLight function sets the values of individual light source parameters. The light parameter names the light and is a symbolic name of the form:

GL_LIGHTi, where 0 ≤ i < GL_MAX_LIGHTS

The pname parameter specifies one of ten light source parameters, again by symbolic name. The params parameter is either a single value or a pointer to an array that contains the new values.

Lighting calculation is enabled and disabled using glEnable and glDisable with argument GL_LIGHTING. When lighting is enabled, light sources that are enabled contribute to the lighting calculation. Light source i is enabled and disabled using glEnable and glDisable with argument GL_LIGHTi.

It is always the case that GL_LIGHTi = GL_LIGHT0 + i.

The following functions retrieve information related to the glLight function:

glGetLight

glIsEnabled with argument GL_LIGHTING

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | light or pname was not an accepted value.
GL_INVALID_VALUE | a spot exponent value was specified outside the range [0,128], or if spot cutoff was specified outside the range [0,90] (except for the special value 180), or if a negative attenuation factor was specified.
GL_INVALID_OPERATION | glLight was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glColorMaterial, glEnd, glLightModel, glMaterial


glLightModel

glLightModelf, glLightModeli, glLightModelfv, glLightModeliv

These functions set the lighting model parameters.

void glLightModelf(
  GLenum pname,
  GLfloat param
);

void glLightModeli(
  GLenum pname,
  GLint param
);

Parameters

pname
A single-valued lighting model parameter. The following values are accepted:
GL_LIGHT_MODEL_LOCAL_VIEWER
The params parameter is a single integer or floating-point value that specifies how specular reflection angles are computed. If params is 0 (or 0.0), specular reflection angles take the view direction to be parallel to and in the direction of the –z axis, regardless of the location of the vertex in eye coordinates. Otherwise specular reflections are computed from the origin of the eye coordinate system. The default is 0.
GL_LIGHT_MODEL_TWO_SIDE
The params parameter is a single integer or floating-point value that specifies whether one- or two-sided lighting calculations are done for polygons. It has no effect on the lighting calculations for points, lines, or bitmaps. If params is 0 (or 0.0), one-sided lighting is specified, and only the front material parameters are used in the lighting equation. Otherwise, two-sided lighting is specified. In this case, vertices of back-facing polygons are lighted using the back material parameters, and have their normals reversed before the lighting equation is evaluated. Vertices of front-facing polygons are always lighted using the front material parameters, with no change to their normals. The default is 0.
param
The value to which param will be set.
void glLightModelfv(
  GLenum pname,
  const GLfloat *params
);

void glLightModeliv(
  GLenum pname,
  const GLint *params
);

Parameters

pname
A lighting model parameter. The following values are accepted:
GL_LIGHT_MODEL_AMBIENT
The params parameter contains four integer or floating-point values that specify the ambient RGBA intensity of the entire scene. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to –1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default ambient scene intensity is (0.2, 0.2, 0.2, 1.0).
GL_LIGHT_MODEL_LOCAL_VIEWER
The params parameter is a single integer or floating-point value that specifies how specular reflection angles are computed. If params is 0 (or 0.0), specular reflection angles take the view direction to be parallel to and in the direction of the –z axis, regardless of the location of the vertex in eye coordinates. Otherwise specular reflections are computed from the origin of the eye coordinate system. The default is 0.
GL_LIGHT_MODEL_TWO_SIDE
The params parameter is a single integer or floating-point value that specifies whether one- or two-sided lighting calculations are done for polygons. It has no effect on the lighting calculations for points, lines, or bitmaps. If params is 0 (or 0.0), one-sided lighting is specified, and only the front material parameters are used in the lighting equation. Otherwise, two-sided lighting is specified. In this case, vertices of back-facing polygons are lighted using the back material parameters, and have their normals reversed before the lighting equation is evaluated. Vertices of front-facing polygons are always lighted using the front material parameters, with no change to their normals. The default is 0.
params
A pointer to the value or values to which params will be set.

Remarks

The glLightModel function sets the lighting model parameter. The pname parameter names a parameter and params gives the new value.

In RGBA mode, the lighted color of a vertex is the sum of the material emission intensity, the product of the material ambient reflectance and the lighting model full-scene ambient intensity, and the contribution of each enabled light source. Each light source contributes the sum of three terms: ambient, diffuse, and specular. The ambient light source contribution is the product of the material ambient reflectance and the light's ambient intensity. The diffuse light source contribution is the product of the material diffuse reflectance, the light's diffuse intensity, and the dot product of the vertex's normal with the normalized vector from the vertex to the light source. The specular light source contribution is the product of the material specular reflectance, the light's specular intensity, and the dot product of the normalized vertex-to-eye and vertex-to-light vectors, raised to the power of the shininess of the material. All three light source contributions are attenuated equally based on the distance from the vertex to the light source and on light source direction, spread exponent, and spread cutoff angle. All dot products are replaced with zero if they evaluate to a negative value.

The alpha component of the resulting lighted color is set to the alpha value of the material diffuse reflectance.

In color-index mode, the value of the lighted index of a vertex ranges from the ambient to the specular values passed to glMaterial using GL_COLOR_INDEXES. Diffuse and specular coefficients, computed with a (.30, .59, .11) weighting of the light's colors, the shininess of the material, and the same reflection and attenuation equations as in the RGBA case, determine how much above ambient the resulting index is.

The following functions retrieve information related to glLightModel:

glGet with argument GL_LIGHT_MODEL_AMBIENT

glGet with argument GL_LIGHT_MODEL_LOCAL_VIEWER

glGet with argument GL_LIGHT_MODEL_TWO_SIDE

glIsEnabled with argument GL_LIGHTING

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | pname was not an accepted value.
GL_INVALID_OPERATION | glLightModel was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glLight, glMaterial


glLineStipple

The glLineStipple function specifies the line stipple pattern.

void glLineStipple(
  GLint factor,
  GLushort pattern
);

Parameters

factor
A multiplier for each bit in the line stipple pattern. If factor is 3, for example, each bit in the pattern will be used three times before the next bit in the pattern is used. The factor parameter is clamped to the range [1, 256] and defaults to one.
pattern
A 16-bit integer whose bit pattern determines which fragments of a line will be drawn when the line is rasterized. Bit zero is used first, and the default pattern is all ones.

Remarks

The glLineStipple function specifies the line stipple pattern. Line stippling masks out certain fragments produced by rasterization; those fragments will not be drawn. The masking is achieved by using three parameters: the 16-bit line stipple pattern pattern, the repeat count factor, and an integer stipple counter s.

Counter s is reset to zero whenever glBegin is called, and before each line segment of a glBegin(GL_LINES)/glEnd sequence is generated. It is incremented after each fragment of a unit width aliased line segment is generated, or after each i fragments of an i width line segment are generated. The i fragments associated with count s are masked out if pattern bit (s factor) mod 16 is zero, otherwise these fragments are sent to the frame buffer. Bit zero of pattern is the least significant bit.

Antialiased lines are treated as a sequence of 1xwidth rectangles for purposes of stippling. Rectangle s is rasterized or not based on the fragment rule described for aliased lines; it counts rectangles rather than groups of fragments.

Line stippling is enabled or disabled using glEnable and glDisable with argument GL_LINE_STIPPLE. When enabled, the line stipple pattern is applied as described above. When disabled, it is as if the pattern were all ones. Initially, line stippling is disabled.

The following functions retrieve information related to glLineStipple:

glGet with argument GL_LINE_STIPPLE_PATTERN

glGet with argument GL_LINE_STIPPLE_REPEAT

glIsEnabled with argument GL_LINE_STIPPLE

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glLineStipple was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glLineWidth, glPolygonStipple


glLineWidth

The glLineWidth function specifies the width of rasterized lines.

void glLineWidth(
  GLfloat width
);

Parameters

width
The width of rasterized lines. The default is 1.0.

Remarks

The glLineWidth function specifies the rasterized width of both aliased and antialiased lines. Using a line width other than 1.0 has different effects, depending on whether line antialiasing is enabled. Line antialiasing is controlled by calling glEnable and glDisable with argument GL_LINE_SMOOTH.

If line antialiasing is disabled, the actual width is determined by rounding the supplied width to the nearest integer. (If the rounding results in the value 0, it is as if the line width were 1.) If | Δ x | ≥ | Δ y |, i pixels are filled in each column that is rasterized, where i is the rounded value of width. Otherwise, i pixels are filled in each row that is rasterized.

If antialiasing is enabled, line rasterization produces a fragment for each pixel square that intersects the region lying within the rectangle having width equal to the current line width, length equal to the actual length of the line, and centered on the mathematical line segment. The coverage value for each fragment is the window coordinate area of the intersection of the rectangular region with the corresponding pixel square. This value is saved and used in the final rasterization step.

Not all widths can be supported when line antialiasing is enabled. If an unsupported width is requested, the nearest supported width is used. Only width 1.0 is guaranteed to be supported; others depend on the implementation. The range of supported widths and the size difference between supported widths within the range can be queried by calling glGet with arguments GL_LINE_WIDTH_RANGE and GL_LINE_WIDTH_GRANULARITY.

The line width specified by glLineWidth is always returned when GL_LINE_WIDTH is queried. Clamping and rounding for aliased and antialiased lines have no effect on the specified value.

Non-antialiased line width may be clamped to an implementation-dependent maximum. Although this maximum cannot be queried, it must be no less than the maximum value for antialiased lines, rounded to the nearest integer value.

The following functions retrieve information related to glLineWidth:

glGet with argument GL_LINE_WIDTH

glGet with argument GL_LINE_WIDTH_RANGE

glGet with argument GL_LINE_WIDTH_GRANULARITY

glIsEnabled with argument GL_LINE_SMOOTH

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_VALUE | width was less than or equal to zero.
GL_INVALID_OPERATION | glLineWidth was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnable, glEnd, glIsEnabled


glListBase

The glListBase function sets the display list base for glCallLists.

void glListBase(
  GLuint base
);

Parameters

base
An integer offset that will be added to glCallLists offsets to generate display list names. Initial value is zero.

Remarks

The glListBase function specifies an array of offsets. Display list names are generated by adding base to each offset. Names that reference valid display lists are executed; others are ignored.

The following function retrieves information related to glListBase:

glGet with argument GL_LIST_BASE

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glListBase was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glCallLists, glEnd


glLoadIdentity

The glLoadIdentity function replaces the current matrix with the identity matrix.

void glLoadIdentity(
  void
);

Remarks

The glLoadIdentity function replaces the current matrix with the identity matrix. It is semantically equivalent to calling glLoadMatrix with the identity matrix

but in some cases it is more efficient.

The following functions retrieve information related to glLoadIdentity:

glGet with argument GL_MATRIX_MODE

glGet with argument GL_MODELVIEW_MATRIX

glGet with argument GL_PROJECTION_MATRIX

glGet with argument GL_TEXTURE_MATRIX

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glLoadIdentity was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glLoadMatrix, glMatrixMode, glMultMatrix, glPushMatrix


glLoadMatrix

glLoadMatrixd, glLoadMatrixf

The glLoadMatrixd and glLoadMatrixf functions replace the current matrix with an arbitrary matrix.

void glLoadMatrixd(
  const GLdouble *m
);

void glLoadMatrixf(
  const GLfloat *m
);

Parameters

m
A pointer to a 4x4 matrix stored in column-major order as 16 consecutive values.

Remarks

The glLoadMatrix function replaces the current matrix with the one specified in m. The current matrix is the projection matrix, modelview matrix, or texture matrix, determined by the current matrix mode (see glMatrixMode).

The m parameter points to a 4x4 matrix of single- or double-precision floating-point values stored in column-major order. That is, the matrix is stored as follows:

The following functions retrieve information related to glLoadMatrix:

glGet with argument GL_MATRIX_MODE

glGet with argument GL_MODELVIEW_MATRIX

glGet with argument GL_PROJECTION_MATRIX

glGet with argument GL_TEXTURE_MATRIX

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glLoadMatrix was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glLoadIdentity, glMatrixMode, glMultMatrix, glPushMatrix


glLoadName

The glLoadName function loads a name onto the name stack.

void glLoadName(
  GLuint name
);

Parameters

name
A name that will replace the top value on the name stack.

Remarks

The glLoadName function causes name to replace the value on the top of the name stack, which is initially empty. The name stack is used during selection mode to allow sets of rendering commands to be uniquely identified. It consists of an ordered set of unsigned integers.

The name stack is always empty while the render mode is not GL_SELECT. Calls to glLoadName while the render mode is not GL_SELECT are ignored.

The following functions retrieve information related to glLoadName:

glGet with argument GL_NAME_STACK_DEPTH

glGet with argument GL_MAX_NAME_STACK_DEPTH

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glLoadName was called while the name stack was empty.
GL_INVALID_OPERATION | glLoadName was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glInitNames, glPushName, glRenderMode, glSelectBuffer


glLogicOp

The glLogicOp function specifies a logical pixel operation for color index rendering.

void glLogicOp(
  GLenum opcode
);

Parameters

opcode
A symbolic constant that selects a logical operation. The following symbols are accepted:
Opcode | Resulting Value
GL_CLEAR | 0
GL_SET | 1
GL_COPY | s
GL_COPY_INVERTED | !s
GL_NOOP | d
GL_INVERT | !d
GL_AND | s & d
GL_NAND | !(s & d)
GL_OR | s | d
GL_NOR | !(s | d)
GL_XOR | s ^ d
GL_EQUIV | !(s ^ d)
GL_AND_REVERSE | s & !d
GL_AND_INVERTED | !s & d
GL_OR_REVERSE | s | !d
GL_OR_INVERTED | !s | d

Remarks

The glLogicOp function specifies a logical operation that, when enabled, is applied between the incoming color index and the color index at the corresponding location in the frame buffer. The logical operation is enabled or disabled with glEnable and glDisable using the symbolic constant GL_LOGIC_OP.

The opcode parameter is a symbolic constant chosen from the list below. In the explanation of the logical operations, s represents the incoming color index and d represents the index in the frame buffer. Standard C-language operators are used. As these bitwise operators suggest, the logical operation is applied independently to each bit pair of the source and destination indexes.

Logical pixel operations are not applied to RGBA color buffers.

When more than one color-index buffer is enabled for drawing, logical operations are done separately for each enabled buffer, using the contents of that buffer for the destination index (see glDrawBuffer).

The opcode parameter must be one of the 16 accepted values. Other values result in an error.

The following functions retrieve information related to glLogicOp:

glGet with argument GL_LOGIC_OP_MODE

glIsEnabled with argument GL_LOGIC_OP

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | opcode was not an accepted value.
GL_INVALID_OPERATION | glLogicOp was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glAlphaFunc, glBegin, glBlendFunc, glDrawBuffer, glEnable, glEnd, glIsEnabled, glStencilOp


glMap1

glMap1d, glMap1f

The glMap1d and glMap1f functions define a one-dimensional evaluator.

void glMap1d(
  GLenum target,
  GLdouble u1,
  GLdouble u2,
  GLint stride,
  GLint order,
  const GLdouble *points
);

void glMap1f(
  GLenum target,
  GLfloat u1,
  GLfloat u2,
  GLint stride,
  GLint order,
  const GLfloat *points
);

Parameters

target
The kind of values that are generated by the evaluator. Symbolic constants. The target parameter is a symbolic constant that indicates what kind of control points are provided in points, and what output is generated when the map is evaluated. It can assume one of nine predefined values:
GL_MAP1_VERTEX_3
Each control point is three floating-point values representing x, y, and z. Internal glVertex3 commands are generated when the map is evaluated.
GL_MAP1_VERTEX_4
Each control point is four floating-point values representing x, y, z, and w. Internal glVertex4 commands are generated when the map is evaluated.
GL_MAP1_INDEX
Each control point is a single floating-point value representing a color index. Internal glIndex commands are generated when the map is evaluated. However, the current index is not updated with the value of these glIndex commands.
GL_MAP1_COLOR_4
Each control point is four floating-point values representing red, green, blue, and alpha. Internal glColor4 commands are generated when the map is evaluated. However, the current color is not updated with the value of these glColor4 commands.
GL_MAP1_NORMAL
Each control point is three floating-point values representing the x, y, and z components of a normal vector. Internal glNormal commands are generated when the map is evaluated. However, the current normal is not updated with the value of these glNormal commands.
GL_MAP1_TEXTURE_COORD_1
Each control point is a single floating-point value representing the s texture coordinate. Internal glTexCoord1 commands are generated when the map is evaluated. However, the current texture coordinates are not updated with the value of these glTexCoord commands.
GL_MAP1_TEXTURE_COORD_2
Each control point is two floating-point values representing the s and t texture coordinates. Internal glTexCoord2 commands are generated when the map is evaluated. However, the current texture coordinates are not updated with the value of these glTexCoord commands.
GL_MAP1_TEXTURE_COORD_3
Each control point is three floating-point values representing the s, t, and r texture coordinates. Internal glTexCoord3 commands are generated when the map is evaluated. However, the current texture coordinates are not updated with the value of these glTexCoord commands.
GL_MAP1_TEXTURE_COORD_4
Each control point is four floating-point values representing the s, t, r, and q texture coordinates. Internal glTexCoord4 commands are generated when the map is evaluated. However, the current texture coordinates are not updated with the value of these glTexCoord commands.
u1, u2
A linear mapping of u, as presented to glEvalCoord1, to û, the variable that is evaluated by the equations specified by this command.
stride
The number of floats or doubles between the beginning of one control point and the beginning of the next one in the data structure referenced in points. This allows control points to be embedded in arbitrary data structures. The only constraint is that the values for a particular control point must occupy contiguous memory locations.
order
The number of control points. Must be positive.
points
A pointer to the array of control points.

Remarks

Evaluators provide a way to use polynomial or rational polynomial mapping to produce vertices, normals, texture coordinates, and colors. The values produced by an evaluator are sent to further stages of OpenGL processing just as if they had been presented using glVertex, glNormal, glTexCoord, and glColor commands, except that the generated values do not update the current normal, texture coordinates, or color.

All polynomial or rational polynomial splines of any degree (up to the maximum degree supported by the OpenGL implementation) can be described using evaluators. These include almost all splines used in computer graphics, including B-splines, Bezier curves, Hermite splines, and so on.

Evaluators define curves based on Bernstein polynomials. Define p(û) as

where R (i) is a control point and (û) is the ith Bernstein polynomial of degree n (order = n + 1):

Recall that

The glMap1 function is used to define the basis and to specify what kind of values are produced. Once defined, a map can be enabled and disabled by calling glEnable and glDisable with the map name, one of the nine predefined values for target described above. The glEvalCoord1 function evaluates the one-dimensional maps that are enabled. When glEvalCoord1 presents a value u, the Bernstein functions are evaluated using û, where

The stride, order, and points parameters define the array addressing for accessing the control points. The points parameter is the location of the first control point, which occupies one, two, three, or four contiguous memory locations, depending on which map is being defined. The order parameter is the number of control points in the array. The stride parameter tells how many float or double locations to advance the internal memory pointer to reach the next control point.

As is the case with all OpenGL commands that accept pointers to data, it is as if the contents of points were copied by glMap1 before it returned. Changes to the contents of points have no effect after glMap1 is called.

The following functions retrieve information related to glMap1:

glGet with argument GL_MAX_EVAL_ORDER

glGetMap

glIsEnabled with argument GL_MAP1_VERTEX_3

glIsEnabled with argument GL_MAP1_VERTEX_4

glIsEnabled with argument GL_MAP1_INDEX

glIsEnabled with argument GL_MAP1_COLOR_4

glIsEnabled with argument GL_MAP1_NORMAL

glIsEnabled with argument GL_MAP1_TEXTURE_COORD_1

glIsEnabled with argument GL_MAP1_TEXTURE_COORD_2

glIsEnabled with argument GL_MAP1_TEXTURE_COORD_3

glIsEnabled with argument GL_MAP1_TEXTURE_COORD_4

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | target was not an accepted value.
GL_INVALID_VALUE | u1 was equal to u2.
GL_INVALID_VALUE | stride was less than the number of values in a control point.
GL_INVALID_VALUE | order was less than one or greater than GL_MAX_EVAL_ORDER.
GL_INVALID_OPERATION | glMap1 was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glColor, glEnable, glEnd, glEvalCoord, glEvalMesh, glEvalPoint, glMap2, glMapGrid, glNormal, glTexCoord, glVertex


glMap2

glMap2d, glMap2f

The glMap2d and glMap2f functions define a two-dimensional evaluator.

void glMap2d(
  GLenum target,
  GLdouble u1,
  GLdouble u2,
  GLint ustride,
  GLint uorder,
  GLdouble v1,
  GLdouble v2,
  GLint vstride,
  GLint vorder,
  const GLdouble *points
);

void glMap2f(
  GLenum target,
  GLfloat u1,
  GLfloat u2,
  GLint ustride,
  GLint uorder,
  GLfloat v1,
  GLfloat v2,
  GLint vstride,
  GLint vorder,
  const GLfloat *points
);

Parameters

target
The kind of values that are generated by the evaluator. The following symbolic constants are accepted:
GL_MAP2_VERTEX_3
Each control point is three floating-point values representing x, y, and z. Internal glVertex3 commands are generated when the map is evaluated.
GL_MAP2_VERTEX_4
Each control point is four floating-point values representing x, y, z, and w. Internal glVertex4 commands are generated when the map is evaluated.
GL_MAP2_INDEX
Each control point is a single floating-point value representing a color index. Internal glIndex commands are generated when the map is evaluated. The current index is not updated with the value of these glIndex commands, however.
GL_MAP2_COLOR_4
Each control point is four floating-point values representing red, green, blue, and alpha. Internal glColor4 commands are generated when the map is evaluated. The current color is not updated with the value of these glColor4 commands, however.
GL_MAP2_NORMAL
Each control point is three floating-point values representing the x, y, and z components of a normal vector. Internal glNormal commands are generated when the map is evaluated. The current normal is not updated with the value of these glNormal commands, however.
GL_MAP2_TEXTURE_COORD_1
Each control point is a single floating-point value representing the s texture coordinate. Internal glTexCoord1 commands are generated when the map is evaluated. The current texture coordinates are not updated with the value of these glTexCoord commands, however.
GL_MAP2_TEXTURE_COORD_2
Each control point is two floating-point values representing the s and t texture coordinates. Internal glTexCoord2 commands are generated when the map is evaluated. The current texture coordinates are not updated with the value of these glTexCoord commands, however.
GL_MAP2_TEXTURE_COORD_3
Each control point is three floating-point values representing the s, t, and r texture coordinates. Internal glTexCoord3 commands are generated when the map is evaluated. The current texture coordinates are not updated with the value of these glTexCoord commands, however.
GL_MAP2_TEXTURE_COORD_4
Each control point is four floating-point values representing the s, t, r, and q texture coordinates. Internal glTexCoord4 commands are generated when the map is evaluated. The current texture coordinates are not updated with the value of these glTexCoord commands, however.
u1, u2
A linear mapping of u, as presented to glEvalCoord2, to û, one of the two variables that is evaluated by the equations specified by this command.
ustride
The number of floats or doubles between the beginning of control point R (ij) and the beginning of control point R ((i+1)j) , where i and j are the u and v control point indexes, respectively. This allows control points to be embedded in arbitrary data structures. The only constraint is that the values for a particular control point must occupy contiguous memory locations.
uorder
The dimension of the control point array in the u-axis. Must be positive.
v1, v2
A linear mapping of v, as presented to glEvalCoord2, to , one of the two variables that is evaluated by the equations specified by this command.
vstride
The number of floats or doubles between the beginning of control point R (ij) and the beginning of control point R (i(j+1)) , where i and j are the u and v control point indexes, respectively. This allows control points to be embedded in arbitrary data structures. The only constraint is that the values for a particular control point must occupy contiguous memory locations.
vorder
The dimension of the control point array in the v-axis. Must be positive.
points
A pointer to the array of control points.

Remarks

Evaluators provide a way to use polynomial or rational polynomial mapping to produce vertices, normals, texture coordinates, and colors. The values produced by an evaluator are sent on to further stages of OpenGL processing just as if they had been presented using glVertex, glNormal, glTexCoord, and glColor commands, except that the generated values do not update the current normal, texture coordinates, or color.

All polynomial or rational polynomial splines of any degree (up to the maximum degree supported by the OpenGL implementation) can be described using evaluators. These include almost all surfaces used in computer graphics, including B-spline surfaces, NURBS surfaces, Bezier surfaces, and so on.

Evaluators define surfaces based on bivariate Bernstein polynomials. Define p(û,) as

where R (ij) is a control point, (û) is the ith Bernstein polynomial of degree

n (uorder = n + 1)

and () is the jth Bernstein polynomial of degree m (vorder = m + 1)

Recall that

The glMap2 function is used to define the basis and to specify what kind of values are produced. Once defined, a map can be enabled and disabled by calling glEnable and glDisable with the map name, one of the nine predefined values for target, described above. When glEvalCoord2 presents values u and v, the bivariate Bernstein polynomials are evaluated using û and , where

The target parameter is a symbolic constant that indicates what kind of control points are provided in points, and what output is generated when the map is evaluated.

The ustride, uorder, vstride, vorder, and points parameters define the array addressing for accessing the control points. The points parameter is the location of the first control point, which occupies one, two, three, or four contiguous memory locations, depending on which map is being defined. There are uorderxvorder control points in the array. The ustride parameter tells how many float or double locations are skipped to advance the internal memory pointer from control point R (ij) to control point R ((i+1)j) . The vstride parameter tells how many float or double locations are skipped to advance the internal memory pointer from control point R (ij) to control point R (i(j+1)) .

As is the case with all OpenGL commands that accept pointers to data, it is as if the contents of points were copied by glMap2 before it returned. Changes to the contents of points have no effect after glMap2 is called.

The following functions retrieve information related to glMap2:

glGet with argument GL_MAX_EVAL_ORDER

glGetMap

glIsEnabled with argument GL_MAP2_VERTEX_3

glIsEnabled with argument GL_MAP2_VERTEX_4

glIsEnabled with argument GL_MAP2_INDEX

glIsEnabled with argument GL_MAP2_COLOR_4

glIsEnabled with argument GL_MAP2_NORMAL

glIsEnabled with argument GL_MAP2_TEXTURE_COORD_1

glIsEnabled with argument GL_MAP2_TEXTURE_COORD_2

glIsEnabled with argument GL_MAP2_TEXTURE_COORD_3

glIsEnabled with argument GL_MAP2_TEXTURE_COORD_4

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | target was not an accepted value.
GL_INVALID_VALUE | u1 was equal to u2, or if v1 was equal to v2.
GL_INVALID_VALUE | either ustride or vstride was less than the number of values in a control point.
GL_INVALID_VALUE | either uorder or vorder was less than one or greater than GL_MAX_EVAL_ORDER.
GL_INVALID_OPERATION | glMap2 was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glColor, glEnable, glEnd, glEvalCoord, glEvalMesh, glEvalPoint, glMap1, glMapGrid, glNormal, glTexCoord, glVertex


glMapGrid

glMapGrid1d, glMapGrid1f, glMapGrid2d, glMapGrid2f

These functions define a one- or two-dimensional mesh.

void glMapGrid1d(
  GLint un,
  GLdouble u1,
  GLdouble u2
);

void glMapGrid1f(
  GLint un,
  GLfloat u1,
  GLfloat u2
);

void glMapGrid2d(
  GLint un,
  GLdouble u1,
  GLdouble u2,
  GLint vn,
  GLdouble v1,
  GLdouble v2
);

void glMapGrid2f(
  GLint un,
  GLfloat u1,
  GLfloat u2,
  GLint vn,
  GLfloat v1,
  GLfloat v2
);

Parameters

un
The number of partitions in the grid range interval [u1, u2]. Must be positive.
u1, u2
The mappings for integer grid domain values i = 0 and i = un.
vn
The number of partitions in the grid range interval [v1, v2] (glMapGrid2 only).
v1, v2
The mappings for integer grid domain values j = 0 and j = vn (glMapGrid2 only).

Remarks

The glMapGrid and glEvalMesh functions are used in tandem to efficiently generate and evaluate a series of evenly spaced map domain values. The glEvalMesh function steps through the integer domain of a one- or two-dimensional grid, whose range is the domain of the evaluation maps specified by glMap1 and glMap2.

The glMapGrid1 and glMapGrid2 functions specify the linear grid mappings between the i (or i and j) integer grid coordinates, to the u (or u and v) floating-point evaluation map coordinates. See glMap1 and glMap2 for details of how u and v coordinates are evaluated.

The glMapGrid1 function specifies a single linear mapping such that integer grid coordinate 0 maps exactly to u1, and integer grid coordinate un maps exactly to u2. All other integer grid coordinates i are mapped such that:

u = i(u2 – u1)/un + u1

The glMapGrid2 function specifies two such linear mappings. One maps integer grid coordinate i = 0 exactly to u1, and integer grid coordinate i = un exactly to u2. The other maps integer grid coordinate j = 0 exactly to v1, and integer grid coordinate j = vn exactly to v2. Other integer grid coordinates i and j are mapped such that

u = i(u2 – u1)/un + u1

v = j (v2 – v1)/vn + v1

The mappings specified by glMapGrid are used identically by glEvalMesh and glEvalPoint.

The following functions retrieve information related to glMapGrid:

glGet with argument GL_MAP1_GRID_DOMAIN

glGet with argument GL_MAP2_GRID_DOMAIN

glGet with argument GL_MAP1_GRID_SEGMENTS

glGet with argument GL_MAP2_GRID_SEGMENTS

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_VALUE | Either un or vn was not positive.
GL_INVALID_OPERATION | glMapGrid was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glEvalCoord, glEvalMesh, glEvalPoint, glMap1, glMap2


glMaterial

glMaterialf, glMateriali, glMaterialfv, glMaterialiv

These functions specify material parameters for the lighting model.

void glMaterialf(
  GLenum face,
  GLenum pname,
  GLfloat param
);

void glMateriali(
  GLenum face,
  GLenum pname,
  GLint param
);

Parameters

face
The face or faces that are being updated. Must be one of GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK.
pname
The single-valued material parameter of the face or faces being updated. Must be GL_SHININESS.
param
The value that parameter GL_SHININESS will be set to.
void glMaterialfv(
  GLenum face,
  GLenum pname,
  const GLfloat *params
);

void glMaterialiv(
  GLenum face,
  GLenum pname,
  const GLint *params
);

Parameters

face
The face or faces that are being updated. Must be one of GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK.
pname
The material parameter of the face or faces being updated. The parameters that can be specified using glMaterial, and their interpretations by the lighting equation, are as follows:
GL_AMBIENT
The params parameter contains four integer or floating-point values that specify the ambient RGBA reflectance of the material. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to –1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default ambient reflectance for both front- and back-facing materials is (0.2, 0.2, 0.2, 1.0).
GL_DIFFUSE
The params parameter contains four integer or floating-point values that specify the diffuse RGBA reflectance of the material. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to –1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default diffuse reflectance for both front- and back-facing materials is (0.8, 0.8, 0.8, 1.0).
GL_SPECULAR
The params parameter contains four integer or floating-point values that specify the specular RGBA reflectance of the material. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to –1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default specular reflectance for both front- and back-facing materials is (0.0, 0.0, 0.0, 1.0).
GL_EMISSION
The params parameter contains four integer or floating-point values that specify the RGBA emitted light intensity of the material. Integer values are mapped linearly such that the most positive representable value maps to 1.0, and the most negative representable value maps to –1.0. Floating-point values are mapped directly. Neither integer nor floating-point values are clamped. The default emission intensity for both front- and back-facing materials is (0.0, 0.0, 0.0, 1.0).
GL_SHININESS
The params parameter is a single integer or floating-point value that specifies the RGBA specular exponent of the material. Integer and floating-point values are mapped directly. Only values in the range [0,128] are accepted. The default specular exponent for both front- and back-facing materials is 0.
GL_AMBIENT_AND_DIFFUSE
Equivalent to calling glMaterial twice with the same parameter values, once with GL_AMBIENT and once with GL_DIFFUSE.
GL_COLOR_INDEXES
The params parameter contains three integer or floating-point values specifying the color indexes for ambient, diffuse, and specular lighting. These three values, and GL_SHININESS, are the only material values used by the color-index mode lighting equation. Refer to glLightModel for a discussion of color-index lighting.
params
A pointer to the value or values to which pname will be set.

Remarks

The glMaterial function assigns values to material parameters. There are two matched sets of material parameters. One, the front-facing set, is used to shade points, lines, bitmaps, and all polygons (when two-sided lighting is disabled), or just front-facing polygons (when two-sided lighting is enabled). The other set, back-facing, is used to shade back-facing polygons only when two-sided lighting is enabled. Refer to glLightModel for details concerning one- and two-sided lighting calculations.

The glMaterial function takes three arguments. The first, face, specifies whether the GL_FRONT materials, the GL_BACK materials, or both GL_FRONT_AND_BACK materials will be modified. The second, pname, specifies which of several parameters in one or both sets will be modified. The third, params, specifies what value or values will be assigned to the specified parameter.

Material parameters are used in the lighting equation that is optionally applied to each vertex. The equation is discussed in glLightModel.

The material parameters can be updated at any time. In particular, glMaterial can be called between a call to glBegin and the corresponding call to glEnd. If only a single material parameter is to be changed per vertex, however, glColorMaterial is preferred over glMaterial.

The following function retrieves information related to glMaterial:

glGetMaterial

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | Either face or pname was not an accepted value.
GL_INVALID_VALUE | A specular exponent outside the range [0,128] was specified.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glColorMaterial, glLight, glLightModel


glMatrixMode

The glMatrixMode function specifies which matrix is the current matrix.

void glMatrixMode(
  GLenum mode
);

Parameters

mode
The matrix stack that is the target for subsequent matrix operations. The mode parameter can assume one of three values:
GL_MODELVIEW
Applies subsequent matrix operations to the modelview matrix stack.
GL_PROJECTION
Applies subsequent matrix operations to the projection matrix stack.
GL_TEXTURE
Applies subsequent matrix operations to the texture matrix stack.

Remarks

The glMatrixMode function sets the current matrix mode.

The following function retrieves information related to glMatrixMode:

glGet with argument GL_MATRIX_MODE

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | mode was not an accepted value.
GL_INVALID_OPERATION | glMatrixMode was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glLoadMatrix, glPushMatrix


glMultMatrix

glMultMatrixd, glMultMatrixf

The glMultMatrixd and glMultMatrixf functions multiply the current matrix by an arbitrary matrix.

void glMultMatrixd(
  const GLdouble *m
);

void glMultMatrixf(
  const GLfloat *m
);

Parameters

m
A pointer to a 4x4 matrix stored in column-major order as 16 consecutive values.

Remarks

The glMultMatrix function multiplies the current matrix by the one specified in m. That is, if M is the current matrix and T is the matrix passed to glMultMatrix, then M is replaced with M · T.

The current matrix is the projection matrix, modelview matrix, or texture matrix, determined by the current matrix mode (see glMatrixMode).

The m parameter points to a 4x4 matrix of single- or double-precision floating-point values stored in column-major order. That is, the matrix is stored as:

The following functions retrieve information related to glMultMatrix:

glGet with argument GL_MATRIX_MODE

glGet with argument GL_MODELVIEW_MATRIX

glGet with argument GL_PROJECTION_MATRIX

glGet with argument GL_TEXTURE_MATRIX

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glMultMatrix was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glLoadIdentity, glLoadMatrix, glMatrixMode, glPushMatrix


glNewList, glEndList

The glNewList and glEndList functions create or replace a display list.

void glNewList(
  GLuint list,
  GLenum mode
);

void glEndList(
  void
);

Parameters

list
The display list name.
mode
The compilation mode. The following values are accepted:
GL_COMPILE
Commands are merely compiled.
GL_COMPILE_AND_EXECUTE
Commands are executed as they are compiled into the display list.

Remarks

Display lists are groups of OpenGL commands that have been stored for subsequent execution. The display lists are created with glNewList. All subsequent commands are placed in the display list, in the order issued, until glEndList is called.

The glNewList function has two parameters. The first parameter, list, is a positive integer that becomes the unique name for the display list. Names can be created and reserved with glGenLists and tested for uniqueness with glIsList. The second parameter, mode, is a symbolic constant that can assume one of the two preceding values.

Certain commands are not compiled into the display list, but are executed immediately, regardless of the display list mode. These commands are glIsList, glGenLists, glDeleteLists, glFeedbackBuffer, glSelectBuffer, glRenderMode, glReadPixels, glPixelStore, glFlush, glFinish, glIsEnabled, and all of the glGet routines.

When the glNewList function is encountered, the display list definition is completed by associating the list with the unique name list (specified in the glNewList command). If a display list with name list already exists, it is replaced only when glEndList is called.

The glCallList and glCallLists functions can be entered into display lists. The commands in the display list or lists executed by glCallList or glCallLists are not included in the display list being created, even if the list creation mode is GL_COMPILE_AND_EXECUTE.

The following function retrieves information related to glNewList:

glGet with argument GL_MATRIX_MODE

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_VALUE | list was zero.
GL_INVALID_ENUM | mode was not an accepted value.
GL_INVALID_OPERATION | glEndList was called without a preceding glNewList, or if glNewList was called while a display list was being defined.
GL_INVALID_OPERATION | glNewList was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glCallList, glCallLists, glDeleteLists, glEnd, glGenLists, glIsList


glNormal

glNormal3b, glNormal3d, glNormal3f, glNormal3i, glNormal3s, glNormal3bv, glNormal3dv, glNormal3fv, glNormal3iv, glNormal3sv

These functions set the current normal vector.

void glNormal3b(
  GLbyte nx,
  GLbyte ny,
  GLbyte nz
);

void glNormal3d(
  GLdouble nx,
  GLdouble ny,
  GLdouble nz
);

void glNormal3f(
  GLfloat nx,
  GLfloat ny,
  GLfloat nz
);

void glNormal3i(
  GLint nx,
  GLint ny,
  GLint nz
);

void glNormal3s(
  GLshort nx,
  GLshort ny,
  GLshort nz
);

Parameters

nx, ny, nz
The x, y, and z coordinates of the new current normal. The initial value of the current normal is (0,0,1).
void glNormal3bv(
  const GLbyte *v
);

void glNormal3dv(
  const GLdouble *v
);

void glNormal3fv(
  const GLfloat *v
);

void glNormal3iv(
  const GLint *v
);

void glNormal3sv(
  const GLshort *v
);

Parameters

v
A pointer to an array of three elements: the x, y, and z coordinates of the new current normal.

Remarks

The current normal is set to the given coordinates whenever glNormal is issued. Byte, short, or integer arguments are converted to floating-point format with a linear mapping that maps the most positive representable integer value to 1.0, and the most negative representable integer value to –1.0.

Normals specified with glNormal need not have unit length. If normalization is enabled, then normals specified with glNormal are normalized after transformation. Normalization is controlled using glEnable and glDisable with the argument GL_NORMALIZE. By default, normalization is disabled.

The current normal can be updated at any time. In particular, glNormal can be called between a call to glBegin and the corresponding call to glEnd.

The following functions retrieve information related to glNormal:

glGet with argument GL_CURRENT_NORMAL

glIsEnable with argument GL_NORMALIZE

See Also

glBegin, glColor, glEnd, glIndex, glTexCoord, glVertex


glNormalPointer

The glNormalPointer function defines an array of normals.

void glNormalPointer(
  GLenum type,
  GLsizei stride,
  GLsizei count,
  const GLvoid *pointer
);

Parameters

type
The data type of each coordinate in the array using the following symbolic constants: GL_BYTE, GL_SHORT, GL_INT, GL_FLOAT, and GL_DOUBLE.
stride
The byte offset between consecutive normals. When stride is zero, the normals are tightly packed in the array.
count
The number of normals, counting from the first, that are static.
pointer
A pointer to the first normal in the array.

Remarks

The glNormalPointer function specifies the location and data of an array of normals to use when rendering. The type parameter specifies the data type of each normal coordinate. The stride parameter determines the byte offset from one normal to the next, enabling the packing of vertices and attributes in a single array or storage in separate arrays. In some implementations storing the vertices and attributes in a single array can be more efficient than using separate arrays. Starting from the first normal element, count indicates the total number of static elements. Your application can modify static elements, but once the elements are modified, the application must explicitly specify the array again before using the array for any rendering. Non-static array elements are not accessed until you call glDrawArrays or glArrayElement.

A normal array is enabled when you specify the GL_NORMAL_ARRAY constant with glEnableClientState. When enabled, glDrawArrays and glArrayElement use the normal array. By default the normal array is disabled.

You cannot include glNormalPointer in display lists.

When you specify a normal array using glNormalPointer, the values of all the function's normal array parameters are saved in a client-side state and static array elements can be cached. Because the normal array parameters are saved in a client-side state, their values are not saved or restored by glPushAttrib and glPopAttrib.

Although no error is generated when you call glNormalPointer within glBegin and glEnd pairs, the results are undefined.

The following functions are associated with glNormalPointer:

glGet with argument GL_NORMAL_ARRAY_STRIDE

glGet with argument GL_NORMAL_ARRAY_COUNT

glGet with argument GL_NORMAL_ARRAY_TYPE

glGetPointerv with argument GL_NORMAL_ARRAY_POINTER

glIsEnabled with argument GL_NORMAL_ARRAY

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | type was not an accepted value.
GL_INVALID_VALUE | stride or count was negative.

See Also

glArrayElement, glColorPointer, glDrawArrays, glEdgeFlagPointer, glGetPointerv, glIndexPointer, glIsEnabled, glTexCoordPointer, glVertexPointer, glGetString


glOrtho

The glOrtho function multiplies the current matrix by an orthographic matrix.

void glOrtho(
  GLdouble left,
  GLdouble right,
  GLdouble bottom,
  GLdouble top,
  GLdouble near,
  GLdouble far
);

Parameters

left, right
The coordinates for the left and right vertical clipping planes.
bottom, top
The coordinates for the bottom and top horizontal clipping planes.
near, far
The distances to the nearer and farther depth clipping planes. These distances are negative if the plane is to be behind the viewer.

Remarks

The glOrtho function describes a perspective matrix that produces a parallel projection. The (left, bottom, – near) and (right, top, – near) parameters specify the points on the near clipping plane that are mapped to the lower-left and upper-right corners of the window, respectively, assuming that the eye is located at (0, 0, 0). The –far parameter specifies the location of the far clipping plane. Both near and far can be either positive or negative. The corresponding matrix is

where

The current matrix is multiplied by this matrix with the result replacing the current matrix. That is, if M is the current matrix and O is the ortho matrix, then M is replaced with M · O.

Use glPushMatrix and glPopMatrix to save and restore the current matrix stack.

The following functions retrieve information related to glOrtho:

glGet with argument GL_MATRIX_MODE

glGet with argument GL_MODELVIEW_MATRIX

glGet with argument GL_PROJECTION_MATRIX

glGet with argument GL_TEXTURE_MATRIX

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glOrtho was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glFrustum, glMatrixMode, glMultMatrix, glPushMatrix, glViewport


glPassThrough

The glPassThrough function places a marker in the feedback buffer.

void glPassThrough(
  GLfloat token
);

Parameters

token
A marker value to be placed in the feedback buffer. It is indicated with the following unique identifying value:
GL_PASS_THROUGH_TOKEN
The order of glPassThrough commands with respect to the specification of graphics primitives is maintained.

Remarks

Feedback is an OpenGL render mode. The mode is selected by calling glRenderMode with GL_FEEDBACK. When OpenGL is in feedback mode, no pixels are produced by rasterization. Instead, information about primitives that would have been rasterized is fed back to the application using OpenGL. See glFeedbackBuffer for a description of the feedback buffer and the values in it.

The glPassThrough function inserts a user-defined marker in the feedback buffer when it is executed in feedback mode. The token parameter is returned as if it were a primitive.

The glPassThrough function is ignored if OpenGL is not in feedback mode.

The following function retrieves information related to glPassThrough:

glGet with argument GL_RENDER_MODE

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glPassThrough was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glFeedbackBuffer, glRenderMode


glPixelMap

glPixelMapfv, glPixelMapuiv, glPixelMapusv

These functions set up pixel transfer maps.

void glPixelMapfv(
  GLenum map,
  GLint mapsize,
  const GLfloat *values
);

void glPixelMapuiv(
  GLenum map,
  GLint mapsize,
  const GLuint *values
);

void glPixelMapusv(
  GLenum map,
  GLint mapsize,
  const GLushort *values
);

Parameters

map
A symbolic map name. The ten maps are as follows:
GL_PIXEL_MAP_I_TO_I
Maps color indexes to color indexes.
GL_PIXEL_MAP_S_TO_S
Maps stencil indexes to stencil indexes.
GL_PIXEL_MAP_I_TO_R
Maps color indexes to red components.
GL_PIXEL_MAP_I_TO_G
Maps color indexes to green components.
GL_PIXEL_MAP_I_TO_B
Maps color indexes to blue components.
GL_PIXEL_MAP_I_TO_A
Maps color indexes to alpha components.
GL_PIXEL_MAP_R_TO_R
Maps red components to red components.
GL_PIXEL_MAP_G_TO_G
Maps green components to green components.
GL_PIXEL_MAP_B_TO_B
Maps blue components to blue components.
GL_PIXEL_MAP_A_TO_A
Maps alpha components to alpha components.
mapsize
The size of the map being defined.
values
An array of mapsize values.

Remarks

The glPixelMap function sets up translation tables, or maps, used by glDrawPixels, glReadPixels, glCopyPixels, glTexImage1D, and glTexImage2D. Use of these maps is described completely in the glPixelTransfer topic, and partly in the topics for the pixel and texture image commands. Only the specification of the maps is described in this topic.

The map parameter is a symbolic map name, indicating one of ten maps to set. The mapsize parameter specifies the number of entries in the map, and values is a pointer to an array of mapsize map values.

The entries in a map can be specified as single-precision floating-point numbers, unsigned short integers, or unsigned long integers. Maps that store color component values (all but GL_PIXEL_MAP_I_TO_I and GL_PIXEL_MAP_S_TO_S) retain their values in floating-point format, with unspecified mantissa and exponent sizes. Floating-point values specified by glPixelMapfv are converted directly to the internal floating-point format of these maps, and then clamped to the range [0,1]. Unsigned integer values specified by glPixelMapusv and glPixelMapuiv are converted linearly such that the largest representable integer maps to 1.0, and zero maps to 0.0.

Maps that store indexes, GL_PIXEL_MAP_I_TO_I and GL_PIXEL_MAP_S_TO_S, retain their values in fixed-point format, with an unspecified number of bits to the right of the binary point. Floating-point values specified by glPixelMapfv are converted directly to the internal fixed-point format of these maps. Unsigned integer values specified by glPixelMapusv and glPixelMapuiv specify integer values, with all zeros to the right of the binary point.

The following table shows the initial sizes and values for each of the maps. Maps that are indexed by either color or stencil indexes must have mapsize = 2^n for some n or results are undefined. The maximum allowable size for each map depends on the implementation and can be determined by calling glGet with argument GL_MAX_PIXEL_MAP_TABLE. The single maximum applies to all maps, and it is at least 32.

GL_PIXEL_MAP_I_TO_I

Lookup Index: | color index
Lookup Value: | color index
Initial Size: | 1
Initial Value: | 0.0

GL_PIXEL_MAP_S_TO_S

Lookup Index: | stencil index
Lookup Value: | stencil index
Initial Size: | 1
Initial Value: | 0.0

GL_PIXEL_MAP_I_TO_R

Lookup Index: | color index
Lookup Value: | R
Initial Size: | 1
Initial Value: | 0.0

GL_PIXEL_MAP_I_TO_G

Lookup Index: | color index
Lookup Value: | G
Initial Size: | 1
Initial Value: | 0.0

GL_PIXEL_MAP_I_TO_B

Lookup Index: | color index
Lookup Value: | B
Initial Size: | 1
Initial Value: | 0.0

GL_PIXEL_MAP_I_TO_A

Lookup Index: | color index
Lookup Value: | A
Initial Size: | 1
Initial Value: | 0.0

GL_PIXEL_MAP_R_TO_R

Lookup Index: | R
Lookup Value: | R
Initial Size: | 1
Initial Value: | 0.0

GL_PIXEL_MAP_G_TO_G

Lookup Index: | G
Lookup Value: | G
Initial Size: | 1
Initial Value: | 0.0

GL_PIXEL_MAP_B_TO_B

Lookup Index: | B
Lookup Value: | B
Initial Size: | 1
Initial Value: | 0.0

GL_PIXEL_MAP_A_TO_A

Lookup Index: | A
Lookup Value: | A
Initial Size: | 1
Initial Value: | 0.0

The following functions retrieve information related to glPixelMap:

glGet with argument GL_PIXEL_MAP_I_TO_I_SIZE

glGet with argument GL_PIXEL_MAP_S_TO_S_SIZE

glGet with argument GL_PIXEL_MAP_I_TO_R_SIZE

glGet with argument GL_PIXEL_MAP_I_TO_G_SIZE

glGet with argument GL_PIXEL_MAP_I_TO_B_SIZE

glGet with argument GL_PIXEL_MAP_I_TO_A_SIZE

glGet with argument GL_PIXEL_MAP_R_TO_R_SIZE

glGet with argument GL_PIXEL_MAP_G_TO_G_SIZE

glGet with argument GL_PIXEL_MAP_B_TO_B_SIZE

glGet with argument GL_PIXEL_MAP_A_TO_A_SIZE

glGet with argument GL_MAX_PIXEL_MAP_TABLE

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | map was not an accepted value.
GL_INVALID_VALUE | mapsize was negative or larger than GL_MAX_PIXEL_MAP_TABLE.
GL_INVALID_VALUE | map was GL_PIXEL_MAP_I_TO_I, GL_PIXEL_MAP_S_TO_S, GL_PIXEL_MAP_I_TO_R, GL_PIXEL_MAP_I_TO_G, GL_PIXEL_MAP_I_TO_B, or GL_PIXEL_MAP_I_TO_A, and mapsize was not a power of two.
GL_INVALID_OPERATION | glPixelMap was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glCopyPixels, glDrawPixels, glEnd, glPixelStore, glPixelTransfer, glReadPixels, glTexImage1D, glTexImage2D


glPixelStore

glPixelStoref, glPixelStorei

The glPixelStoref and glPixelStorei functions set pixel storage modes.

void glPixelStoref(
  GLenum pname,
  GLfloat param
);

void glPixelStorei(
  GLenum pname,
  GLint param
);

Parameters

pname
The symbolic name of the parameter to be set. Six of the twelve storage parameters affect how pixel data is returned to client memory, and are therefore significant only for glReadPixels commands. They are as follows:
GL_PACK_SWAP_BYTES
If true, byte ordering for multibyte color components, depth components, color indexes, or stencil indexes is reversed. That is, if a four-byte component is made up of bytes b (0) , b (1) , b (2) , b (3) , it is stored in memory as b (3) , b (2) , b (1) , b (0) if GL_PACK_SWAP_BYTES is true. GL_PACK_SWAP_BYTES has no effect on the memory order of components within a pixel, only on the order of bytes within components or indexes. For example, the three components of a GL_RGB format pixel are always stored with red first, green second, and blue third, regardless of the value of GL_PACK_SWAP_BYTES.
GL_PACK_LSB_FIRST
If true, bits are ordered within a byte from least significant to most significant; otherwise, the first bit in each byte is the most significant one. This parameter is significant for bitmap data only.
GL_PACK_ROW_LENGTH
If greater than zero, GL_PACK_ROW_LENGTH defines the number of pixels in a row. If the first pixel of a row is placed at location p in memory, then the location of the first pixel of the next row is obtained by skipping

components or indexes, where n is the number of components or indexes in a pixel, l is the number of pixels in a row (GL_PACK_ROW_LENGTH if it is greater than zero, the width argument to the pixel routine otherwise), a is the value of GL_PACK_ALIGNMENT, and s is the size, in bytes, of a single component (if a < s, then it is as if a = s). In the case of 1-bit values, the location of the next row is obtained by skipping

components or indexes.

The word component in this description refers to the nonindex values red, green, blue, alpha, and depth. Storage format GL_RGB, for example, has three components per pixel: first red, then green, and finally blue.

GL_PACK_SKIP_PIXELS and GL_PACK_SKIP_ROWS
These values are provided as a convenience to the programmer; they provide no functionality that cannot be duplicated simply by incrementing the pointer passed to glReadPixels. Setting GL_PACK_SKIP_PIXELS to i is equivalent to incrementing the pointer by i n components or indexes, where n is the number of components or indexes in each pixel. Setting GL_PACK_SKIP_ROWS to j is equivalent to incrementing the pointer by j k components or indexes, where k is the number of components or indexes per row, as computed above in the GL_PACK_ROW_LENGTH section.
GL_PACK_ALIGNMENT
Specifies the alignment requirements for the start of each pixel row in memory. The allowable values are 1 (byte-alignment), 2 (rows aligned to even-numbered bytes), 4 (word alignment), and 8 (rows start on double-word boundaries).

The other six of the twelve storage parameters affect how pixel data is read from client memory. These values are significant for glDrawPixels, glTexImage1D, glTexImage2D, glBitmap, and glPolygonStipple. They are as follows:

GL_UNPACK_SWAP_BYTES
If true, byte ordering for multibyte color components, depth components, color indexes, or stencil indexes is reversed. That is, if a four-byte component is made up of bytes b (0) , b (1) , b (2) , b (3) , it is taken from memory as b (3) , b (2) , b (1) , b (0) if GL_UNPACK_SWAP_BYTES is true. GL_UNPACK_SWAP_BYTES has no effect on the memory order of components within a pixel, only on the order of bytes within components or indexes. For example, the three components of a GL_RGB format pixel are always stored with red first, green second, and blue third, regardless of the value of GL_UNPACK_SWAP_BYTES.
GL_UNPACK_LSB_FIRST
If true, bits are ordered within a byte from least significant to most significant; otherwise, the first bit in each byte is the most significant one. This is significant for bitmap data only.
GL_UNPACK_ROW_LENGTH
If greater than zero, GL_UNPACK_ROW_LENGTH defines the number of pixels in a row. If the first pixel of a row is placed at location p in memory, then the location of the first pixel of the next row is obtained by skipping

components or indexes, where n is the number of components or indexes in a pixel, l is the number of pixels in a row (GL_UNPACK_ROW_LENGTH if it is greater than zero, the width argument to the pixel routine otherwise), a is the value of GL_UNPACK_ALIGNMENT, and s is the size, in bytes, of a single component (if a < s, then it is as if a = s). In the case of 1-bit values, the location of the next row is obtained by skipping

components or indexes.

The word component in this description refers to the nonindex values red, green, blue, alpha, and depth. Storage format GL_RGB, for example, has three components per pixel: first red, then green, and finally blue.

GL_UNPACK_SKIP_PIXELS and GL_UNPACK_SKIP_ROWS
These values are provided as a convenience to the programmer; they provide no functionality that cannot be duplicated simply by incrementing the pointer passed to glDrawPixels, glTexImage1D, glTexImage2D, glBitmap, or glPolygonStipple. Setting GL_UNPACK_SKIP_PIXELS to i is equivalent to incrementing the pointer by i n components or indexes, where n is the number of components or indexes in each pixel. Setting GL_UNPACK_SKIP_ROWS to j is equivalent to incrementing the pointer by j k components or indexes, where k is the number of components or indexes per row, as computed above in the GL_UNPACK_ROW_LENGTH section.
GL_UNPACK_ALIGNMENT
Specifies the alignment requirements for the start of each pixel row in memory. The allowable values are 1 (byte-alignment), 2 (rows aligned to even-numbered bytes), 4 (word alignment), and 8 (rows start on double-word boundaries).
param
The value that pname is set to.

Remarks

The glPixelStore function sets pixel storage modes that affect the operation of subsequent glDrawPixels and glReadPixels as well as the unpacking of polygon stipple patterns (see glPolygonStipple), bitmaps (see glBitmap), and texture patterns (see glTexImage1D and glTexImage2D).

The following table gives the type, initial value, and range of valid values for each of the storage parameters that can be set with glPixelStore.

Pname | Type | Initial Value | Valid Range
GL_PACK_SWAP_BYTES | Boolean | false | true or false
GL_PACK_SWAP_BYTES | Boolean | false | true or false
GL_PACK_ROW_LENGTH | integer | 0 | [0,)
GL_PACK_SKIP_ROWS | integer | 0 | [0,)
GL_PACK_SKIP_PIXELS | integer | 0 | [0,)
GL_PACK_ALIGNMENT | integer | 4 | 1, 2, 4, or 8
GL_UNPACK_SWAP_BYTES | Boolean | false | true or false
GL_UNPACK_LSB_FIRST | Boolean | false | true or false
GL_UNPACK_ROW_LENGTH | integer | 0 | [0,)
GL_UNPACK_SKIP_ROWS | integer | 0 | [0,)
GL_UNPACK_SKIP_PIXELS | integer | 0 | [0,)
GL_UNPACK_ALIGNMENT | integer | 4 | 1, 2, 4, or 8

The glPixelStoref function can be used to set any pixel store parameter. If the parameter type is Boolean, and if param is 0.0, then the parameter is false; otherwise it is set to true. If pname is an integer type parameter, then param is rounded to the nearest integer.

Likewise, the glPixelStorei function can also be used to set any of the pixel store parameters. Boolean parameters are set to false if param is 0 and true otherwise. The param parameter is converted to floating point before being assigned to real-valued parameters.

The pixel storage modes in effect when glDrawPixels, glReadPixels, glTexImage1D, glTexImage2D, glBitmap, or glPolygonStipple is placed in a display list control the interpretation of memory data. The pixel storage modes in effect when a display list is executed are not significant.

The following functions retrieve information related to glPixelStore:

glGet with argument GL_PACK_SWAP_BYTES

glGet with argument GL_PACK_LSB_FIRST

glGet with argument GL_PACK_ROW_LENGTH

glGet with argument GL_PACK_SKIP_ROWS

glGet with argument GL_PACK_SKIP_PIXELS

glGet with argument GL_PACK_ALIGNMENT

glGet with argument GL_UNPACK_SWAP_BYTES

glGet with argument GL_UNPACK_LSB_FIRST

glGet with argument GL_UNPACK_ROW_LENGTH

glGet with argument GL_UNPACK_SKIP_ROWS

glGet with argument GL_UNPACK_SKIP_PIXELS

glGet with argument GL_UNPACK_ALIGNMENT

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | pname was not an accepted value.
GL_INVALID_VALUE | A negative row length, pixel skip, or row skip value was specified, or if alignment was specified as other than 1, 2, 4, or 8.
GL_INVALID_OPERATION | glPixelStore was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glBitmap, glDrawPixels, glEnd, glPixelMap, glPixelTransfer, glPixelZoom, glPolygonStipple, glReadPixels, glTexImage1D, glTexImage2D


glPixelTransfer

glPixelTransferf, glPixelTransferi

The glPixelTransferf and glPixelTransferi functions set pixel transfer modes.

void glPixelTransferf(
  GLenum pname,
  GLfloat param
);

void glPixelTransferi(
  GLenum pname,
  GLint param
);

Parameters

pname
The symbolic name of the pixel transfer parameter to be set. The following table gives the type, initial value, and range of valid values for each of the pixel transfer parameters that are set with glPixelTransfer.
Pname | Type | Initial Value | Valid Range
GL_MAP_COLOR | Boolean | false | true/false
GL_MAP_STENCIL | Boolean | false | true/false
GL_INDEX_SHIFT integer 0 ( – ,)
GL_INDEX_OFFSET integer 0 ( – ,)
GL_RED_SCALE float 1.0 ( – ,)
GL_GREEN_SCALE float 1.0 ( – ,)
GL_BLUE_SCALE float 1.0 ( – ,)
GL_ALPHA_SCALE float 1.0 ( – ,)
GL_DEPTH_SCALE float 1.0 ( – ,)
GL_RED_BIAS float 0.0 ( – ,)
GL_GREEN_BIAS float 0.0 ( – ,)
GL_BLUE_BIAS float 0.0 ( – ,)
GL_ALPHA_BIAS float 0.0 ( – ,)
GL_DEPTH_BIAS float 0.0 ( – ,)

param
The value that pname is set to.

Remarks

The glPixelTransfer function sets pixel transfer modes that affect the operation of subsequent glDrawPixels, glReadPixels, glCopyPixels, glTexImage1D, and glTexImage2D commands. The algorithms that are specified by pixel transfer modes operate on pixels after they are read from the frame buffer (glReadPixels and glCopyPixels) or unpacked from client memory (glDrawPixels, glTexImage1D, and glTexImage2D). Pixel transfer operations happen in the same order, and in the same manner, regardless of the command that resulted in the pixel operation. Pixel storage modes (glPixelStore) control the unpacking of pixels being read from client memory, and the packing of pixels being written back into client memory.

Pixel transfer operations handle four fundamental pixel types: color, color index, depth, and stencil. Color pixels are made up of four floating-point values with unspecified mantissa and exponent sizes, scaled such that 0.0 represents zero intensity and 1.0 represents full intensity. Color indexes comprise a single fixed-point value, with unspecified precision to the right of the binary point. Depth pixels comprise a single floating-point value, with unspecified mantissa and exponent sizes, scaled such that 0.0 represents the minimum depth buffer value, and 1.0 represents the maximum depth buffer value. Finally, stencil pixels comprise a single fixed-point value, with unspecified precision to the right of the binary point.

The pixel transfer operations performed on the four basic pixel types are as follows:

Color
Each of the four color components is multiplied by a scale factor, and then added to a bias factor. That is, the red component is multiplied by GL_RED_SCALE, and then added to GL_RED_BIAS; the green component is multiplied by GL_GREEN_SCALE, and then added to GL_GREEN_BIAS; the blue component is multiplied by GL_BLUE_SCALE, and then added to GL_BLUE_BIAS; and the alpha component is multiplied by GL_ALPHA_SCALE, and then added to GL_ALPHA_BIAS. After all four color components are scaled and biased, each is clamped to the range [0,1]. All color scale and bias values are specified with glPixelTransfer.

If GL_MAP_COLOR is true, each color component is scaled by the size of the corresponding color-to-color map, and then replaced by the contents of that map indexed by the scaled component. That is, the red component is scaled by GL_PIXEL_MAP_R_TO_R_SIZE, and then replaced by the contents of GL_PIXEL_MAP_R_TO_R indexed by itself. The green component is scaled by GL_PIXEL_MAP_G_TO_G_SIZE, and then replaced by the contents of GL_PIXEL_MAP_G_TO_G indexed by itself. The blue component is scaled by GL_PIXEL_MAP_B_TO_B_SIZE, and then replaced by the contents of GL_PIXEL_MAP_B_TO_B indexed by itself. The alpha component is scaled by GL_PIXEL_MAP_A_TO_A_SIZE, and then replaced by the contents of GL_PIXEL_MAP_A_TO_A indexed by itself. All components taken from the maps are then clamped to the range [0,1]. GL_MAP_COLOR is specified with glPixelTransfer. The contents of the various maps are specified with glPixelMap.

Color index
Each color index is shifted left by GL_INDEX_SHIFT bits, filling with zeros any bits beyond the number of fraction bits carried by the fixed-point index. If GL_INDEX_SHIFT is negative, the shift is to the right, again zero filled. GL_INDEX_OFFSET is then added to the index. GL_INDEX_SHIFT and GL_INDEX_OFFSET are specified with glPixelTransfer.

From this point, operation diverges depending on the required format of the resulting pixels. If the resulting pixels are to be written to a color-index buffer, or if they are being read back to client memory in GL_COLOR_INDEX format, the pixels continue to be treated as indexes. If GL_MAP_COLOR is true, then each index is masked by 2^n – 1, where n is GL_PIXEL_MAP_I_TO_I_SIZE, and then replaced by the contents of GL_PIXEL_MAP_I_TO_I indexed by the masked value. GL_MAP_COLOR is specified with glPixelTransfer. The contents of the index map are specified with glPixelMap.

If the resulting pixels are to be written to an RGBA color buffer, or if they are being read back to client memory in a format other than GL_COLOR_INDEX, the pixels are converted from indexes to colors by referencing the four maps GL_PIXEL_MAP_I_TO_R, GL_PIXEL_MAP_I_TO_G, GL_PIXEL_MAP_I_TO_B, and GL_PIXEL_MAP_I_TO_A. Before being dereferenced, the index is masked by 2n – 1, where n is GL_PIXEL_MAP_I_TO_R_SIZE for the red map, GL_PIXEL_MAP_I_TO_G_SIZE for the green map, GL_PIXEL_MAP_I_TO_B_SIZE for the blue map, and GL_PIXEL_MAP_I_TO_A_SIZE for the alpha map. All components taken from the maps are then clamped to the range [0,1]. The contents of the four maps are specified with glPixelMap.

Depth
Each depth value is multiplied by GL_DEPTH_SCALE, added to GL_DEPTH_BIAS, and then clamped to the range [0,1].
Stencil
Each index is shifted GL_INDEX_SHIFT bits just as a color index is, and then added to GL_INDEX_OFFSET. If GL_MAP_STENCIL is true, each index is masked by 2^n – 1, where n is GL_PIXEL_MAP_S_TO_S_SIZE, then replaced by the contents of GL_PIXEL_MAP_S_TO_S indexed by the masked value.

The glPixelTransferf function can be used to set any pixel transfer parameter. If the parameter type is Boolean, 0.0 implies false and any other value implies true. If pname is an integer parameter, param is rounded to the nearest integer.

Likewise, glPixelTransferi can also be used to set any of the pixel transfer parameters. Boolean parameters are set to false if param is 0 and true otherwise. The param parameter is converted to floating point before being assigned to real-valued parameters.

If a glDrawPixels, glReadPixels, glCopyPixels, glTexImage1D, or glTexImage2D command is placed in a display list (see glNewList and glCallList), the pixel transfer mode settings in effect when the display list is executed are the ones that are used. They may be different from the settings when the command was compiled into the display list.

The following functions retrieve information related to glPixelTransfer:

glGet with argument GL_MAP_COLOR

glGet with argument GL_MAP_STENCIL

glGet with argument GL_INDEX_SHIFT

glGet with argument GL_INDEX_OFFSET

glGet with argument GL_RED_SCALE

glGet with argument GL_RED_BIAS

glGet with argument GL_GREEN_SCALE

glGet with argument GL_GREEN_BIAS

glGet with argument GL_BLUE_SCALE

glGet with argument GL_BLUE_BIAS

glGet with argument GL_ALPHA_SCALE

glGet with argument GL_ALPHA_BIAS

glGet with argument GL_DEPTH_SCALE

glGet with argument GL_DEPTH_BIAS

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | pname was not an accepted value.
GL_INVALID_OPERATION | glPixelTransfer was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glCallList, glCopyPixels, glDrawPixels, glEnd, glNewList, glPixelMap, glPixelStore, glPixelZoom, glReadPixels, glTexImage1D, glTexImage2D


glPixelZoom

The glPixelZoom function specifies the pixel zoom factors.

void glPixelZoom(
  GLfloat xfactor,
  GLfloat yfactor
);

Parameters

xfactor, yfactor
The x and y zoom factors for pixel write operations.

Remarks

The glPixelZoom function specifies values for the x and y zoom factors. During the execution of glDrawPixels or glCopyPixels, if (x (r) , y (r) ) is the current raster position, and a given element is in the nth row and mth column of the pixel rectangle, then pixels whose centers are in the rectangle with corners at

are candidates for replacement. Any pixel whose center lies on the bottom or left edge of this rectangular region is also modified.

Pixel zoom factors are not limited to positive values. Negative zoom factors reflect the resulting image about the current raster position.

The following functions retrieve information related to glPixelZoom:

glGet with argument GL_ZOOM_X

glGet with argument GL_ZOOM_Y

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glPixelZoom was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glCopyPixels, glDrawPixels, glEnd


glPointSize

The glPointSize function specifies the diameter of rasterized points.

void glPointSize(
  GLfloat size
);

Parameters

size
The diameter of rasterized points. The default is 1.0.

Remarks

The glPointSize function specifies the rasterized diameter of both aliased and antialiased points. Using a point size other than 1.0 has different effects, depending on whether point antialiasing is enabled. Point antialiasing is controlled by calling glEnable and glDisable with argument GL_POINT_SMOOTH.

If point antialiasing is disabled, the actual size is determined by rounding the supplied size to the nearest integer. (If the rounding results in the value 0, it is as if the point size were 1.) If the rounded size is odd, then the center point (x, y) of the pixel fragment that represents the point is computed as

( ëx (w) û + .5, ëy (w) û + .5)

where w subscripts indicate window coordinates. All pixels that lie within the square grid of the rounded size centered at (x, y) make up the fragment. If the size is even, the center point is

( ëx (w) + .5û, ëy (w) + .5û )

and the rasterized fragment's centers are the half-integer window coordinates within the square of the rounded size centered at (x, y). All pixel fragments produced in rasterizing a nonantialiased point are assigned the same associated data; that of the vertex corresponding to the point.

If antialiasing is enabled, then point rasterization produces a fragment for each pixel square that intersects the region lying within the circle having diameter equal to the current point size and centered at the points (x (w) , y (w) ). The coverage value for each fragment is the window coordinate area of the intersection of the circular region with the corresponding pixel square. This value is saved and used in the final rasterization step. The data associated with each fragment is the data associated with the point being rasterized.

Not all sizes are supported when point antialiasing is enabled. If an unsupported size is requested, the nearest supported size is used. Only size 1.0 is guaranteed to be supported; others depend on the implementation. The range of supported sizes and the size difference between supported sizes within the range can be queried by calling glGet with arguments GL_POINT_SIZE_RANGE and GL_POINT_SIZE_GRANULARITY.

The point size specified by glPointSize is always returned when GL_POINT_SIZE is queried. Clamping and rounding for aliased and antialiased points have no effect on the specified value.

Non-antialiased point size may be clamped to an implementation-dependent maximum. Although this maximum cannot be queried, it must be no less than the maximum value for antialiased points, rounded to the nearest integer value.

The following functions retrieve information related to glPointSize:

glGet with argument GL_POINT_SIZE

glGet with argument GL_POINT_SIZE_RANGE

glGet with argument GL_POINT_SIZE_GRANULARITY

glIsEnabled with argument GL_POINT_SMOOTH

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_VALUE | size was less than or equal to zero.
GL_INVALID_OPERATION | glPointSize was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnable, glEnd, glIsEnabled


glPolygonMode

The glPolygonMode function selects a polygon rasterization mode.

void glPolygonMode(
  GLenum face,
  GLenum mode
);

Parameters

face
The polygons that mode applies to. Must be GL_FRONT for front-facing polygons, GL_BACK for back-facing polygons, or GL_FRONT_AND_BACK for front- and back-facing polygons.
mode
The way polygons will be rasterized. The following modes are defined and can be specified in mode. The default is GL_FILL for both front- and back-facing polygons.
GL_POINT
Polygon vertices that are marked as the start of a boundary edge are drawn as points. Point attributes such as GL_POINT_SIZE and GL_POINT_SMOOTH control the rasterization of the points. Polygon rasterization attributes other than GL_POLYGON_MODE have no effect.
GL_LINE
Boundary edges of the polygon are drawn as line segments. They are treated as connected line segments for line stippling; the line stipple counter and pattern are not reset between segments (see glLineStipple). Line attributes such as GL_LINE_WIDTH and GL_LINE_SMOOTH control the rasterization of the lines. Polygon rasterization attributes other than GL_POLYGON_MODE have no effect.
GL_FILL
The interior of the polygon is filled. Polygon attributes such as GL_POLYGON_STIPPLE and GL_POLYGON_SMOOTH control the rasterization of the polygon.

Remarks

The glPolygonMode function controls the interpretation of polygons for rasterization. The face parameter describes which polygons mode applies to: front-facing polygons (GL_FRONT), back-facing polygons (GL_BACK), or both (GL_FRONT_AND_BACK). The polygon mode affects only the final rasterization of polygons. In particular, a polygon's vertices are lit and the polygon is clipped and possibly culled before these modes are applied.

To draw a surface with filled back-facing polygons and outlined front-facing polygons, call

glPolygonMode(GL_FRONT, GL_LINE);

Vertices are marked as boundary or nonboundary with an edge flag. Edge flags are generated internally by OpenGL when it decomposes polygons, and they can be set explicitly using glEdgeFlag.

The following function retrieves information related to glPolygonMode:

glGet with argument GL_POLYGON_MODE

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | Either face or mode was not an accepted value.
GL_INVALID_OPERATION | glPolygonMode was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEdgeFlag, glEnd, glLineStipple, glLineWidth, glPointSize, glPolygonStipple


glPolygonStipple

The glPolygonStipple function sets the polygon stippling pattern.

void glPolygonStipple(
  const GLubyte *mask
);

Parameters

mask
A pointer to a 32x32 stipple pattern that will be unpacked from memory in the same way that glDrawPixels unpacks pixels.

Remarks

The glPolygonStipple function sets the polygon stippling pattern. Polygon stippling, like line stippling (see glLineStipple), masks out certain fragments produced by rasterization, creating a pattern. Stippling is independent of polygon antialiasing.

The mask parameter is a pointer to a 32x32 stipple pattern that is stored in memory just like the pixel data supplied to glDrawPixels with height and width both equal to 32, a pixel format of GL_COLOR_INDEX, and data type of GL_BITMAP. That is, the stipple pattern is represented as a 32x32 array of 1-bit color indexes packed in unsigned bytes. The glPixelStore function parameters, such as GL_UNPACK_SWAP_BYTES and GL_UNPACK_LSB_FIRST, affect the assembling of the bits into a stipple pattern. Pixel transfer operations (shift, offset, and pixel map) are not applied to the stipple image, however.

Polygon stippling is enabled and disabled with glEnable and glDisable, using argument GL_POLYGON_STIPPLE. If enabled, a rasterized polygon fragment with window coordinates x (w) and y (w) is sent to the next stage of OpenGL if and only if the (x (w) mod 32)th bit in the (y (w) mod 32)th row of the stipple pattern is one. When polygon stippling is disabled, it is as if the stipple pattern were all ones.

The following functions retrieve information related to glPolygonStipple:

glGetPolygonStipple

glIsEnabled with argument GL_POLYGON_STIPPLE

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glPolygonStipple was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glDrawPixels, glEnd, glLineStipple, glPixelStore, glPixelTransfer


glPrioritizeTextures

The glPrioritizeTextures function sets the residence priority of textures.

void glPrioritizeTextures(
  GLsizei n,
  GLuint * textures,
  GLclampf * priorities
);

Parameters

n
The number of textures to be prioritized.
textures
A pointer to the first element of an array containing the names of the textures to be prioritized.
priorities
A pointer to the first element of an array containing the texture priorities. A priority given in an element of the priorities parameter applies to the texture named by the corresponding element of the textures parameter.

Remarks

The glPrioritizeTextures function assigns the n texture priorities specified in the priorities parameter to the n textures named in the textures parameter. On machines with a limited amount of texture memory, OpenGL establishes a ``working set'' of textures that are resident in texture memory. These textures can be bound to a texture target much more efficiently than textures that are not resident.

By specifying a priority for each texture, the glPrioritizeTextures function enables you to determine which textures should be resident.

The texture priorities elements in priorities are clamped to the range [0.0, 1.0] before being assigned. Zero indicates the lowest priority; thus textures with priority zero are least likely to be resident. The value 1.0 indicates the highest priority; thus textures with priority 1.0 are most likely to be resident. However, textures are not guaranteed to be resident until they are bound.

The glPrioritizeTextures function ignores attempts to prioritize textures with a priorities value of zero or any texture name that does not correspond to an existing texture. None of the functions named by the textures parameter need to be bound to a texture target.

If a texture is currently bound, you can also use the glTexParameter function to set its priority. This is the only way to set the priority of a default texture.

You can include glPrioritizeTextures in display lists.

Note The glPrioritizeTextures function is only available in OpenGL version 1.1 or later.

The following function retrieves the priority of a currently-bound texture related to glPrioritizeTextures:

glGetTexParameter with parameter name GL_TEXTURE_PRIORITY.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_VALUE | n was a negative value.
GL_INVALID_OPERATION | glPrioritizeTextures was called between a call to glBegin and the corresponding call to glEnd.

See Also

glAreTexturesResident, glBegin, glEnd, glGetTexParameter, glTexImage1D, glTexImage2D, glTexParameter


glPushAttrib, glPopAttrib

The glPushAttrib and glPopAttrib functions push and pop the attribute stack.

void glPushAttrib(
  GLbitfield mask
);

Parameters

mask
A mask that indicates which attributes to save. The symbolic mask constants and their associated OpenGL state are as follows (the indented paragraphs list which attributes are saved):
GL_ACCUM_BUFFER_BIT
Accumulation buffer clear value
GL_COLOR_BUFFER_BIT
GL_ALPHA_TEST enable bit

Alpha test function and reference value

GL_BLEND enable bit

Blending source and destination functions

GL_DITHER enable bit

GL_DRAW_BUFFER setting

GL_LOGIC_OP enable bit

Logic op function

Color-mode and index-mode clear values

Color-mode and index-mode writemasks

GL_CURRENT_BIT
Current RGBA color

Current color index

Current normal vector

Current texture coordinates

Current raster position

GL_CURRENT_RASTER_POSITION_VALID flag

RGBA color associated with current raster position

Color index associated with current raster position

Texture coordinates associated with current raster position

GL_EDGE_FLAG flag

GL_DEPTH_BUFFER_BIT
GL_DEPTH_TEST enable bit

Depth buffer test function

Depth buffer clear value

GL_DEPTH_WRITEMASK enable bit

GL_ENABLE_BIT
GL_ALPHA_TEST flag

GL_AUTO_NORMAL flag

GL_BLEND flag

Enable bits for the user-definable clipping planes

GL_COLOR_MATERIAL

GL_CULL_FACE flag

GL_DEPTH_TEST flag

GL_DITHER flag

GL_FOG flag

GL_LIGHTi where 0 <= i < GL_MAX_LIGHTS

GL_LIGHTING flag

GL_LINE_SMOOTH flag

GL_LINE_STIPPLE flag

GL_LOGIC_OP flag

GL_MAP1_x where x is a map type

GL_MAP2_x where x is a map type

GL_NORMALIZE flag

GL_POINT_SMOOTH flag

GL_POLYGON_SMOOTH flag

GL_POLYGON_STIPPLE flag

GL_SCISSOR_TEST flag

GL_STENCIL_TEST flag

GL_TEXTURE_1D flag

GL_TEXTURE_2D flag

Flags GL_TEXTURE_GEN_x where x is S, T, R, or Q

GL_EVAL_BIT
GL_MAP1_x enable bits, where x is a map type

GL_MAP2_x enable bits, where x is a map type

1-D grid endpoints and divisions

2-D grid endpoints and divisions

GL_AUTO_NORMAL enable bit

GL_FOG_BIT
GL_FOG enable flag

Fog color

Fog density

Linear fog start

Linear fog end

Fog index

GL_FOG_MODE value

GL_HINT_BIT
GL_PERSPECTIVE_CORRECTION_HINT setting

GL_POINT_SMOOTH_HINT setting

GL_LINE_SMOOTH_HINT setting

GL_POLYGON_SMOOTH_HINT setting

GL_FOG_HINT setting

GL_LIGHTING_BIT
GL_COLOR_MATERIAL enable bit

GL_COLOR_MATERIAL_FACE value

Color material parameters that are tracking the current color

Ambient scene color

GL_LIGHT_MODEL_LOCAL_VIEWER value

GL_LIGHT_MODEL_TWO_SIDE setting

GL_LIGHTING enable bit

Enable bit for each light

Ambient, diffuse, and specular intensity for each light

Direction, position, exponent, and cutoff angle for each light

Constant, linear, and quadratic attenuation factors for each light

Ambient, diffuse, specular, and emissive color for each material

Ambient, diffuse, and specular color indexes for each material

Specular exponent for each material

GL_SHADE_MODEL setting

GL_LINE_BIT
GL_LINE_SMOOTH flag

GL_LINE_STIPPLE enable bit

Line stipple pattern and repeat counter

Line width

GL_LIST_BIT
GL_LIST_BASE setting
GL_PIXEL_MODE_BIT
GL_RED_BIAS and GL_RED_SCALE settings

GL_GREEN_BIAS and GL_GREEN_SCALE values

GL_BLUE_BIAS and GL_BLUE_SCALE

GL_ALPHA_BIAS and GL_ALPHA_SCALE

GL_DEPTH_BIAS and GL_DEPTH_SCALE

GL_INDEX_OFFSET and GL_INDEX_SHIFT values

GL_MAP_COLOR and GL_MAP_STENCIL flags

GL_ZOOM_X and GL_ZOOM_Y factors

GL_READ_BUFFER setting

GL_POINT_BIT
GL_POINT_SMOOTH flag

Point size

GL_POLYGON_BIT
GL_CULL_FACE enable bit

GL_CULL_FACE_MODE value

GL_FRONT_FACE indicator

GL_POLYGON_MODE setting

GL_POLYGON_SMOOTH flag

GL_POLYGON_STIPPLE enable bit

GL_POLYGON_STIPPLE_BIT
Polygon stipple image
GL_SCISSOR_BIT
GL_SCISSOR_TEST flag

Scissor box

GL_STENCIL_BUFFER_BIT
GL_STENCIL_TEST enable bit

Stencil function and reference value

Stencil value mask

Stencil fail, pass, and depth buffer pass actions

Stencil buffer clear value

Stencil buffer writemask

GL_TEXTURE_BIT
Enable bits for the four texture coordinates

Border color for each texture image

Minification function for each texture image

Magnification function for each texture image

Texture coordinates and wrap mode for each texture image

Color and mode for each texture environment

Enable bits GL_TEXTURE_GEN_x; x is S, T, R, and Q

GL_TEXTURE_GEN_MODE setting for S, T, R, and Q

glTexGen plane equations for S, T, R, and Q

GL_TRANSFORM_BIT
Coefficients of the six clipping planes

Enable bits for the user-definable clipping planes

GL_MATRIX_MODE value

GL_NORMALIZE flag

GL_VIEWPORT_BIT
Depth range (near and far)

Viewport origin and extent

void glPopAttrib(
  void
);

Remarks

The glPushAttrib function takes one argument, a mask that indicates which groups of state variables to save on the attribute stack. Symbolic constants are used to set bits in the mask. The mask parameter is typically constructed by ORing several of these constants together. The special mask GL_ALL_ATTRIB_BITS can be used to save all stackable states.

The glPopAttrib function restores the values of the state variables saved with the last glPushAttrib command. Those not saved are left unchanged.

It is an error to push attributes onto a full stack, or to pop attributes off an empty stack. In either case, the error flag is set and no other change is made to the OpenGL state.

Initially, the attribute stack is empty.

Not all values for the OpenGL state can be saved on the attribute stack. For example, pixel pack and unpack state, render mode state, and select and feedback state cannot be saved.

The depth of the attribute stack depends on the implementation, but it must be at least 16.

The following functions retrieve information related to glPushAttrib and glPopAttrib:

glGet with argument GL_ATTRIB_STACK_DEPTH

glGet with argument GL_MAX_ATTRIB_STACK_DEPTH

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_STACK_OVERFLOW | glPushAttrib was called while the attribute stack was full.
GL_STACK_UNDERFLOW | glPopAttrib was called while the attribute stack was empty.
GL_INVALID_OPERATION | glPushAttrib was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glGet, glGetClipPlane, glGetError, glGetLight, glGetMap, glGetMaterial, glGetPixelMap, glGetPolygonStipple, glGetString, glGetTexEnv, glGetTexGen, glGetTexImage, glGetTexLevelParameter, glGetTexParameter, glIsEnabled


glPushClientAttrib, glPopClientAttrib

The glPushClientAttrib and glPopClientAttrib functions save and restore groups of client-state variables on the client-attribute stack.

void glPushClientAttrib(
  GLbitfield mask
);

Parameters

mask
A mask that indicates which attributes to save. The following are the symbolic mask constants and their associated OpenGL client state:
GL_CLIENT_PIXEL_STORE_BIT
Pixel storage mode attributes.
GL_CLIENT_VERTEX_ARRAY_BIT
Vertex array attributes.
GL_CLIENT_ALL_ATTRIB_BITS
All stackable client-state attributes.
void glPopClientAttrib(
  void void
);

Remarks

The glPushClientAttrib function uses its mask parameter to determine which groups of client-state variables are saved on the client-attribute stack. You can OR together accepted symbolic constants to set bits and construct a mask.

The glPopClientAttrib function restores the values of the client-state variables last saved with glPushClientAttrib. Client-state variables not previously saved are left unchanged. Pushing attributes onto a full client-attribute stack or popping attributes off an empty stack sets an error flag and no other change is made to the OpenGL state. By default the client attribute stack is empty.

Some OpenGL client-state values cannot be saved on the client-attribute stack. For example, you cannot save the select or feedback states on the client-attribute stack. The depth of the client-attribute stack is at least 16.

The glPushClientAttrib and glPopClientAttrib functions are not compiled into display lists, but are executed immediately.

The glPushClientAttrib and glPopClientAttrib functions can only push and pop pixel storage modes and vertex array client states. You must use glPushAttrib and glPopAttrib to push and pop states that are kept on the server.

Note The glPushClientAttrib and glPopClientAttrib functions are only available in OpenGL version 1.1 or later.

The following functions retrieve information related to glPushClientAttrib and glPopClientAttrib:

glGet with argument GL_CLIENT_ATTRIB_STACK_DEPTH

glGet with argument GL_MAX_CLIENT_ATTRIB_STACK_DEPTH

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_STACK_OVERFLOW | glPushClientAttrib was called while the client-attribute stack was full.
GL_STACK_UNDERFLOW | glPopClientAttrib was called while the client-attribute stack was empty.

See Also

glColorPointer, glDisableClientState, glEdgeFlagPointer, glEnableClientState, glGet, glGetError, glIndexPointer, glNormalPointer, glNewList, glPixelStore, glPushAttrib, glTexCoordPointer, glVertexPointer


glPushMatrix, glPopMatrix

The glPushMatrix and glPopMatrix functions push and pop the current matrix stack.

void glPushMatrix(
  void
);

void glPopMatrix(
  void
);

Remarks

There is a stack of matrices for each of the matrix modes. In GL_MODELVIEW mode, the stack depth is at least 32. In the other two modes, GL_PROJECTION and GL_TEXTURE, the depth is at least 2. The current matrix in any mode is the matrix on the top of the stack for that mode.

The glPushMatrix function pushes the current matrix stack down by one, duplicating the current matrix. That is, after a glPushMatrix call, the matrix on the top of the stack is identical to the one below it. The glPushMatrix function pops the current matrix stack, replacing the current matrix with the one below it on the stack. Initially, each of the stacks contains one matrix, an identity matrix.

The following functions retrieve information related to glPushMatrix and glPopMatrix:

glGet with argument GL_MATRIX_MODE

glGet with argument GL_MODELVIEW_MATRIX

glGet with argument GL_PROJECTION_MATRIX

glGet with argument GL_TEXTURE_MATRIX

glGet with argument GL_MODELVIEW_STACK_DEPTH

glGet with argument GL_PROJECTION_STACK_DEPTH

glGet with argument GL_TEXTURE_STACK_DEPTH

glGet with argument GL_MAX_MODELVIEW_STACK_DEPTH

glGet with argument GL_MAX_PROJECTION_STACK_DEPTH

glGet with argument GL_MAX_TEXTURE_STACK_DEPTH

Error Codes

It is an error to push a full matrix stack, or to pop a matrix stack that contains only a single matrix. In either case, the error flag is set and no other change is made to the OpenGL state.

The following are the error codes generated and their conditions.

Error Code | Condition
GL_STACK_OVERFLOW | glPushMatrix was called while the current matrix stack was full.
GL_STACK_UNDERFLOW | glPopMatrix was called while the current matrix stack contained only a single matrix.
GL_INVALID_OPERATION | glPushMatrix was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glFrustum, glLoadIdentity, glLoadMatrix, glMatrixMode, glMultMatrix, glOrtho, glRotate, glScale, glTranslate, glViewport


glPushName, glPopName

The glPushName and glPopName functions push and pop the name stack.

void glPushName(
  GLuint name
);

Parameters

name
A name that will be pushed onto the name stack.
void glPopName(
  void
);

Remarks

The glPushName function causes name to be pushed onto the name stack, which is initially empty. The glPopName function pops one name off the top of the stack. The name stack is used during selection mode to allow sets of rendering commands to be uniquely identified. It consists of an ordered set of unsigned integers.

The name stack is always empty while the render mode is not GL_SELECT. Calls to glPushName or glPopName while the render mode is not GL_SELECT are ignored.

The following functions retrieve information related to glPushName and glPopName:

glGet with argument GL_NAME_STACK_DEPTH

glGet with argument GL_MAX_NAME_STACK_DEPTH

Error Codes

It is an error to push a name onto a full stack, or to pop a name off an empty stack. It is also an error to manipulate the name stack between a call to glBegin and the corresponding call to glEnd. In any of these cases, the error flag is set and no other change is made to the OpenGL state.

The following are the error codes generated and their conditions.

Error Code | Condition
GL_STACK_OVERFLOW | glPushName was called while the name stack was full.
GL_STACK_UNDERFLOW | glPopName was called while the name stack was empty.
GL_INVALID_OPERATION | glPushName or glPopName was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glInitNames, glLoadName, glRenderMode, glSelectBuffer


glRasterPos

glRasterPos2d, glRasterPos2f, glRasterPos2i, glRasterPos2s, glRasterPos3d, glRasterPos3f, glRasterPos3i, glRasterPos3s, glRasterPos4d, glRasterPos4f, glRasterPos4i, glRasterPos4s, glRasterPos2dv, glRasterPos2fv, glRasterPos2iv, glRasterPos2sv, glRastePos3dv, glRasterPos3fv, glRasterPos3iv, glRasterPos3sv, glRasterPos4dv, glRasterPos4fv, glRasterPos4iv, glRasterPos4sv

These functions specify the raster position for pixel operations.

void glRasterPos2d(
  GLdouble x,
  GLdouble y
);

void glRasterPos2f(
  GLfloat x,
  GLfloat y
);

void glRasterPos2i(
  GLint x,
  GLint y
);

void glRasterPos2s(
  GLshort x,
  GLshort y
);

void glRasterPos3d(
  GLdouble x,
  GLdouble y,
  GLdouble z
);

void glRasterPos3f(
  GLfloat x,
  GLfloat y,
  GLfloat z
);

void glRasterPos3i(
  GLint x,
  GLint y,
  GLint z
);

void glRasterPos3s(
  GLshort x,
  GLshort y,
  GLshort z
);

void glRasterPos4d(
  GLdouble x,
  GLdouble y,
  GLdouble z,
  GLdouble w
);

void glRasterPos4f(
  GLfloat x,
  GLfloat y,
  GLfloat z,
  GLfloat w
);

void glRasterPos4i(
  GLint x,
  GLint y,
  GLint z,
  GLint w
);

void glRasterPos4s(
  GLshort x,
  GLshort y,
  GLshort z,
  GLshort w
);

Parameters

x, y, z, w
The x, y, z, and w object coordinates (if present) for the raster position.
void glRasterPos2dv(
  const GLdouble *v
);

void glRasterPos2fv(
  const GLfloat *v
);

void glRasterPos2iv(
  const GLint *v
);

void glRasterPos2sv(
  const GLshort *v
);

void glRasterPos3dv(
  const GLdouble *v
);

void glRasterPos3fv(
  const GLfloat *v
);

void glRasterPos3iv(
  const GLint *v
);

void glRasterPos3sv(
  const GLshort *v
);

void glRasterPos4dv(
  const GLdouble *v
);

void glRasterPos4fv(
  const GLfloat *v
);

void glRasterPos4iv(
  const GLint *v
);

void glRasterPos4sv(
  const GLshort *v
);

Parameters

v
A pointer to an array of two, three, or four elements, specifying x, y, z, and w coordinates, respectively.

Remarks

OpenGL maintains a 3-D position in window coordinates. This position, called the raster position, is maintained with subpixel accuracy. It is used to position pixel and bitmap write operations. See glBitmap, glDrawPixels, and glCopyPixels.

The current raster position consists of three window coordinates (x, y, z), a clip coordinate w value, an eye coordinate distance, a valid bit, and associated color data and texture coordinates. The w coordinate is a clip coordinate, because w is not projected to window coordinates. The glRasterPos4 function specifies object coordinates x, y, z, and w explicitly. The glRasterPos3 function specifies object coordinates x, y, and z explicitly, while w is implicitly set to one. The glRasterPos2 function uses the argument values for x and y while implicitly setting z and w to zero and one.

The object coordinates presented by glRasterPos are treated just like those of a glVertex command. They are transformed by the current modelview and projection matrices and passed to the clipping stage. If the vertex is not culled, then it is projected and scaled to window coordinates, which become the new current raster position, and the GL_CURRENT_RASTER_POSITION_VALID flag is set. If the vertex is culled, then the valid bit is cleared and the current raster position and associated color and texture coordinates are undefined.

The current raster position also includes some associated color data and texture coordinates. If lighting is enabled, then GL_CURRENT_RASTER_COLOR, in RGBA mode, or the GL_CURRENT_RASTER_INDEX, in color-index mode, is set to the color produced by the lighting calculation (see glLight, glLightModel, and glShadeModel). If lighting is disabled, current color (in RGBA mode, state variable GL_CURRENT_COLOR) or color index (in color-index mode, state variable GL_CURRENT_INDEX) is used to update the current raster color.

Likewise, GL_CURRENT_RASTER_TEXTURE_COORDS is updated as a function of GL_CURRENT_TEXTURE_COORDS, based on the texture matrix and the texture generation functions (see glTexGen). Finally, the distance from the origin of the eye coordinate system to the vertex, as transformed by only the modelview matrix, replaces GL_CURRENT_RASTER_DISTANCE.

Initially, the current raster position is (0,0,0,1), the current raster distance is 0, the valid bit is set, the associated RGBA color is (1,1,1,1), the associated color index is 1, and the associated texture coordinates are (0, 0, 0, 1). In RGBA mode, GL_CURRENT_RASTER_INDEX is always 1; in color-index mode, the current raster RGBA color always maintains its initial value.

Note The raster position is modified both by glRasterPos and by glBitmap.

When the raster position coordinates are invalid, drawing commands that are based on the raster position are ignored (that is, they do not result in changes to the OpenGL state).

The following functions retrieve information related to glRasterPos:

glGet with argument GL_CURRENT_RASTER_POSITION

glGet with argument GL_CURRENT_RASTER_POSITION_VALID

glGet with argument GL_CURRENT_RASTER_DISTANCE

glGet with argument GL_CURRENT_RASTER_COLOR

glGet with argument GL_CURRENT_RASTER_INDEX

glGet with argument GL_CURRENT_RASTER_TEXTURE_COORDS

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glRasterPos was called between a call to glBegin and the corresponding call to glEnd.

See Also

glBegin, glBitmap, glCopyPixels, glDrawPixels, glEnd, glLight, glLightModel, glShadeModel, glTexCoord, glTexGen, glVertex


glReadBuffer

The glReadBuffer function selects a color buffer source for pixels.

void glReadBuffer(
  GLenum mode
);

Parameters

mode
A color buffer. Accepted values are GL_FRONT_LEFT, GL_FRONT_RIGHT, GL_BACK_LEFT, GL_BACK_RIGHT, GL_FRONT, GL_BACK, GL_LEFT, GL_RIGHT, and GL_AUXi, where i is between 0 and GL_AUX_BUFFERS –1.

Remarks

The glReadBuffer function specifies a color buffer as the source for subsequent glReadPixels and glCopyPixels commands. The mode parameter accepts one of twelve or more predefined values. (GL_AUX0 through GL_AUX3 are always defined.) In a fully configured system, GL_FRONT, GL_LEFT, and GL_FRONT_LEFT all name the front-left buffer, GL_FRONT_RIGHT and GL_RIGHT name the front-right buffer, and GL_BACK_LEFT and GL_BACK name the back-left buffer.

Nonstereo double-buffered configurations have only a front-left and a back-left buffer. Single-buffered configurations have a front-left and a front-right buffer if stereo, and only a front-left buffer if nonstereo. It is an error to specify a nonexistent buffer to glReadBuffer.

By default, mode is GL_FRONT in single-buffered configurations, and GL_BACK in double-buffered configurations.

The following function retrieves information related to glReadBuffer:

glGet with argument GL_READ_BUFFER

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | mode was not one of the twelve (or more) accepted values.
GL_INVALID_OPERATION | mode specified a buffer that does not exist.
GL_INVALID_OPERATION | glReadBuffer was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glCopyPixels, glDrawBuffer, glEnd, glReadPixels


glReadPixels

The glReadPixels function reads a block of pixels from the frame buffer.

void glReadPixels(
  GLint x,
  GLint y,
  GLsizei width,
  GLsizei height,
  GLenum format,
  GLenum type,
  GLvoid *pixels
);

Parameters

x, y
The window coordinates of the first pixel that is read from the frame buffer. This location is the lower-left corner of a rectangular block of pixels.
width, height
The dimensions of the pixel rectangle. The width and height parameters of one correspond to a single pixel.
format
The format of the pixel data. The following symbolic values are accepted:
GL_COLOR_INDEX
Color indexes are read from the color buffer selected by glReadBuffer. Each index is converted to fixed point, shifted left or right depending on the value and sign of GL_INDEX_SHIFT, and added to GL_INDEX_OFFSET. If GL_MAP_COLOR is GL_TRUE, indexes are replaced by their mappings in the table GL_PIXEL_MAP_I_TO_I.
GL_STENCIL_INDEX
Stencil values are read from the stencil buffer. Each index is converted to fixed point, shifted left or right depending on the value and sign of GL_INDEX_SHIFT, and added to GL_INDEX_OFFSET. If GL_MAP_STENCIL is GL_TRUE, indexes are replaced by their mappings in the table GL_PIXEL_MAP_S_TO_S.
GL_DEPTH_COMPONENT
Depth values are read from the depth buffer. Each component is converted to floating point such that the minimum depth value maps to 0.0 and the maximum value maps to 1.0. Each component is then multiplied by GL_DEPTH_SCALE, added to GL_DEPTH_BIAS, and finally clamped to the range [0,1].
GL_RED
GL_GREEN
GL_BLUE
GL_ALPHA
GL_RGB
GL_RGBA
GL_BGR_EXT
GL_BGRA_EXT
GL_LUMINANCE
GL_LUMINANCE_ALPHA
Processing differs depending on whether color buffers store color indexes or RGBA color components. If color indexes are stored, they are read from the color buffer selected by glReadBuffer. Each index is converted to fixed point, shifted left or right depending on the value and sign of GL_INDEX_SHIFT, and added to GL_INDEX_OFFSET. Indexes are then replaced by the red, green, blue, and alpha values obtained by indexing the GL_PIXEL_MAP_I_TO_R, GL_PIXEL_MAP_I_TO_G, GL_PIXEL_MAP_I_TO_B, and GL_PIXEL_MAP_I_TO_A tables.

If RGBA color components are stored in the color buffers, they are read from the color buffer selected by glReadBuffer. Each color component is converted to floating point such that zero intensity maps to 0.0 and full intensity maps to 1.0. Each component is then multiplied by GL_c_SCALE and added to GL_c_BIAS, where c is GL_RED, GL_GREEN, GL_BLUE, and GL_ALPHA. Each component is clamped to the range [0,1]. Finally, if GL_MAP_COLOR is GL_TRUE, each color component c is replaced by its mapping in the table GL_PIXEL_MAP_c_TO_c, where c again is GL_RED, GL_GREEN, GL_BLUE, and GL_ALPHA. Each component is scaled to the size of its corresponding table before the lookup is performed.

Finally, unneeded data is discarded. For example, GL_RED discards the green, blue, and alpha components, while GL_RGB discards only the alpha component. GL_LUMINANCE computes a single component value as the sum of the red, green, and blue components, and GL_LUMINANCE_ALPHA does the same, while keeping alpha as a second value.

type
The data type of the pixel data. Must be one of the following values:
Type | Index Mask | Component Conversion
GL_UNSIGNED_BYTE | 2^8–1 | (2^8–1)c
GL_BYTE | 2^7–1 | [2^7–1]c-1]/2
GL_BITMAP | 1 | 1
GL_UNSIGNED_SHORT | 2^16–1 | (2^16–1) c
GL_SHORT | 2^15–1 | [(2^15–1) c–1] / 2
GL_UNSIGNED_INT | 2^32–1 | (2^32–1) c
GL_INT | 2^31–1 | [(2^31–1) c–1] / 2
GL_FLOAT | none | c

pixels
Returns the pixel data.

Remarks

The glReadPixels function returns pixel data from the frame buffer, starting with the pixel whose lower-left corner is at location (x, y), into client memory starting at location pixels. Several parameters control the processing of the pixel data before it is placed into client memory. These parameters are set with three commands: glPixelStore, glPixelTransfer, and glPixelMap. This topic describes the effects on glReadPixels of most, but not all of the parameters specified by these three commands.

The glReadPixels function returns values from each pixel with lower-left corner at (x + i, y + j) for 0 ≤ i < width and 0 ≤ j < height. This pixel is said to be the ith pixel in the jth row. Pixels are returned in row order from the lowest to the highest row, left to right in each row.

The shift, scale, bias, and lookup factors described above are all specified by glPixelTransfer. The lookup table contents are specified by glPixelMap.

The final step involves converting the indexes or components to the proper format, as specified by type. If format is GL_COLOR_INDEX or GL_STENCIL_INDEX and type is not GL_FLOAT, each index is masked with the mask value given in the following table. If type is GL_FLOAT, then each integer index is converted to single-precision floating-point format.

If format is GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA, GL_BGR_EXT, GL_BGRA_EXT, GL_LUMINANCE, or GL_LUMINANCE_ALPHA and type is not GL_FLOAT, each component is multiplied by the multiplier shown in the preceding table. If type is GL_FLOAT, then each component is passed as is (or converted to the client's single-precision floating-point format if it is different from the one used by OpenGL).

Return values are placed in memory as follows. If format is GL_COLOR_INDEX, GL_STENCIL_INDEX, GL_DEPTH_COMPONENT, GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, or GL_LUMINANCE, a single value is returned and the data for the ith pixel in the jth row is placed in location (j) width + i. GL_RGB and GL_BGR_EXT return three values, GL_RGBA and GL_BGRA_EXT return four values, and GL_LUMINANCE_ALPHA returns two values for each pixel, with all values corresponding to a single pixel occupying contiguous space in pixels. Storage parameters set by glPixelStore, such as GL_PACK_SWAP_BYTES and GL_PACK_LSB_FIRST, affect the way that data is written into memory. See glPixelStore for a description.

Values for pixels that lie outside the window connected to the current OpenGL context are undefined.

If an error is generated, no change is made to the contents of pixels.

The following function retrieves information related to glReadPixels:

glGet with argument GL_INDEX_MODE

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | format or type was not an accepted value.
GL_INVALID_VALUE | either width or height was negative.
GL_INVALID_OPERATION | format was GL_COLOR_INDEX and the color buffers stored RGBA or BGRA color components.
GL_INVALID_OPERATION | format was GL_STENCIL_INDEX and there was no stencil buffer.
GL_INVALID_OPERATION | format was GL_DEPTH_COMPONENT and there was no depth buffer.
GL_INVALID_OPERATION | glReadPixels was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glCopyPixels, glDrawPixels, glEnd, glPixelMap, glPixelStore, glPixelTransfer, glReadBuffer


glRect

glRectd, glRectf, glRecti, glRects, glRectdv, glRectfv, glRectiv, glRectsv

These functions draw a rectangle.

void glRectd(
  GLdouble x1,
  GLdouble y1,
  GLdouble x2,
  GLdouble y2
);

void glRectf(
  GLfloat x1,
  GLfloat y1,
  GLfloat x2,
  GLfloat y2
);

void glRecti(
  GLint x1,
  GLint y1,
  GLint x2,
  GLint y2
);

void glRects(
  GLshort x1,
  GLshort y1,
  GLshort x2,
  GLshort y2
);

Parameters

x1, y1
One vertex of a rectangle.
x2, y2
The opposite vertex of the rectangle.
void glRectdv(
  const GLdouble *v1,
  const GLdouble *v2
);

void glRectfv(
  const GLfloat *v1,
  const GLfloat *v2
);

void glRectiv(
  const GLint *v1,
  const GLint *v2
);

void glRectsv(
  const GLshort *v1,
  const GLshort *v2
);

Parameters

v1
A pointer to one vertex of a rectangle.
v2
A pointer to the opposite vertex of the rectangle.

Remarks

The glRect function supports efficient specification of rectangles as two corner points. Each rectangle command takes four arguments, organized either as two consecutive pairs of (x, y) coordinates, or as two pointers to arrays, each containing an (x,y) pair. The resulting rectangle is defined in the z = 0 plane.

The glRect(x1, y1, x2, y2) function is exactly equivalent to the following sequence:

glBegin(GL_POLYGON);

glVertex2(x1, y1);

glVertex2(x2, y1);

glVertex2(x2, y2);

glVertex2(x1, y2);

glEnd( );

Notice that if the second vertex is above and to the right of the first vertex, the rectangle is constructed with a counterclockwise winding.

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glRect was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glVertex


glRenderMode

The glRenderMode function sets the rasterization mode.

GLint glRenderMode(
  GLenum mode
);

Parameters

mode
The rasterization mode. The following three values are accepted. The default value is GL_RENDER.
GL_RENDER
Render mode. Primitives are rasterized, producing pixel fragments, which are written into the frame buffer. This is the normal mode and also the default mode.
GL_SELECT
Selection mode. No pixel fragments are produced, and no change to the frame buffer contents is made. Instead, a record of the names of primitives that would have been drawn if the render mode was GL_RENDER is returned in a select buffer, which must be created (see glSelectBuffer) before selection mode is entered.
GL_FEEDBACK
Feedback mode. No pixel fragments are produced, and no change to the frame buffer contents is made. Instead, the coordinates and attributes of vertices that would have been drawn had the render mode been GL_RENDER are returned in a feedback buffer, which must be created (see glFeedbackBuffer) before feedback mode is entered.

Remarks

The glRenderMode function takes one argument, mode, which can assume one of three predefined values above.

The return value of the glRenderMode function is determined by the render mode at the time glRenderMode is called, rather than by mode. The values returned for the three render modes are as follows:

GL_RENDER
Zero.
GL_SELECT
The number of hit records transferred to the select buffer.
GL_FEEDBACK
The number of values (not vertices) transferred to the feedback buffer.

Refer to glSelectBuffer and glFeedbackBuffer for more details concerning selection and feedback operation.

If an error is generated, glRenderMode returns zero regardless of the current render mode.

The following function retrieves information related to glRenderMode:

glGet with argument GL_RENDER_MODE

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | mode was not one of the three accepted values.
GL_INVALID_OPERATION | glSelectBuffer was called while the render mode was GL_SELECT, or if glRenderMode was called with argument GL_SELECT before glSelectBuffer was called at least once.
GL_INVALID_OPERATION | glFeedbackBuffer was called while the render mode was GL_FEEDBACK, or if glRenderMode was called with argument GL_FEEDBACK before glFeedbackBuffer was called at least once.
GL_INVALID_OPERATION | glRenderMode was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glFeedbackBuffer, glInitNames, glLoadName, glPassThrough, glPushName, glSelectBuffer


glRotate

glRotated, glRotatef

The glRotated and glRotatef functions multiply the current matrix by a rotation matrix.

void glRotated(
  GLdouble angle,
  GLdouble x,
  GLdouble y,
  GLdouble z
);

void glRotatef(
  GLfloat angle,
  GLfloat x,
  GLfloat y,
  GLfloat z
);

Parameters

angle
The angle of rotation, in degrees.
x, y, z
The x, y, and z coordinates of a vector, respectively.

Remarks

The glRotate function computes a matrix that performs a counterclockwise rotation of angle degrees about the vector from the origin through the point (x, y, z).

The current matrix (see glMatrixMode) is multiplied by this rotation matrix, with the product replacing the current matrix. That is, if M is the current matrix and R is the translation matrix, then M is replaced with M·R.

If the matrix mode is either GL_MODELVIEW or GL_PROJECTION, all objects drawn after glRotate is called are rotated. Use glPushMatrix and glPopMatrix to save and restore the unrotated coordinate system.

The following functions retrieve information related to glRotate:

glGet with argument GL_RENDER_MODE

glGet with argument GL_MATRIX_MODE

glGet with argument GL_MODELVIEW_MATRIX

glGet with argument GL_PROJECTION_MATRIX

glGet with argument GL_TEXTURE_MATRIX

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glRotate was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glMatrixMode, glMultMatrix, glPushMatrix, glScale, glTranslate


glScale

glScaled, glScalef

The glScaled and glScalef functions multiply the current matrix by a general scaling matrix.

void glScaled(
  GLdouble x,
  GLdouble y,
  GLdouble z
);

void glScalef(
  GLfloat x,
  GLfloat y,
  GLfloat z
);

Parameters

x, y, z
Scale factors along the x, y, and z axes, respectively.

Remarks

The glScale function produces a general scaling along the x, y, and z axes. The three arguments indicate the desired scale factors along each of the three axes. The resulting matrix is

The current matrix (see glMatrixMode) is multiplied by this scale matrix, with the product replacing the current matrix. That is, if M is the current matrix and S is the scale matrix, then M is replaced with M·S.

If the matrix mode is either GL_MODELVIEW or GL_PROJECTION, all objects drawn after glScale is called are scaled. Use glPushMatrix and glPopMatrix to save and restore the unscaled coordinate system.

If scale factors other than 1.0 are applied to the modelview matrix and lighting is enabled, automatic normalization of normals should probably also be enabled (glEnable and glDisable with argument GL_NORMALIZE).

The following functions retrieve information related to glScale:

glGet with argument GL_MATRIX_MODE

glGet with argument GL_MODELVIEW_MATRIX

glGet with argument GL_PROJECTION_MATRIX

glGet with argument GL_TEXTURE_MATRIX

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glScale was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glMatrixMode, glMultMatrix, glPushMatrix, glRotate, glTranslate


glScissor

The glScissor function defines the scissor box.

void glScissor(
  GLint x,
  GLint y,
  GLsizei width,
  GLsizei height
);

Parameters

x, y
The lower-left corner of the scissor box. Initially (0,0).
width, height
The width and height of the scissor box. When an OpenGL context is first attached to a window, width and height are set to the dimensions of that window.

Remarks

The glScissor function defines a rectangle, called the scissor box, in window coordinates. The first two parameters, x and y, specify the lower-left corner of the box. The width and height parameters specify the width and height of the box.

The scissor test is enabled and disabled using glEnable and glDisable with argument GL_SCISSOR_TEST. While the scissor test is enabled, only pixels that lie within the scissor box can be modified by drawing commands. Window coordinates have integer values at the shared corners of frame buffer pixels, so glScissor(0,0,1,1) allows only the lower-left pixel in the window to be modified, and glScissor(0,0,0,0) disallows modification to all pixels in the window.

When the scissor test is disabled, it is as though the scissor box includes the entire window.

The following functions retrieve information related to glScissor:

glGet with argument GL_SCISSOR_BOX

glIsEnabled with argument GL_SCISSOR_TEST

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_VALUE | either width or height was negative.
GL_INVALID_OPERATION | glScissor was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnable, glEnd, glIsEnabled, glViewport


glSelectBuffer

The glSelectBuffer function establishes a buffer for selection mode values.

void glSelectBuffer(
  GLsizei size,
  GLuint *buffer
);

Parameters

size
The size of buffer.
buffer
Returns the selection data.

Remarks

The glSelectBuffer function has two parameters: buffer is a pointer to an array of unsigned integers, and size indicates the size of the array. The buffer parameter returns values from the name stack (see glInitNames, glLoadName, glPushName) when the rendering mode is GL_SELECT (see glRenderMode). The glSelectBuffer function must be issued before selection mode is enabled, and it must not be issued while the rendering mode is GL_SELECT.

Selection is used by a programmer to determine which primitives are drawn into some region of a window. The region is defined by the current modelview and perspective matrices.

In selection mode, no pixel fragments are produced from rasterization. Instead, if a primitive intersects the clip volume defined by the viewing frustum and the user-defined clipping planes, this primitive causes a selection hit. (With polygons, no hit occurs if the polygon is culled.) When a change is made to the name stack, or when glRenderMode is called, a hit record is copied to buffer if any hits have occurred since the last such event (either a name stack change or a glRenderMode call). The hit record consists of the number of names in the name stack at the time of the event; followed by the minimum and maximum depth values of all vertices that hit since the previous event; followed by the name stack contents, bottom name first.

Returned depth values are mapped such that the largest unsigned integer value corresponds to window coordinate depth 1.0, and zero corresponds to window coordinate depth 0.0.

An internal index into buffer is reset to zero whenever selection mode is entered. Each time a hit record is copied into buffer, the index is incremented to point to the cell just past the end of the block of names—that is, to the next available cell. If the hit record is larger than the number of remaining locations in buffer, as much data as can fit is copied, and the overflow flag is set. If the name stack is empty when a hit record is copied, that record consists of zero followed by the minimum and maximum depth values.

Selection mode is exited by calling glRenderMode with an argument other than GL_SELECT. Whenever glRenderMode is called while the render mode is GL_SELECT, it returns the number of hit records copied to buffer, resets the overflow flag and the selection buffer pointer, and initializes the name stack to be empty. If the overflow bit was set when glRenderMode was called, a negative hit record count is returned.

The contents of buffer are undefined until glRenderMode is called with an argument other than GL_SELECT.

The glBegin/glEnd primitives and calls to glRasterPos can result in hits.

The following function retrieves information related to glSelectBuffer:

glGet with argument GL_NAME_STACK_DEPTH

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_VALUE | size was negative.
GL_INVALID_OPERATION | glSelectBuffer was called while the render mode was GL_SELECT, or if glRenderMode was called with argument GL_SELECT before glSelectBuffer was called at least once.
GL_INVALID_OPERATION | glSelectBuffer was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glFeedbackBuffer, glInitNames, glLoadName, glPushName, glRenderMode


glShadeModel

The glShadeModel function selects flat or smooth shading.

void glShadeModel(
  GLenum mode
);

Parameters

mode
A symbolic value representing a shading technique. Accepted values are GL_FLAT and GL_SMOOTH. The default is GL_SMOOTH.

Remarks

OpenGL primitives can have either flat or smooth shading. Smooth shading, the default, causes the computed colors of vertices to be interpolated as the primitive is rasterized, typically assigning different colors to each resulting pixel fragment. Flat shading selects the computed color of just one vertex and assigns it to all the pixel fragments generated by rasterizing a single primitive. In either case, the computed color of a vertex is the result of lighting, if lighting is enabled, or it is the current color at the time the vertex was specified, if lighting is disabled.

Flat and smooth shading are indistinguishable for points. Counting vertices and primitives from one, starting when glBegin is issued, each flat-shaded line segment i is given the computed color of vertex i + 1, its second vertex. Counting similarly from one, each flat-shaded polygon is given the computed color of the vertex listed in the following table. This is the last vertex to specify the polygon in all cases except single polygons, where the first vertex specifies the flat-shaded color.

Primitive Type of Polygon i | Vertex
Single polygon (i º1) | 1
Triangle strip | i + 2
Triangle fan | i + 2
Independent triangle | 3i
Quad strip | 2i + 2
Independent quad | 4i

Flat and smooth shading are specified by glShadeModel with mode set to GL_FLAT and GL_SMOOTH, respectively.

The following function retrieves information related to glShadeModel:

glGet with argument GL_SHADE_MODEL

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | mode was any value other than GL_FLAT or GL_SMOOTH.
GL_INVALID_OPERATION | glShadeModel was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glColor, glEnd, glLight, glLightModel


glStencilFunc

The glStencilFunc function sets the function and reference value for stencil testing.

void glStencilFunc(
  GLenum func,
  GLint ref,
  GLuint mask
);

Parameters

func
The test function. The following eight tokens are valid:
GL_NEVER
Always fails.
GL_LESS
Passes if ( ref & mask) < ( stencil & mask).
GL_LEQUAL
Passes if ( ref & mask) ≤ ( stencil & mask).
GL_GREATER
Passes if ( ref & mask) > ( stencil & mask).
GL_GEQUAL
Passes if ( ref & mask) ≥ ( stencil & mask).
GL_EQUAL
Passes if ( ref & mask) = ( stencil & mask).
GL_NOTEQUAL
Passes if ( ref & mask) ¹ ( stencil & mask).
GL_ALWAYS
Always passes.
ref
The reference value for the stencil test. The ref parameter is clamped to the range [0,2^n – 1], where n is the number of bitplanes in the stencil buffer.
mask
A mask that is ANDed with both the reference value and the stored stencil value when the test is done.

Remarks

Stenciling, like z-buffering, enables and disables drawing on a per-pixel basis. You draw into the stencil planes using OpenGL drawing primitives, then render geometry and images, using the stencil planes to mask out portions of the screen. Stenciling is typically used in multipass rendering algorithms to achieve special effects, such as decals, outlining, and constructive solid geometry rendering.

The stencil test conditionally eliminates a pixel based on the outcome of a comparison between the reference value and the value in the stencil buffer. The test is enabled by glEnable and glDisable with argument GL_STENCIL. Actions taken based on the outcome of the stencil test are specified with glStencilOp.

The func parameter is a symbolic constant that determines the stencil comparison function. It accepts one of the eight values shown above. The ref parameter is an integer reference value that is used in the stencil comparison. It is clamped to the range [0,2^n – 1], where n is the number of bitplanes in the stencil buffer. The mask parameter is bitwise ANDed with both the reference value and the stored stencil value, with the ANDed values participating in the comparison.

If stencil represents the value stored in the corresponding stencil buffer location, the preceding list shows the effect of each comparison function that can be specified by func. Only if the comparison succeeds is the pixel passed through to the next stage in the rasterization process (see glStencilOp). All tests treat stencil values as unsigned integers in the range [0,2^n – 1], where n is the number of bitplanes in the stencil buffer.

Initially, the stencil test is disabled. If there is no stencil buffer, no stencil modification can occur and it is as if the stencil test always passes.

The following functions retrieve information related to glStencilFunc:

glGet with argument GL_STENCIL_FUNC

glGet with argument GL_STENCIL_VALUE_MASK

glGet with argument GL_STENCIL_REF

glGet with argument GL_STENCIL_BITS

glIsEnabled with argument GL_STENCIL_TEST

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | func was not one of the eight accepted values.
GL_INVALID_OPERATION | glStencilFunc was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glAlphaFunc, glBegin, glBlendFunc, glDepthFunc, glEnable, glEnd, glIsEnabled, glLogicOp, glStencilOp


glStencilMask

The glStencilMask function controls the writing of individual bits in the stencil planes.

void glStencilMask(
  GLuint mask
);

Parameters

mask
A bit mask to enable and disable writing of individual bits in the stencil planes. Initially, the mask is all ones.

Remarks

The glStencilMask function controls the writing of individual bits in the stencil planes. The least significant n bits of mask, where n is the number of bits in the stencil buffer, specify a mask. Wherever a one appears in the mask, the corresponding bit in the stencil buffer is made writable. Where a zero appears, the bit is write-protected. Initially, all bits are enabled for writing.

The following functions retrieve information related to glStencilMask:

glGet with argument GL_STENCIL_WRITEMASK

glGet with argument GL_STENCIL_BITS

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glStencilMask was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glColorMask, glDepthMask, glEnd, glIndexMask, glStencilFunc, glStencilOp


glStencilOp

The glStencilOp function sets the stencil test actions.

void glStencilOp(
  GLenum fail,
  GLenum zfail,
  GLenum zpass
);

Parameters

fail
The action to take when the stencil test fails. The following six symbolic constants are accepted:
GL_KEEP
Keeps the current value.
GL_ZERO
Sets the stencil buffer value to zero.
GL_REPLACE
Sets the stencil buffer value to ref, as specified by glStencilFunc.
GL_INCR
Increments the current stencil buffer value. Clamps to the maximum representable unsigned value.
GL_DECR
Decrements the current stencil buffer value. Clamps to zero.
GL_INVERT
Bitwise inverts the current stencil buffer value.
zfail
Stencil action when the stencil test passes, but the depth test fails. Accepts the same symbolic constants as fail.
zpass
Stencil action when both the stencil test and the depth test pass, or when the stencil test passes and either there is no depth buffer or depth testing is not enabled. Accepts the same symbolic constants as fail.

Remarks

Stenciling, like z-buffering, enables and disables drawing on a per-pixel basis. You draw into the stencil planes using OpenGL drawing primitives, then render geometry and images, using the stencil planes to mask out portions of the screen. Stenciling is typically used in multipass rendering algorithms to achieve special effects, such as decals, outlining, and constructive solid geometry rendering.

The stencil test conditionally eliminates a pixel based on the outcome of a comparison between the value in the stencil buffer and a reference value. The test is enabled with glEnable and glDisable calls with argument GL_STENCIL, and controlled with glStencilFunc.

The glStencilOp function takes three arguments that indicate what happens to the stored stencil value while stenciling is enabled. If the stencil test fails, no change is made to the pixel's color or depth buffers, and fail specifies what happens to the stencil buffer contents.

Stencil buffer values are treated as unsigned integers. When incremented and decremented, values are clamped to 0 and 2^n – 1, where n is the value returned by querying GL_STENCIL_BITS.

The other two arguments to glStencilOp specify stencil buffer actions should subsequent depth buffer tests succeed (zpass) or fail (zfail). (See glDepthFunc.) They are specified using the same six symbolic constants as fail. Note that zfail is ignored when there is no depth buffer, or when the depth buffer is not enabled. In these cases, fail and zpass specify stencil action when the stencil test fails and passes, respectively.

Initially the stencil test is disabled. If there is no stencil buffer, no stencil modification can occur and it is as if the stencil tests always pass, regardless of any call to glStencilOp.

The following functions retrieve information related to glStencilOp:

glGet with argument GL_STENCIL_FAIL

glGet with argument GL_STENCIL_PASS_DEPTH_PASS

glGet with argument GL_STENCIL_PASS_DEPTH_FAIL

glGet with argument GL_STENCIL_BITS

glIsEnabled with argument GL_STENCIL_TEST

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | fail, zfail, or zpass was any value other than the six defined constant values.
GL_INVALID_OPERATION | glStencilOp was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glAlphaFunc, glBegin, glBlendFunc, glDepthFunc, glEnable, glEnd, glIsEnabled, glLogicOp, glStencilFunc


glTexCoord

glTexCoord1d, glTexCoord1f, glTexCoord1i, glTexCoord1s, glTexCoord2d, glTexCoord2f, glTexCoord2i, glTexCoord2s, glTexCoord3d, glTexCoord3f, glTexCoord3i, glTexCoord3s, glTexCoord4d, glTexCoord4f, glTexCoord4i, glTexCoord4s, glTexCoord1dv, glTexCoord1fv, glTexCoord1iv, glTexCoord1sv, glTexCoord2dv, glTexCoord2fv, glTexCoord2iv, glTexCoord2sv, glTexCoord3dv, glTexCoord3fv, glTexCoord3iv, glTexCoord3sv, glTexCoord4dv, glTexCoord4fv, glTexCoord4iv, glTexCoord4sv

These functions set the current texture coordinates.

void glTexCoord1d(
  GLdouble s
);

void glTexCoord1f(
  GLfloat s
);

void glTexCoord1i(
  GLint s
);

void glTexCoord1s(
  GLshort s
);

void glTexCoord2d(
  GLdouble s,
  GLdouble t
);

void glTexCoord2f(
  GLfloat s,
  GLfloat t
);

void glTexCoord2i(
  GLint s,
  GLint t
);

void glTexCoord2s(
  GLshort s,
  GLshort t
);

void glTexCoord3d(
  GLdouble s,
  GLdouble t,
  GLdouble r
);

void glTexCoord3f(
  GLfloat s,
  GLfloat t,
  GLfloat r
);

void glTexCoord3i(
  GLint s,
  GLint t,
  GLint r
);

void glTexCoord3s(
  GLshort s,
  GLshort t,
  GLshort r
);

void glTexCoord4d(
  GLdouble s,
  GLdouble t,
  GLdouble r,
  GLdouble q
);

void glTexCoord4f(
  GLfloat s,
  GLfloat t,
  GLfloat r,
  GLfloat q
);

void glTexCoord4i(
  GLint s,
  GLint t,
  GLint r,
  GLint q
);

void glTexCoord4s(
  GLshort s,
  GLshort t,
  GLshort r,
  GLshort q
);

Parameters

s, t, r, q
The s, t, r, and q texture coordinates. Not all parameters are present in all forms of the command.
void glTexCoord1dv(
  const GLdouble *v
);

void glTexCoord1fv(
  const GLfloat *v
);

void glTexCoord1iv(
  const GLint *v
);

void glTexCoord1sv(
  const GLshort *v
);

void glTexCoord2dv(
  const GLdouble *v
);

void glTexCoord2fv(
  const GLfloat *v
);

void glTexCoord2iv(
  const GLint *v
);

void glTexCoord2sv(
  const GLshort *v
);

void glTexCoord3dv(
  const GLdouble *v
);

void glTexCoord3fv(
  const GLfloat *v
);

void glTexCoord3iv(
  const GLint *v
);

void glTexCoord3sv(
  const GLshort *v
);

void glTexCoord4dv(
  const GLdouble *v
);

void glTexCoord4fv(
  const GLfloat *v
);

void glTexCoord4iv(
  const GLint *v
);

void glTexCoord4sv(
  const GLshort *v
);

Parameters

v
A pointer to an array of one, two, three, or four elements, which in turn specify the s, t, r, and q texture coordinates.

Remarks

The glTexCoord function sets the current texture coordinates that are part of the data that is associated with polygon vertices.

The glTexCoord function specifies texture coordinates in one, two, three, or four dimensions. The glTexCoord1 function sets the current texture coordinates to (s, 0, 0, 1); a call to glTexCoord2 sets them to (s, t, 0, 1). Similarly, glTexCoord3 specifies the texture coordinates as (s, t, r, 1), and glTexCoord4 defines all four components explicitly as (s, t, r, q).

The current texture coordinates can be updated at any time. In particular, glTexCoord can be called between a call to glBegin and the corresponding call to glEnd.

The following function retrieves information related to glTexCoord:

glGet with argument GL_CURRENT_TEXTURE_COORDS

See Also

glVertex


glTexCoordPointer

The glTexCoordPointer function defines an array of texture coordinates.

void glTexCoordPointer(
  GLint size,
  GLenum type,
  GLsizei stride,
  GLsizei count,
  const GLvoid *pointer
);

Parameters

size
The number of coordinates per array element. The value of size must be 1, 2, 3, or 4.
type
The data type of each texture coordinate in the array using the following symbolic constants: GL_SHORT, GL_INT, GL_FLOAT, and GL_DOUBLE.
stride
The byte offset between consecutive array elements. When stride is zero, the array elements are tightly packed in the array.
count
The number of array elements, counting from the first, that are static.
pointer
A pointer to the first coordinate of the first element in the array.

Remarks

The glTexCoordPointer function specifies the location and data of an array of texture coordinates to use when rendering.The size parameter specifies the number of coordinates used for each element of the array.The type parameter specifies the data type of each texture coordinate. The stride parameter determines the byte offset from one array element to the next, enabling the packing of vertices and attributes in a single array or storage in separate arrays. In some implementations, storing the vertices and attributes in a single array can be more efficient than using separate arrays. Starting from the first array element, count indicates the total number of static elements. Your application can modify static elements, but once the elements are modified, the application must explicitly specify the array again before using the array for any rendering. Non-static array elements are not accessed until you call glDrawArrays or glArrayElement.

A texture coordinate array is enabled when you specify the GL_TEXTURE_COORD_ARRAY constant with glEnableClientState. When enabled, glDrawArrays and glArrayElement use the texture coordinate array. By default the texture coordinate array is disabled.

You cannot include glTexCoordPointer in display lists.

When you specify a texture coordinate array using glTexCoordPointer, the values of all the function's texture coordinate array parameters are saved in a client-side state, and static array elements can be cached. Because the texture coordinate array parameters are client-side state, their values are not saved or restored by glPushAttrib and glPopAttrib.

Although no error is generated when you call glTexCoordPointer within glBegin and glEnd pairs, the results are undefined.

The following functions retrieve information related to glTexCoordPointer:

glIsEnabled with argument GL_TEXTURE_COORD_ARRAY

glGet with argument GL_TEXTURE_COORD_ARRAY_SIZE

glGet with argument GL_TEXTURE_COORD_ARRAY_STRIDE

glGet with argument GL_TEXTURE_COORD_ARRAY_COUNT

glGet with argument GL_TEXTURE_COORD_ARRAY_TYPE

glGetPointerv with argument GL_TEXTURE_COORD_ARRAY_POINTER

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_VALUE | size was not 1, 2, 3, or 4.
GL_INVALID_ENUM | type was not an accepted value.
GL_INVALID_VALUE | stride or count was negative.

See Also

glArrayElement, glColorPointer, glDrawArrays, glEdgeFlagPointer, glGetPointerv, glGetString, glIndexPointer, glIsEnabled, glNormalPointer, glVertexPointer


glTexEnv

glTexEnvf, glTexEnvi, glTexEnvfv, glTexEnviv

These functions set texture environment parameters.

void glTexEnvf(
  GLenum target,
  GLenum pname,
  GLfloat param
);

void glTexEnvi(
  GLenum target,
  GLenum pname,
  GLint param
);

Parameters

target
A texture environment. Must be GL_TEXTURE_ENV.
pname
The symbolic name of a single-valued texture environment parameter. Must be GL_TEXTURE_ENV_MODE.
param
A single symbolic constant, one of GL_MODULATE, GL_DECAL, or GL_BLEND.
void glTexEnvfv(
  GLenum target,
  GLenum pname,
  const GLfloat *params
);

void glTexEnviv(
  GLenum target,
  GLenum pname,
  const GLint *params
);

Parameters

target
A texture environment. Must be GL_TEXTURE_ENV.
pname
The symbolic name of a texture environment parameter. Accepted values are GL_TEXTURE_ENV_MODE and GL_TEXTURE_ENV_COLOR.
params
A pointer to an array of parameters: either a single symbolic constant or an RGBA color.

Remarks

A texture environment specifies how texture values are interpreted when a fragment is textured. The target parameter must be GL_TEXTURE_ENV. The pname parameter can be either GL_TEXTURE_ENV_MODE or GL_TEXTURE_ENV_COLOR.

If pname is GL_TEXTURE_ENV_MODE, then params is (or points to) the symbolic name of a texture function. Three texture functions are defined: GL_MODULATE, GL_DECAL, and GL_BLEND.

A texture function acts on the fragment to be textured using the texture image value that applies to the fragment (see glTexParameter) and produces an RGBA color for that fragment. The following table shows how the RGBA color is produced for each of the three texture functions that can be chosen. C is a triple of color values (RGB) and A is the associated alpha value. RGBA values extracted from a texture image are in the range [0,1]. The subscript f refers to the incoming fragment, the subscript t to the texture image, the subscript c to the texture environment color, and subscript v indicates a value produced by the texture function.

A texture image can have up to four components per texture element (see glTexImage1D and glTexImage2D). In a one-component image, L (t) indicates that single component. A two-component image uses L (t) and A (t) . A three-component image has only a color value, C (t) . A four-component image has both a color value C (t) and an alpha value A (t) .

If pname is GL_TEXTURE_ENV_COLOR, params is a pointer to an array that holds an RGBA color consisting of four values. Integer color components are interpreted linearly such that the most positive integer maps to 1.0, and the most negative integer maps to –1.0. The values are clamped to the range [0,1] when they are specified. C (c) takes these four values.

GL_TEXTURE_ENV_MODE defaults to GL_MODULATE and GL_TEXTURE_ENV_COLOR defaults to (0,0,0,0).

The following function retrieves information related to glTexEnv:

glGetTexEnv

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | target or pname was not one of the accepted defined values, or when params should have had a defined constant value (based on the value of pname) and did not.
GL_INVALID_OPERATION | glTexEnv was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glTexImage1D, glTexImage2D, glTexParameter


glTexGen

glTexGend, glTexGenf, glTexGeni, glTexGendv, glTexGenfv, glTexGeniv

These functions control the generation of texture coordinates.

void glTexGend(
  GLenum coord,
  GLenum pname,
  GLdouble param
);

void glTexGenf(
  GLenum coord,
  GLenum pname,
  GLfloat param
);

void glTexGeni(
  GLenum coord,
  GLenum pname,
  GLint param
);

Parameters

coord
A texture coordinate. Must be one of the following: GL_S, GL_T, GL_R, or GL_Q.
pname
The symbolic name of the texture-coordinate generation function. Must be GL_TEXTURE_GEN_MODE.
param
A single-valued texture generation parameter, one of GL_OBJECT_LINEAR, GL_EYE_LINEAR, or GL_SPHERE_MAP.
void glTexGendv(
  GLenum coord,
  GLenum pname,
  const GLdouble *params
);

void glTexGenfv(
  GLenum coord,
  GLenum pname,
  const GLfloat *params
);

void glTexGeniv(
  GLenum coord,
  GLenum pname,
  const GLint *params
);

Parameters

coord
A texture coordinate. Must be one of the following: GL_S, GL_T, GL_R, or GL_Q.
pname
The symbolic name of the texture-coordinate generation function or function parameters. Must be GL_TEXTURE_GEN_MODE, GL_OBJECT_PLANE, or GL_EYE_PLANE.
params
A pointer to an array of texture generation parameters. If pname is GL_TEXTURE_GEN_MODE, then the array must contain a single symbolic constant, one of GL_OBJECT_LINEAR, GL_EYE_LINEAR, or GL_SPHERE_MAP. Otherwise, params holds the coefficients for the texture-coordinate generation function specified by pname.

Remarks

The glTexGen function selects a texture-coordinate generation function or supplies coefficients for one of the functions. The coord parameter names one of the (s,t,r,q) texture coordinates, and it must be one of these symbols: GL_S, GL_T, GL_R, or GL_Q. The pname parameter must be one of three symbolic constants: GL_TEXTURE_GEN_MODE, GL_OBJECT_PLANE, or GL_EYE_PLANE. If pname is GL_TEXTURE_GEN_MODE, then params chooses a mode, one of GL_OBJECT_LINEAR, GL_EYE_LINEAR, or GL_SPHERE_MAP. If pname is either GL_OBJECT_PLANE or GL_EYE_PLANE, params contains coefficients for the corresponding texture generation function.

If the texture generation function is GL_OBJECT_LINEAR, the function

is used, where g is the value computed for the coordinate named in coord; p (1) , p (2) , p (3) , and p (4) are the four values supplied in params; and x (o) , y (o) , z (o) , and w (o) are the object coordinates of the vertex. This function can be used to texture-map terrain using sea level as a reference plane (defined by p (1) , p (2) , p (3) , and p (4) ). The altitude of a terrain vertex is computed by the GL_OBJECT_LINEAR coordinate generation function as its distance from sea level; that altitude is used to index the texture image to map white snow onto peaks and green grass onto foothills, for example.

If the texture generation function is GL_EYE_LINEAR, the function

is used, where

and x (e) , y (e) , z (e) , and w (e) are the eye coordinates of the vertex, p (1) , p (2) , p (3) , and p (4) are the values supplied in params, and M is the modelview matrix when glTexGen is invoked. If M is poorly conditioned or singular, texture coordinates generated by the resulting function may be inaccurate or undefined.

Note that the values in params define a reference plane in eye coordinates. The modelview matrix that is applied to them may not be the same one in effect when the polygon vertices are transformed. This function establishes a field of texture coordinates that can produce dynamic contour lines on moving objects.

If pname is GL_SPHERE_MAP and coord is either GL_S or GL_T, s and t texture coordinates are generated as follows. Let u be the unit vector pointing from the origin to the polygon vertex (in eye coordinates). Let n´ be the current normal, after transformation to eye coordinates. Let f = (f (x ) f (y ) f (z) )T be the reflection vector such that

Finally, let

Then the values assigned to the i and t texture coordinates are

A texture-coordinate generation function is enabled or disabled using glEnable or glDisable with one of the symbolic texture-coordinate names (GL_TEXTURE_GEN_S, GL_TEXTURE_GEN_T, GL_TEXTURE_GEN_R, or GL_TEXTURE_GEN_Q) as the argument. When enabled, the specified texture coordinate is computed according to the generating function associated with that coordinate. When disabled, subsequent vertices take the specified texture coordinate from the current set of texture coordinates. Initially, all texture generation functions are set to GL_EYE_LINEAR and are disabled. Both s plane equations are (1,0,0,0); both t plane equations are (0,1,0,0); and all r and q plane equations are (0,0,0,0).

The following functions retrieve information related to glTexGen:

glGetTexGen

glIsEnabled with argument GL_TEXTURE_GEN_S

glIsEnabled with argument GL_TEXTURE_GEN_T

glIsEnabled with argument GL_TEXTURE_GEN_R

glIsEnabled with argument GL_TEXTURE_GEN_Q

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | coord or pname was not an accepted defined value, or when pname was GL_TEXTURE_GEN_MODE and params was not an accepted defined value.
GL_INVALID_ENUM | pname was GL_TEXTURE_GEN_MODE, params was GL_SPHERE_MAP, and coord was either GL_R or GL_Q.
GL_INVALID_OPERATION | glTexGen was called between a call to glBegin and the corresponding call to glEnd.

See Also

glBegin, glEnd, glGetTexGen, glIsEnabled, glTexEnv, glTexImage1D, glTexImage2D, glTexParameter


glTexImage1D

The glTexImage1D function specifies a one-dimensional texture image.

void glTexImage1D(
  GLenum target,
  GLint level,
  GLint components,
  GLsizei width,
  GLint border,
  GLenum format,
  GLenum type,
  const GLvoid *pixels
);

Parameters

target
The target texture. Must be GL_TEXTURE_1D.
level
The level-of-detail number. Level 0 is the base image level. Level n is the nth mipmap reduction image.
components
The number of color components in the texture. Must be 1, 2, 3, or 4.
width
The width of the texture image. Must be 2^n + 2(border) for some integer n. The height of the texture image is 1.
border
The width of the border. Must be either 0 or 1.
format
The format of the pixel data. It can assume one of nine symbolic values:
GL_COLOR_INDEX
Each element is a single value, a color index. It is converted to fixed point (with an unspecified number of 0 bits to the right of the binary point), shifted left or right depending on the value and sign of GL_INDEX_SHIFT, and added to GL_INDEX_OFFSET (see glPixelTransfer). The resulting index is converted to a set of color components using the GL_PIXEL_MAP_I_TO_R, GL_PIXEL_MAP_I_TO_G, GL_PIXEL_MAP_I_TO_B, and GL_PIXEL_MAP_I_TO_A tables, and clamped to the range [0,1].
GL_RED
Each element is a single red component. It is converted to floating point and assembled into an RGBA element by attaching 0.0 for green and blue, and 1.0 for alpha. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_GREEN
Each element is a single green component. It is converted to floating point and assembled into an RGBA element by attaching 0.0 for red and blue, and 1.0 for alpha. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_BLUE
Each element is a single blue component. It is converted to floating point and assembled into an RGBA element by attaching 0.0 for red and green, and 1.0 for alpha. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_ALPHA
Each element is a single red component. It is converted to floating point and assembled into an RGBA element by attaching 0.0 for red, green, and blue. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_RGB
Each element is an RGB triple. It is converted to floating point and assembled into an RGBA element by attaching 1.0 for alpha. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_RGBA
Each element is a complete RGBA element. It is converted to floating point. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_BGR_EXT
Each pixel is a group of three components in this order: blue, green, red.

GL_BGR_EXT provides a format that matches the memory layout of Windows device-independent bitmaps (DIBs). Thus your applications can use the same data with Win32 function calls and OpenGL pixel function calls.

GL_BGRA_EXT
Each pixel is a group of four components in this order: blue, green, red, alpha.

GL_BGRA_EXT provides a format that matches the memory layout of Windows device-independent bitmaps (DIBs). Thus your applications can use the same data with Win32 function calls and OpenGL pixel function calls.

GL_LUMINANCE
Each element is a single luminance value. It is converted to floating point, and then assembled into an RGBA element by replicating the luminance value three times for red, green, and blue, and attaching 1.0 for alpha. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_LUMINANCE_ALPHA
Each element is a luminance/alpha pair. It is converted to floating point, and then assembled into an RGBA element by replicating the luminance value three times for red, green, and blue. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
type
The data type of the pixel data. The following symbolic values are accepted: GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, and GL_FLOAT.
pixels
A pointer to the image data in memory.

Remarks

The glTexImage1D function specifies a one-dimensional texture image. Texturing maps a portion of a specified texture image onto each graphical primitive for which texturing is enabled. One-dimensional texturing is enabled and disabled using glEnable and glDisable with argument GL_TEXTURE_1D.

Texture images are defined with glTexImage1D. The arguments describe the parameters of the texture image, such as width, width of the border, level-of-detail number (see glTexParameter), and number of color components provided. The last three arguments describe the way the image is represented in memory. These arguments are identical to the pixel formats used for glDrawPixels.

Data is read from pixels as a sequence of signed or unsigned bytes, shorts or longs, or single-precision floating-point values, depending on type. These values are grouped into sets of one, two, three, or four values, depending on format, to form elements. If type is GL_BITMAP, the data is considered as a string of unsigned bytes (and format must be GL_COLOR_INDEX). Each data byte is treated as eight 1-bit elements, with bit ordering determined by GL_UNPACK_LSB_FIRST (see glPixelStore).

A texture image can have up to four components per texture element, depending on components. A one-component texture image uses only the red component of the RGBA color extracted from pixels. A two-component image uses the R and A values. A three-component image uses the R, G, and B values. A four-component image uses all of the RGBA components.

Texturing has no effect in color-index mode.

The texture image can be represented by the same data formats as the pixels in a glDrawPixels command, except that GL_STENCIL_INDEX and GL_DEPTH_COMPONENT cannot be used. The glPixelStore and glPixelTransfer modes affect texture images in exactly the way they affect glDrawPixels.

A texture image with zero width indicates the null texture. If the null texture is specified for level-of-detail 0, it is as if texturing were disabled.

The following functions retrieve information related to glTexImageID:

glGetTexImage

glIsEnabled with argument GL_TEXTURE_1D

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | target was not GL_TEXTURE_1D.
GL_INVALID_ENUM | format was not an accepted format constant. Format constants other than GL_STENCIL_INDEX and GL_DEPTH_COMPONENT were accepted.
GL_INVALID_ENUM | type was not a type constant.
GL_INVALID_ENUM | type was GL_BITMAP and format was not GL_COLOR_INDEX.
GL_INVALID_VALUE | level was less than zero or greater than log (2) max, where max was the returned value of GL_MAX_TEXTURE_SIZE.
GL_INVALID_VALUE | components was not 1, 2, 3, or 4.
GL_INVALID_VALUE | width was less than zero or greater than 2 + GL_MAX_TEXTURE_SIZE, or if it could not be represented as 2^n + 2(border) for some integer value of n.
GL_INVALID_VALUE | border was not 0 or 1.
GL_INVALID_OPERATION | glTexImage1D was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glDrawPixels, glEnd, glFog, glGetTexImage, glIsEnabled, glPixelStore, glPixelTransfer, glTexEnv, glTexGen, glTexImage2D, glTexParameter


glTexImage2D

The glTexImage2D function specifies a two-dimensional texture image.

void glTexImage2D(
  GLenum target,
  GLint level,
  GLint components,
  GLsizei width,
  GLsizei height,
  GLint border,
  GLenum format,
  GLenum type,
  const GLvoid *pixels
);

Parameters

target
The target texture. Must be GL_TEXTURE_2D.
level
The level-of-detail number. Level 0 is the base image level. Level n is the nth mipmap reduction image.
components
The number of color components in the texture. Must be 1, 2, 3, or 4.
width
The width of the texture image. Must be 2^n + 2(border) for some integer n.
height
The height of the texture image. Must be 2^m + 2(border) for some integer m.
border
The width of the border. Must be either 0 or 1.
format
The format of the pixel data. It can assume one of nine symbolic values:
GL_COLOR_INDEX
Each element is a single value, a color index. It is converted to fixed point (with an unspecified number of 0 bits to the right of the binary point), shifted left or right depending on the value and sign of GL_INDEX_SHIFT, and added to GL_INDEX_OFFSET (see glPixelTransfer). The resulting index is converted to a set of color components using the GL_PIXEL_MAP_I_TO_R, GL_PIXEL_MAP_I_TO_G, GL_PIXEL_MAP_I_TO_B, and GL_PIXEL_MAP_I_TO_A tables, and clamped to the range [0,1].
GL_RED
Each element is a single red component. It is converted to floating point and assembled into an RGBA element by attaching 0.0 for green and blue, and 1.0 for alpha. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_GREEN
Each element is a single green component. It is converted to floating point and assembled into an RGBA element by attaching 0.0 for red and blue, and 1.0 for alpha. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_BLUE
Each element is a single blue component. It is converted to floating point and assembled into an RGBA element by attaching 0.0 for red and green, and 1.0 for alpha. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_ALPHA
Each element is a single red component. It is converted to floating point and assembled into an RGBA element by attaching 0.0 for red, green, and blue. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_RGB
Each element is an RGB triple. It is converted to floating point and assembled into an RGBA element by attaching 1.0 for alpha. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_RGBA
Each element is a complete RGBA element. It is converted to floating point. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_BGR_EXT
Each pixel is a group of three components in this order: blue, green, red.

GL_BGR_EXT provides a format that matches the memory layout of Windows device-independent bitmaps (DIBs). Thus your applications can use the same data with Win32 function calls and OpenGL pixel function calls.

GL_BGRA_EXT
Each pixel is a group of four components in this order: blue, green, red, alpha.

GL_BGRA_EXT provides a format that matches the memory layout of Windows device-independent bitmaps (DIBs). Thus your applications can use the same data with Win32 function calls and OpenGL pixel function calls.

GL_LUMINANCE
Each element is a single luminance value. It is converted to floating point, and then assembled into an RGBA element by replicating the luminance value three times for red, green, and blue, and attaching 1.0 for alpha. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_LUMINANCE_ALPHA
Each element is a luminance/alpha pair. It is converted to floating point, and then assembled into an RGBA element by replicating the luminance value three times for red, green, and blue. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
type
The data type of the pixel data. The following symbolic values are accepted: GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, and GL_FLOAT.
pixels
A pointer to the image data in memory.

Remarks

The glTexImage2D function specifies a two-dimensional texture image. Texturing maps a portion of a specified texture image onto each graphical primitive for which texturing is enabled. Two-dimensional texturing is enabled and disabled using glEnable and glDisable with argument GL_TEXTURE_2D.

Texture images are defined with glTexImage2D. The arguments describe the parameters of the texture image, such as height, width, width of the border, level-of-detail number (see glTexParameter), and number of color components provided. The last three arguments describe the way the image is represented in memory. These arguments are identical to the pixel formats used for glDrawPixels.

Data is read from pixels as a sequence of signed or unsigned bytes, shorts or longs, or single-precision floating-point values, depending on type. These values are grouped into sets of one, two, three, or four values, depending on format, to form elements. If type is GL_BITMAP, the data is considered as a string of unsigned bytes (and format must be GL_COLOR_INDEX). Each data byte is treated as eight 1-bit elements, with bit ordering determined by GL_UNPACK_LSB_FIRST (see glPixelStore). Please see glDrawPixels for a description of the acceptable values for the type parameter.

A texture image can have up to four components per texture element, depending on components. A one-component texture image uses only the red component of the RGBA color extracted from pixels. A two-component image uses the R and A values. A three-component image uses the R, G, and B values. A four-component image uses all of the RGBA components.

Texturing has no effect in color-index mode.

The texture image can be represented by the same data formats as the pixels in a glDrawPixels command, except that GL_STENCIL_INDEX and GL_DEPTH_COMPONENT cannot be used. The glPixelStore and glPixelTransfer modes affect texture images in exactly the way they affect glDrawPixels.

A texture image with zero height or width indicates the null texture. If the null texture is specified for level-of-detail 0, it is as if texturing were disabled.

The following functions retrieve information related to glTexImage2D:

glGetTexImage

glIsEnabled with argument GL_TEXTURE_2D

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | target was not GL_TEXTURE_2D.
GL_INVALID_ENUM | format was not an accepted format constant. Format constants other than GL_STENCIL_INDEX and GL_DEPTH_COMPONENT were accepted.
GL_INVALID_ENUM | type was not a type constant.
GL_INVALID_ENUM | type was GL_BITMAP and format was not GL_COLOR_INDEX.
GL_INVALID_VALUE | level was less than zero or greater than log (2) max, where max was the returned value of GL_MAX_TEXTURE_SIZE.
GL_INVALID_VALUE | components was not 1, 2, 3, or 4.
GL_INVALID_VALUE | width or height was less than zero or greater than 2 + GL_MAX_TEXTURE_SIZE, or if either could not be represented as 2^k + 2(border) for some integer value of k.
GL_INVALID_VALUE | border was not 0 or 1.
GL_INVALID_OPERATION | glTexImage2D was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glDrawPixels, glEnd, glFog, glIsEnabled, glPixelStore, glPixelTransfer, glTexEnv, glTexGen, glTexImage1D, glTexParameter


glTexParameter

glTexParameterf, glTexParameteri, glTexParameterfv, glTexParameteriv

These functions set texture parameters.

void glTexParameterf(
  GLenum target,
  GLenum pname,
  GLfloat param
);

void glTexParameteri(
  GLenum target,
  GLenum pname,
  GLint param
);

Parameters

target
The target texture, which must be either GL_TEXTURE_1D or GL_TEXTURE_2D.
pname
The symbolic name of a single-valued texture parameter. The following symbols are accepted in pname:
GL_TEXTURE_MIN_FILTER
The texture minifying function is used whenever the pixel being textured maps to an area greater than one texture element. There are six defined minifying functions. Two of them use the nearest one or nearest four texture elements to compute the texture value. The other four use mipmaps.

A mipmap is an ordered set of arrays representing the same image at progressively lower resolutions. If the texture has dimensions 2^nx2^m there are max(n, m) + 1 mipmaps. The first mipmap is the original texture, with dimensions 2^nx2^m. Each subsequent mipmap has dimensions 2^k–1x2^l–1 where 2^kx2^l are the dimensions of the previous mipmap, until either k = 0 or l = 0. At that point, subsequent mipmaps have dimension 1x2^l–1 or 2^k–1x1 until the final mipmap, which has dimension 1x1. Mipmaps are defined using glTexImage1D or glTexImage2D with the level-of-detail argument indicating the order of the mipmaps. Level 0 is the original texture; level bold max(n, m) is the final 1x1 mipmap.

GL_TEXTURE_MAG_FILTER
The texture magnification function is used when the pixel being textured maps to an area less than or equal to one texture element. It sets the texture magnification function to either GL_NEAREST or GL_LINEAR.
GL_TEXTURE_WRAP_S
Sets the wrap parameter for texture coordinate s to either GL_CLAMP or GL_REPEAT. GL_CLAMP causes s coordinates to be clamped to the range [0,1] and is useful for preventing wrapping artifacts when mapping a single image onto an object. GL_REPEAT causes the integer part of the s coordinate to be ignored; OpenGL uses only the fractional part, thereby creating a repeating pattern. Border texture elements are accessed only if wrapping is set to GL_CLAMP. Initially, GL_TEXTURE_WRAP_S is set to GL_REPEAT.
GL_TEXTURE_WRAP_T
Sets the wrap parameter for texture coordinate t to either GL_CLAMP or GL_REPEAT. See the discussion under GL_TEXTURE_WRAP_S. Initially, GL_TEXTURE_WRAP_T is set to GL_REPEAT.
param
The value of pname.
void glTexParameterfv(
  GLenum target,
  GLenum pname,
  const GLfloat *params
);

void glTexParameteriv(
  GLenum target,
  GLenum pname,
  const GLint *params
);

Parameters

target
The target texture, which must be either GL_TEXTURE_1D or GL_TEXTURE_2D.
pname
The symbolic name of a texture parameter. The following symbols are accepted in pname:
GL_TEXTURE_MIN_FILTER
The texture minifying function is used whenever the pixel being textured maps to an area greater than one texture element. There are six defined minifying functions. Two of them use the nearest one or nearest four texture elements to compute the texture value. The other four use mipmaps.

A mipmap is an ordered set of arrays representing the same image at progressively lower resolutions. If the texture has dimensions 2^nx2^m there are max(n, m) + 1 mipmaps. The first mipmap is the original texture, with dimensions 2^nx2^m. Each subsequent mipmap has dimensions 2^k–1x2^l–1 where 2^kx2^l are the dimensions of the previous mipmap, until either k = 0 or l = 0. At that point, subsequent mipmaps have dimension 1x2^l–1 or 2^k–1x1 until the final mipmap, which has dimension 1x1. Mipmaps are defined using glTexImage1D or glTexImage2D with the level-of-detail argument indicating the order of the mipmaps. Level 0 is the original texture; level bold max(n, m) is the final 1x1 mipmap.

GL_TEXTURE_MAG_FILTER
The texture magnification function is used when the pixel being textured maps to an area less than or equal to one texture element. It sets the texture magnification function to either GL_NEAREST or GL_LINEAR.
GL_TEXTURE_WRAP_S
Sets the wrap parameter for texture coordinate s to either GL_CLAMP or GL_REPEAT. GL_CLAMP causes s coordinates to be clamped to the range [0,1] and is useful for preventing wrapping artifacts when mapping a single image onto an object. GL_REPEAT causes the integer part of the s coordinate to be ignored; OpenGL uses only the fractional part, thereby creating a repeating pattern. Border texture elements are accessed only if wrapping is set to GL_CLAMP. Initially, GL_TEXTURE_WRAP_S is set to GL_REPEAT.
GL_TEXTURE_WRAP_T
Sets the wrap parameter for texture coordinate t to either GL_CLAMP or GL_REPEAT. See the discussion under GL_TEXTURE_WRAP_S. Initially, GL_TEXTURE_WRAP_T is set to GL_REPEAT.
GL_TEXTURE_BORDER_COLOR
Sets a border color. The params parameter contains four values that comprise the RGBA color of the texture border. Integer color components are interpreted linearly such that the most positive integer maps to 1.0, and the most negative integer maps to –1.0. The values are clamped to the range [0,1] when they are specified. Initially, the border color is (0, 0, 0, 0).
params
A pointer to an array where the value or values of pname are stored. The params parameter supplies a function for minifying the texture as one of the following:
GL_NEAREST
Returns the value of the texture element that is nearest (in Manhattan distance) to the center of the pixel being textured.
GL_LINEAR
Returns the weighted average of the four texture elements that are closest to the center of the pixel being textured. These can include border texture elements, depending on the values of GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T, and on the exact mapping. GL_NEAREST is generally faster than GL_LINEAR, but it can produce textured images with sharper edges because the transition between texture elements is not as smooth. The default value of GL_TEXTURE_MAG_FILTER is GL_LINEAR.
GL_NEAREST_MIPMAP_NEAREST
Chooses the mipmap that most closely matches the size of the pixel being textured and uses the GL_NEAREST criterion (the texture element nearest to the center of the pixel) to produce a texture value.
GL_LINEAR_MIPMAP_NEAREST
Chooses the mipmap that most closely matches the size of the pixel being textured and uses the GL_LINEAR criterion (a weighted average of the four texture elements that are closest to the center of the pixel) to produce a texture value.
GL_NEAREST_MIPMAP_LINEAR
Chooses the two mipmaps that most closely match the size of the pixel being textured and uses the GL_NEAREST criterion (the texture element nearest to the center of the pixel) to produce a texture value from each mipmap. The final texture value is a weighted average of those two values.
GL_LINEAR_MIPMAP_LINEAR
Chooses the two mipmaps that most closely match the size of the pixel being textured and uses the GL_LINEAR criterion (a weighted average of the four texture elements that are closest to the center of the pixel) to produce a texture value from each mipmap. The final texture value is a weighted average of those two values.

Remarks

Texture mapping is a technique that applies an image onto an object's surface as if the image were a decal or cellophane shrink-wrap. The image is created in texture space, with an (s, t) coordinate system. A texture is a one- or two-dimensional image and a set of parameters that determine how samples are derived from the image.

The glTexParameter function assigns the value or values in params to the texture parameter specified as pname. The target parameter defines the target texture, either GL_TEXTURE_1D or GL_TEXTURE_2D.

As more texture elements are sampled in the minification process, fewer aliasing artifacts will be apparent. While the GL_NEAREST and GL_LINEAR minification functions can be faster than the other four, they sample only one or four texture elements to determine the texture value of the pixel being rendered and can produce moire patterns or ragged transitions. The default value of GL_TEXTURE_MIN_FILTER is GL_NEAREST_MIPMAP_LINEAR.

Suppose texturing is enabled (by calling glEnable with argument GL_TEXTURE_1D or GL_TEXTURE_2D) and GL_TEXTURE_MIN_FILTER is set to one of the functions that requires a mipmap. If either the dimensions of the texture images currently defined (with previous calls to glTexImage1D or glTexImage2D) do not follow the proper sequence for mipmaps, or there are fewer texture images defined than are needed, or the set of texture images have differing numbers of texture components, then it is as if texture mapping were disabled.

Linear filtering accesses the four nearest texture elements only in 2-D textures. In 1-D textures, linear filtering accesses the two nearest texture elements.

The following function retrieves information related to glTexParameterf, glTexParameteri, glTexParameterfv, and glTexParameteriv:

glGetTexParameter

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | target or pname was not one of the accepted defined values, or when params should have had a defined constant value (based on the value of pname) and did not.
GL_INVALID_OPERATION | glTexParameter was called between a call to glBegin and the corresponding call to glEnd.

See Also

glBegin, glEnd, glGetTexParameter, glTexEnv, glTexGen, glTexImage1D, glTexImage2D


glTexSubImage1D

The glTexSubImage1D function specifies a portion of an existing one-dimensional texture image. You cannot define a new texture with glTexSubImage1D.

void glTexSubImage1D(
  GLenum target,
  GLint level,
  GLint xoffset,
  GLsizei width,
  GLenum format,
  GLenum type,
  const GLvoid *pixels
);

Parameters

target
The target texture. Must be GL_TEXTURE_1D.
level
The level-of-detail number. Level 0 is the base image. Level n is the nth mipmap reduction image.
xoffset
A texel offset in the x direction within the texture array.
width
The width of the texture sub-image.
format
The format of the pixel data. This parameter can assume one of the following symbolic values:
GL_COLOR_INDEX
Each element is a single value, a color index. It is converted to fixed point format (with an unspecified number of 0 bits to the right of the binary point), shifted left or right depending on the value and sign of GL_INDEX_SHIFT, and added to GL_INDEX_OFFSET (see glPixelTransfer). The resulting index is converted to a set of color components using the GL_PIXEL_MAP_I_TO_R, GL_PIXEL_MAP_I_TO_G, GL_PIXEL_MAP_I_TO_B, and GL_PIXEL_MAP_I_TO_A tables, and clamped to the range [0,1].
GL_RED
Each element is a single red component. It is converted to floating point format and assembled into an RGBA element by attaching 0.0 for green and blue, and 1.0 for alpha. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_GREEN
Each element is a single green component. It is converted to floating point format and assembled into an RGBA element by attaching 0.0 for red and blue, and 1.0 for alpha. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_BLUE
Each element is a single blue component. It is converted to floating point format and assembled into an RGBA element by attaching 0.0 for red and green, and 1.0 for alpha. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_ALPHA
Each element is a single alpha component. It is converted to floating point format and assembled into an RGBA element by attaching 0.0 for red, green, and blue. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_RGB
Each element is an RGB triple. It is converted to floating point and assembled into an RGBA element by attaching 1.0 for alpha. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_RGBA
Each element is a complete RGBA element. It is converted to floating point format. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_LUMINANCE
Each element is a single luminance value. It is converted to floating point format, and then assembled into an RGBA element by replicating the luminance value three times for red, green, and blue, and attaching 1.0 for alpha. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_LUMINANCE_ALPHA
Each element is a luminance/alpha pair. It is converted to floating point format, and then assembled into an RGBA element by replicating the luminance value three times for red, green, and blue. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
type
The data type of the pixel data. The following symbolic values are accepted: GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, and GL_FLOAT.
pixels
A pointer to the image data in memory.

Remarks

One-dimensional texturing for a primitive is enabled using glEnable and glDisable with the argument GL_TEXTURE_1D. During texturing, part of a specified texture image is mapped onto each enabled primitive. You use the glTexSubImage1D function to specify a contiguous sub-image of an existing one-dimensional texture image for texturing.

The texels referenced by pixels replace a region of the existing texture array with x indexes of xoffset and xoffset + (width – 1) inclusive. This region cannot include any texels outside the range of the originally specified texture array.

Specifying a sub-image with a width of zero has no effect and does not generate an error.

Texturing has no effect in color-index mode.

In general, texture images can be represented by the same data formats as the pixels in a glDrawPixels command, except that GL_STENCIL_INDEX and GL_DEPTH_COMPONENT cannot be used. The glPixelStore and glPixelTransfer modes affect texture images in exactly the way they affect glDrawPixels.

The following functions retrieve information related to glTexSubImage1D:

glGetTexImage

glIsEnabled with argument GL_TEXTURE_1D

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | target was not GL_TEXTURE_1D.
GL_INVALID_ENUM | format was not an accepted constant.
GL_INVALID_ENUM | type was not a constant.
GL_INVALID_ENUM | type was GL_BITMAP and format was not GL_COLOR_INDEX.
GL_INVALID_VALUE | level was less than zero or greater than log (2) max, where max was the returned value of GL_MAX_TEXTURE_SIZE.
GL_INVALID_VALUE | xoffset was less than –b, or xoffset + width was greater than w – b, where w is the GL_TEXTURE_WIDTH, and b is the width of the GL_TEXTURE_BORDER of the texture image being modified. Note that w includes twice the border width.
GL_INVALID_VALUE | width was less than –b, where b is the border width of the texture array.
GL_INVALID_VALUE | border was not zero or 1.
GL_INVALID_OPERATION | The texture array was not defined by a previous glTexImage1D operation.
GL_INVALID_OPERATION | glTexSubImage1D was called between a call to glBegin and the corresponding call to glEnd.

See Also

glDrawPixels, glEnable, glFog, glGetTexImage, glIsEnabled, glPixelStore, glPixelTransfer, glTexEnv, glTexGen, glTexImage1D, glTexParameter


glTexSubImage2D

The glTexSubImage2D function specifies a portion of an existing one-dimensional texture image. You cannot define a new texture with glTexSubImage2D.

void glTexSubImage2D(
  GLenum target,
  GLint level,
  GLint xoffset,
  GLint yoffset,
  GLsizei width,
  GLsizei height,
  GLenum format,
  GLenum type,
  const GLvoid *pixels
);

Parameters

target
The target texture. Must be GL_TEXTURE_2D.
level
The level-of-detail number. Level 0 is the base image. Level n is the nth mipmap reduction image.
xoffset
A texel offset in the x direction within the texture array.
yoffset
A texel offset in the y direction within the texture array.
width
The width of the texture sub-image.
height
The height of the texture sub-image.
format
The format of the pixel data. It can assume one of the following symbolic values:
GL_COLOR_INDEX
Each element is a single value, a color index. It is converted to fixed point format (with an unspecified number of 0 bits to the right of the binary point), shifted left or right depending on the value and sign of GL_INDEX_SHIFT, and added to GL_INDEX_OFFSET (see glPixelTransfer). The resulting index is converted to a set of color components using the GL_PIXEL_MAP_I_TO_R, GL_PIXEL_MAP_I_TO_G, GL_PIXEL_MAP_I_TO_B, and GL_PIXEL_MAP_I_TO_A tables, and clamped to the range [0,1].
GL_RED
Each element is a single red component. It is converted to floating point format and assembled into an RGBA element by attaching 0.0 for green and blue, and 1.0 for alpha. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_GREEN
Each element is a single green component. It is converted to floating point format and assembled into an RGBA element by attaching 0.0 for red and blue, and 1.0 for alpha. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_BLUE
Each element is a single blue component. It is converted to floating point format and assembled into an RGBA element by attaching 0.0 for red and green, and 1.0 for alpha. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_ALPHA
Each element is a single alpha component. It is converted to floating point format and assembled into an RGBA element by attaching 0.0 for red, green, and blue. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_RGB
Each element is an RGB triple. It is converted to floating point format and assembled into an RGBA element by attaching 1.0 for alpha. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_RGBA
Each element is a complete RGBA element. It is converted to floating point. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_LUMINANCE
Each element is a single luminance value. It is converted to floating point format, and then assembled into an RGBA element by replicating the luminance value three times for red, green, and blue, and attaching 1.0 for alpha. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
GL_LUMINANCE_ALPHA
Each element is a luminance/alpha pair. It is converted to floating point format, and then assembled into an RGBA element by replicating the luminance value three times for red, green, and blue. Each component is then multiplied by the signed scale factor GL_c_SCALE, added to the signed bias GL_c_BIAS, and clamped to the range [0,1] (see glPixelTransfer).
type
The data type of the pixel data. The following symbolic values are accepted: GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, and GL_FLOAT.
pixels
A pointer to the image data in memory.

Remarks

Two-dimensional texturing for a primitive is enabled using glEnable and glDisable with the argument GL_TEXTURE_2D. During texturing, part of a specified texture image is mapped onto each enabled primitive. You use the glTexSubImage2D function to specify a contiguous sub-image of an existing one-dimensional texture image for texturing.

The texels referenced by pixels replace a region of the existing texture array with x indexes of xoffset and xoffset + (width – 1) inclusive and y indexes of yoffset and Yoffset + (height – 1) inclusive. This region cannot include any texels outside the range of the originally specified texture array.

Specifying a sub-image with a width of zero has no effect and does not generate an error.

Texturing has no effect in color-index mode.

In general, texture images can be represented by the same data formats as the pixels in a glDrawPixels command, except that GL_STENCIL_INDEX and GL_DEPTH_COMPONENT cannot be used. The glPixelStore and glPixelTransfer modes affect texture images in exactly the way they affect glDrawPixels.

The following functions retrieve information related to glTexSubImage2D:

glGetTexImage

glIsEnabled with argument GL_TEXTURE_2D

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_ENUM | target was not GL_TEXTURE_2D.
GL_INVALID_ENUM | format was not an accepted constant.
GL_INVALID_ENUM | type was not an accepted constant.
GL_INVALID_ENUM | type was GL_BITMAP and format was not GL_COLOR_INDEX.
GL_INVALID_VALUE | level was less than zero or greater than log (2) max, where max was the returned value of GL_MAX_TEXTURE_SIZE.
GL_INVALID_VALUE | xoffset was less than -b; or xoffset + width was greater than w - b; or yoffset was less than -b; or yoffset + height was greater than h - b, where w is the GL_TEXTURE_WIDTH, h is the GL_TEXTURE_HEIGHT, and b is the width of the GL_TEXTURE_BORDER of the texture image being modified. Note that w and h include twice the border width.
GL_INVALID_VALUE | width was less than -b, where b is the border width of the texture array.
GL_INVALID_VALUE | border was not zero or 1.
GL_INVALID_OPERATION | The texture array was not defined by a previous glTexImage2D operation.
GL_INVALID_OPERATION | glTexSubImage2D was called between a call to glBegin and the corresponding call to glEnd.

See Also

glDrawPixels, glEnable, glFog, glGetTexImage, glIsEnabled, glPixelStore, glPixelTransfer, glTexEnv, glTexGen, glTexImage2D, glTexParameter


glTranslate

glTranslated, glTranslatef

The glTranslated and glTranslatef functions multiply the current matrix by a translation matrix.

void glTranslated(
  GLdouble x,
  GLdouble y,
  GLdouble z
);

void glTranslatef(
  GLfloat x,
  GLfloat y,
  GLfloat z
);

Parameters

x, y, z
The x, y, and z coordinates of a translation vector.

Remarks

The glTranslate function moves the coordinate system origin to the point specified by (x, y, z). The translation vector is used to compute a 4x4 translation matrix:

The current matrix (see glMatrixMode) is multiplied by this translation matrix, with the product replacing the current matrix. That is, if M is the current matrix and T is the translation matrix, then M is replaced with M·T.

If the matrix mode is either GL_MODELVIEW or GL_PROJECTION, all objects drawn after glTranslate is called are translated. Use glPushMatrix and glPopMatrix to save and restore the untranslated coordinate system.

The following functions retrieve information related to glTranslated and glTranslatef:

glGet with argument GL_MATRIX_MODE

glGet with argument GL_MODELVIEW_MATRIX

glGet with argument GL_PROJECTION_MATRIX

glGet with argument GL_TEXTURE_MATRIX

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_OPERATION | glTranslate was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glEnd, glMatrixMode, glMultMatrix, glPushMatrix, glRotate, glScale


glVertex

glVertex2d, glVertex2f, glVertex2i, glVertex2s, glVertex3d, glVertex3f, glVertex3i, glVertex3s, glVertex4d, glVertex4f, glVertex4i, glVertex4s, glVertex2dv, glVertex2fv, glVertex2iv, glVertex2sv, glVertex3dv, glVertex3fv, glVertex3iv, glVertex3sv, glVertex4dv, glVertex4fv, glVertex4iv, glVertex4sv

These functions specify a vertex.

void glVertex2d(
  GLdouble x,
  GLdouble y
);

void glVertex2f(
  GLfloat x,
  GLfloat y
);

void glVertex2i(
  GLint x,
  GLint y
);

void glVertex2s(
  GLshort x,
  GLshort y
);

void glVertex3d(
  GLdouble x,
  GLdouble y,
  GLdouble z
);

void glVertex3f(
  GLfloat x,
  GLfloat y,
  GLfloat z
);

void glVertex3i(
  GLint x,
  GLint y,
  GLint z
);

void glVertex3s(
  GLshort x,
  GLshort y,
  GLshort z
);

void glVertex4d(
  GLdouble x,
  GLdouble y,
  GLdouble z,
  GLdouble w
);

void glVertex4f(
  GLfloat x,
  GLfloat y,
  GLfloat z,
  GLfloat w
);

void glVertex4i(
  GLint x,
  GLint y,
  GLint z,
  GLint w
);

void glVertex4s(
  GLshort x,
  GLshort y,
  GLshort z,
  GLshort w
);

Parameters

x, y, z, w
The x, y, z, and w coordinates of a vertex. Not all parameters are present in all forms of the command.
void glVertex2dv(
  const GLdouble *v
);

void glVertex2fv(
  const GLfloat *v
);

void glVertex2iv(
  const GLint *v
);

void glVertex2sv(
  const GLshort *v
);

void glVertex3dv(
  const GLdouble *v
);

void glVertex3fv(
  const GLfloat *v
);

void glVertex3iv(
  const GLint *v
);

void glVertex3sv(
  const GLshort *v
);

void glVertex4dv(
  const GLdouble *v
);

void glVertex4fv(
  const GLfloat *v
);

void glVertex4iv(
  const GLint *v
);

void glVertex4sv(
  const GLshort *v
);

Parameters

v
A pointer to an array of two, three, or four elements. The elements of a two-element array are x and y; of a three-element array, x, y, and z; and of a four-element array, x, y, z, and w.

Remarks

The glVertex function commands are used within glBegin/glEnd pairs to specify point, line, and polygon vertices. The current color, normal, and texture coordinates are associated with the vertex when glVertex is called.

When only x and y are specified, z defaults to 0.0 and w defaults to 1.0. When x, y, and z are specified, w defaults to 1.0.

Invoking glVertex outside of a glBegin/glEnd pair results in undefined behavior.

See Also

glBegin, glCallList, glColor, glEdgeFlag, glEnd, glEvalCoord, glIndex, glMaterial, glNormal, glRect, glTexCoord


glVertexPointer

The glVertexPointer function defines an array of vertex data.

void glVertexPointer(
  GLint size,
  GLenum type,
  GLsizei stride,
  GLsizei count,
  const GLvoid *pointer
);

Parameters

size
The number of coordinates per vertex. The value of size must be 2, 3, or 4.
type
The data type of each coordinate in the array using the following symbolic constants: GL_SHORT, GL_INT, GL_FLOAT, and GL_DOUBLE.
stride
The byte offset between consecutive vertices. When stride is zero, the vertices are tightly packed in the array.
count
The number of vertices, counting from the first, that are static.
pointer
A pointer to the first coordinate of the first vertex in the array.

Remarks

The glVertexPointer function specifies the location and data of an array of vertex coordinates to use when rendering. The size parameter specifies the number of coordinates per vertex. The type parameter specifies the data type of each vertex coordinate. The stride parameter determines the byte offset from one vertex to the next, enabling the packing of vertices and attributes in a single array or storage in separate arrays. In some implementations, storing the vertices and attributes in a single array can be more efficient than using separate arrays. Starting from the first vertex element, the count parameter indicates the total number of static elements. Your application can modify static elements, but once the elements are modified, the application must explicitly specify the array again before using the array for any rendering. Non-static array elements are not accessed until you call glDrawArrays or glArrayElement.

A vertex array is enabled when you specify the GL_VERTEX_ARRAY constant with glEnableClientState. When enabled, glDrawArrays and glArrayElement use the vertex array. By default, the vertex array is disabled.

You cannot include glVertexPointer in display lists.

When you specify a vertex array using glVertexPointer, the values of all the function's vertex array parameters are saved in a client-side state and static array elements can be cached. Because the vertex array parameters are client-side state, their values are not saved or restored by glPushAttrib and glPopAttrib.

Although no error is generated if you call glVertexPointer within glBegin and glEnd pairs, the results are undefined.

The following functions retrieve information related to glVertexPointer:

glGet with argument GL_VERTEX_ARRAY_SIZE

glGet with argument GL_VERTEX_ARRAY_STRIDE

glGet with argument GL_VERTEX_ARRAY_COUNT

glGet with argument GL_VERTEX_ARRAY_TYPE

glGetPointerv with argument GL_VERTEX_ARRAY_POINTER

glIsEnabled with argument GL_VERTEX_ARRAY

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_VALUE | size was not 2, 3, or 4.
GL_INVALID_ENUM | type was not an accepted value.
GL_INVALID_VALUE | stride or count was negative.

See Also

glArrayElement, glColorPointer, glDrawArrays, glEdgeFlagPointer, glEnableClientState, glGetPointerv, glGetString, glIndexPointer, glIsEnabled, glNormalPointer, glTexCoordPointer


glViewport

The glViewport function sets the viewport.

void glViewport(
  GLint x,
  GLint y,
  GLsizei width,
  GLsizei height
);

Parameters

x, y
The lower-left corner of the viewport rectangle, in pixels. The default is (0,0).
width, height
The width and height, respectively, of the viewport. When an OpenGL context is first attached to a window, width and height are set to the dimensions of that window.

Remarks

The glViewport function specifies the affine transformation of x and y from normalized device coordinates to window coordinates. Let (x (nd) , y (nd) ) be normalized device coordinates. The window coordinates (x (w) , y (w) ) are then computed as follows:

Viewport width and height are silently clamped to a range that depends on the implementation. This range is queried by calling glGet with argument GL_MAX_VIEWPORT_DIMS.

The following functions retrieve information related to glViewport:

glGet with argument GL_VIEWPORT

glGet with argument GL_MAX_VIEWPORT_DIMS

Error Codes

The following are the error codes generated and their conditions.

Error Code | Condition
GL_INVALID_VALUE | Either width or height was negative.
GL_INVALID_OPERATION | glViewport was called between a call to glBegin and the corresponding call to glEnd.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in gl.h.
Import Library: Link with opengl32.lib.

See Also

glBegin, glDepthRange


gluBeginCurve, gluEndCurve

The gluBeginCurve and gluEndCurve functions delimit a NURBS curve definition.

void gluBeginCurve(
  GLUnurbsObj * nobj
);

void gluEndCurve(
  GLUnurbsObj * nobj
);

Parameters

nobj
The NURBS object (created with gluNewNurbsRenderer).

Remarks

Use gluBeginCurve to mark the beginning of a NURBS curve definition. After calling gluBeginCurve, make one or more calls to gluNurbsCurve to define the attributes of the curve. Exactly one of the calls to gluNurbsCurve must have a curve type of GL_MAP1_VERTEX_3 or GL_MAP1_VERTEX_4. To mark the end of the NURBS curve definition, call gluEndCurve.

OpenGL evaluators are used to render the NURBS curve as a series of line segments. Evaluator state is preserved during rendering with glPushAttrib(GL_EVAL_BIT) and glPopAttrib. For information on exactly what state these calls preserve, see glPushAttrib.

Example

The following functions render a textured NURBS curve with normals; texture coordinates and normals are also specified as NURBS curves:

gluBeginCurve(nobj);
gluNurbsCurve(nobj, . . ., GL_MAP1_TEXTURE_COORD_2);
gluNurbsCurve(nobj, . . ., GL_MAP1_NORMAL);
gluNurbsCurve(nobj, . . ., GL_MAP1_VERTEX_4);
gluEndCurve(nobj);

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

glPopAttrib, glPushAttrib, gluBeginSurface, gluBeginTrim, gluNewNurbsRenderer, gluNurbsCurve


gluBeginPolygon, gluEndPolygon

The gluBeginPolygon and gluEndPolygon functions delimit a polygon description.

void gluBeginPolygon(
  GLUtesselator * tess
);

void gluEndPolygon(
  GLUtesselator * tess
);

Parameters

tess
The tessellation object (created with gluNewTess).

Remarks

Use gluBeginPolygon and gluEndPolygon to delimit the definition of a nonconvex polygon.

To define such a polygon
  1. Call gluBeginPolygon.
  2. Define the contours of the polygon by calling gluTessVertex for each vertex and gluNextContour to start each new contour.
  3. Call gluEndPolygon to signal the end of the definition.

    Once gluEndPolygon is called, the polygon is tessellated, and the resulting triangles are described through callbacks. For descriptions of the callback functions, see gluTessCallback.

    Note The gluBeginPolygon and gluEndPolygon functions are obsolete and are provided for backward compatibility only. The gluBeginPolygon function is mapped to gluTessBeginPolygon followed by gluTessBeginContour; gluEndPolygon is mapped to gluTessEndPolygon followed by gluTessEndContour.

Example

The following example describes a quadrilateral with a triangular hole:

gluBeginPolygon(tess);
    gluTessVertex(tess, v1, v1);
    gluTessVertex(tess, v2, v2);
    gluTessVertex(tess, v3, v3);
    gluTessVertex(tess, v4, v4);
gluNextContour(tess, GLU_INTERIOR);
    gluTessVertex(tess, v5, v5);
    gluTessVertex(tess, v6, v6);
    gluTessVertex(tess, v7, v7);
gluEndPolygon(tess);

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluNewTess, gluNextContour, gluTessBeginContour, gluTessBeginPolygon, gluTessCallback, gluTessVertex


gluBeginSurface, gluEndSurface

The gluBeginSurface and gluEndSurface functions delimit a NURBS surface definition.

void gluBeginSurface(
  GLUnurbsObj * nobj
);

void gluEndSurface(
  GLUnurbsObj * nobj
);

Parameters

nobj
The NURBS object (created with gluNewNurbsRenderer).

Remarks

The gluBeginSurface and gluEndSurface functions mark the beginning and end of NURBS surface definitions, which are defined with calls to gluNurbsSurface.

To define a surface
  1. Call gluBeginSurface to mark the beginning of a NURBS surface definition.
  2. Make one or more calls to gluNurbsSurface to define the attributes of the surface.

    Exactly one of these calls to gluNurbsSurface must have a surface type of GL_MAP2_VERTEX_3 or GL_MAP2_VERTEX_4.

  3. To mark the end of the NURBS surface definition, call gluEndSurface.

The gluBeginTrim, gluPwlCurve, gluNurbsCurve, and gluEndTrim functions support trimming of NURBS surfaces. For more information, see gluBeginTrim.

Use OpenGL evaluators to render the NURBS surface as a set of polygons. Preserve the evaluator state during rendering with glPushAttrib(GL_EVAL_BIT) and glPopAttrib. For information on exactly what states these calls preserve, see glPushAttrib.

Example

The following functions render a textured NURBS surface with normals; the texture coordinates and normals are also described as NURBS surfaces:

gluBeginSurface(nobj);
    gluNurbsSurface(nobj, . . ., GL_MAP2_TEXTURE_COORD_2);
    gluNurbsSurface(nobj, . . ., GL_MAP2_NORMAL);
    gluNurbsSurface(nobj, . . ., GL_MAP2_VERTEX_4);
gluEndSurface(nobj);

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluBeginCurve, gluBeginTrim, gluNewNurbsRenderer, gluNurbsCurve, gluNurbsSurface, gluPwlCurve


gluBeginTrim, gluEndTrim

The gluBeginTrim and gluEndTrim functions delimit a NURBS trimming loop definition.

void gluBeginTrim(
  GLUnurbsObj * nobj
);

void gluEndTrim(
  GLUnurbsObj * nobj
);

Parameters

nobj
The NURBS object (created with gluNewNurbsRenderer).

Remarks

Use gluBeginTrim to mark the beginning of a trimming loop, and gluEndTrim to mark the end of a trimming loop. A trimming loop is a set of oriented curve segments (forming a closed curve) that define boundaries of a NURBS surface. You include these trimming loops in the definition of a NURBS surface, between calls to gluBeginSurface and gluEndSurface.

The definition for a NURBS surface can contain many trimming loops. For example, if you write a definition for a NURBS surface that resembles a rectangle with a hole punched out, the definition would contain two trimming loops. One loop would define the outer edge of the rectangle; the other would define the punched-out hole. The definitions of each of these trimming loops would be bracketed by a gluBeginTrim/gluEndTrim pair.

The definition of a single closed trimming loop can consist of multiple curve segments, each described as a series of line segments that form a linear curve (see gluPwlCurve), as a single NURBS curve (see gluNurbsCurve), or as a combination of both in any order. The only library calls that can appear in a trimming-loop definition (between the calls to gluBeginTrim and gluEndTrim) are gluPwlCurve and gluNurbsCurve.

The displayed area of the NURBS surface is the region in the domain to the left of the trimming curve as the curve parameter increases. Thus, the retained region of the NURBS surface is inside a counterclockwise trimming loop and outside a clockwise trimming loop. For the rectangle mentioned earlier, the trimming loop for the outer edge of the rectangle runs counterclockwise, while the trimming loop for the punched-out hole runs clockwise.

If you use more than one curve to define a single trimming loop, the curve segments must form a closed loop (that is, the endpoint of each curve must be the starting point of the next curve, and the endpoint of the final curve must be the starting point of the first curve). If the endpoints of the curve are sufficiently close together but not exactly coincident, they will be forced to match. If the endpoints are not sufficiently close, an error results (see gluNurbsCallback).

If a trimming-loop definition contains multiple curves, the direction of the curves must be consistent (that is, the inside must be to the left of all of the curves). You can use nested trimming loops as long as the curve orientations alternate correctly. Trimming curves cannot be self-intersecting, nor can they intersect one another (or an error results).

If no trimming information is given for a NURBS surface, the entire surface is drawn.

Example

This code fragment defines a trimming loop that consists of one piecewise linear curve and two NURBS curves:

gluBeginTrim(nobj);
    gluPwlCurve(. . ., GLU_MAP1_TRIM_2);
    gluNurbsCurve(. . ., GLU_MAP1_TRIM_2);
    gluNurbsCurve(. . ., GLU_MAP1_TRIM_3);
gluEndTrim(nobj);

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluBeginSurface, gluEndSurface, gluNewNurbsRenderer, gluNurbsCallback, gluNurbsCurve, gluPwlCurve


gluBuild1DMipmaps

The gluBuild1DMipmaps function creates 1-D mipmaps.

int gluBuild1DMipmaps(
  GLenum target,
  GLint components,
  GLint width,
  GLenum format,
  GLenum type,
  const void * data
);

Parameters

target
The target texture. Must be GL_TEXTURE_1D.
components
The number of color components in the texture. Must be 1, 2, 3, or 4.
width
The width of the texture image.
format
The format of the pixel data. The following values are valid: GL_COLOR_INDEX, GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA, GL_BGR_EXT, GL_BGRA_EXT, GL_LUMINANCE, or GL_LUMINANCE_ALPHA.
type
The data type for data. The following values are valid: GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, or GL_FLOAT.
data
A pointer to the image data in memory.

Remarks

The gluBuild1DMipmaps function obtains the input image and generates all mipmap images (using gluScaleImage) so that the input image can be used as a mipmapped texture image. The glTexImage1D function is then called to load each of the images. If the width of the input image is not a power of two, then the image is scaled to the nearest power of two before the mipmaps are generated.

A return value of zero indicates success. Otherwise, a GLU error code is returned (see gluErrorString).

For a description of the acceptable values for the format parameter, see glTexImage1D. For a description of the acceptable values for the type parameter, see glDrawPixels.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

glDrawPixels, glTexImage1D, gluBuild2DMipmaps, gluScaleImage


gluBuild2DMipmaps

The gluBuild2DMipmaps function creates 2-D mipmaps.

int gluBuild2DMipmaps(
  GLenum target,
  GLint components,
  GLint width,
  GLint height,
  GLenum format,
  GLenum type,
  const void * data
);

Parameters

target
The target texture. Must be GL_TEXTURE_2D.
components
The number of color components in the texture. Must be 1, 2, 3, or 4.
width, height
The width and height, respectively, of the texture image.
format
The format of the pixel data. Must be one of the following: GL_COLOR_INDEX, GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA, GL_BGR_EXT, GL_BGRA_EXT, GL_LUMINANCE, or GL_LUMINANCE_ALPHA.
type
The data type for data. Must be one of the following: GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, or GL_FLOAT.
data
A pointer to the image data in memory.

Remarks

The gluBuild2DMipmaps function obtains the input image and generates all mipmap images (using gluScaleImage) so the input image can be used as a mipmapped texture image. To load each of the images, call glTexImage2D. If the dimensions of the input image are not powers of two, then the image is scaled so that both the width and height are powers of two before the mipmaps are generated.

A return value of zero indicates success. Otherwise, a GLU error code is returned (see gluErrorString).

For a description of the acceptable values for the format parameter, see glTexImage2D. For a description of the acceptable values for type, see glDrawPixels.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

glDrawPixels, glTexImage2D, gluBuild1DMipmaps, gluScaleImage


gluCylinder

The gluCylinder function draws a cylinder.

void gluCylinder(
  GLUquadricObj * qobj,
  GLdouble baseRadius,
  GLdouble topRadius,
  GLdouble height,
  GLint slices,
  GLint stacks
);

Parameters

qobj
The quadric object (created with gluNewQuadric).
baseRadius
The radius of the cylinder at z = 0.
topRadius
The radius of the cylinder at z = height.
height
The height of the cylinder.
slices
The number of subdivisions around the z-axis.
stacks
The number of subdivisions along the z-axis.

Remarks

The gluCylinder function draws a cylinder oriented along the z-axis. The base of the cylinder is placed at z = 0, and the top at z = height. Like a sphere, a cylinder is subdivided around the z-axis into slices, and along the z-axis into stacks.

Notice that if topRadius is set to zero, then this routine will generate a cone.

If the orientation is set to GLU_OUTSIDE (with gluQuadricOrientation), then any generated normals point away from the z-axis. Otherwise, they point toward the z-axis.

If texturing is turned on (with gluQuadricTexture): texture coordinates are generated so that t ranges linearly from 0.0 at z = 0 to 1.0 at z = height; and s ranges from 0.0 at the positive y-axis, to 0.25 at the positive x-axis, to 0.5 at the negative y-axis, to 0.75 at the positive x-axis, and back to 1.0 at the positive y-axis.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluDisk, gluNewQuadric, gluPartialDisk, gluQuadricOrientation, gluQuadricTexture, gluSphere


gluDeleteNurbsRenderer

The gluDeleteNurbsRenderer function destroys a NURBS object.

void gluDeleteNurbsRenderer(
  GLUnurbsObj * nobj
);

Parameters

nobj
The NURBS object to be destroyed (created with gluNewNurbsRenderer).

Remarks

The gluDeleteNurbsRenderer function destroys the NURBS object and frees any memory that it used. After you have called gluDeleteNurbsRenderer, you cannot use nobj again.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluNewNurbsRenderer


gluDeleteQuadric

The gluDeleteQuadric function destroys a quadric object.

void gluDeleteQuadric(
  GLUquadricObj * state
);

Parameters

state
The quadric object to be destroyed (created with gluNewQuadric).

Remarks

The gluDeleteQuadric function destroys the quadric object and frees any memory that it used. After you have called gluDeleteQuadric, you cannot use state again.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluNewQuadric


gluDeleteTess

The gluDeleteTess function destroys a tessellation object.

void gluDeleteTess(
  GLUtesselator * tess
);

Parameters

tess
The tessellation object to destroy (created with gluNewTess).

Remarks

The gluDeleteTess function destroys the indicated tessellation object and frees any memory that it used.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluNewTess, gluTessBeginPolygon, gluTessCallback


gluDisk

The gluDisk function draws a disk.

void gluDisk(
  GLUquadricObj * qobj,
  GLdouble innerRadius,
  GLdouble outerRadius,
  GLint slices,
  GLint loops
);

Parameters

qobj
The quadric object (created with gluNewQuadric).
innerRadius
The inner radius of the disk (may be zero).
outerRadius
The outer radius of the disk.
slices
The number of subdivisions around the z-axis.
loops
The number of concentric rings about the origin into which the disk is subdivided.

Remarks

The gluDisk function renders a disk on the z = 0 plane. The disk has a radius of outerRadius, and contains a concentric circular hole with a radius of innerRadius. If innerRadius is 0, then no hole is generated. The disk is subdivided around the z-axis into slices (like pizza slices) and also about the z-axis into rings (as specified by slices and loops, respectively).

With respect to orientation, the positive z-side of the disk is considered to be outside (see gluQuadricOrientation). This means that if the orientation is set to GLU_OUTSIDE, then any normals generated point along the positive z-axis.

If texturing is turned on (with gluQuadricTexture), texture coordinates are generated linearly such that where r = outerRadius, the value at (r, 0, 0) is (1, 0.5); at (0, r, 0) it is (0.5, 1); at (r, 0, 0) it is (0, 0.5); and at (0, r, 0) it is (0.5, 0).

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluCylinder, gluNewQuadric, gluPartialDisk, gluQuadricTexture, gluSphere


gluErrorString

The gluErrorString function produces an error string from an OpenGL or GLU error code. The error string is ANSI only.

const GLubyte* gluErrorString(
  GLenum errCode
);

Parameters

errCode
An OpenGL or GLU error code.

Remarks

The gluErrorString function produces an error string from an OpenGL or GLU error code. The string is in an ISO Latin 1 format. For example, gluErrorString(GL_OUT_OF_MEMORY) returns the string out of memory.

The standard GLU error codes are GLU_INVALID_ENUM, GLU_INVALID_VALUE, and GLU_OUT_OF_MEMORY. Certain other GLU functions can return specialized error codes through callbacks. For the list of OpenGL error codes, see glGetError.

The gluErrorString function produces error strings in ANSI only. Whenever possible, use gluErrorStringWIN, which allows ANSI or Unicode error strings. This makes it easier to localize your program for use with another language.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

glGetError, gluNurbsCallback, gluQuadricCallback, gluTessCallback


gluGetNurbsProperty

The gluGetNurbsProperty function gets a NURBS property.

void gluGetNurbsProperty(
  GLUnurbsObj * nobj,
  GLenum property,
  GLfloat * value
);

Parameters

nobj
The NURBS object (created with gluNewNurbsRenderer).
property
The property whose value is to be retrieved. The following values are valid: GLU_SAMPLING_TOLERANCE, GLU_DISPLAY_MODE, GLU_CULLING, GLU_AUTO_LOAD_MATRIX, GLU_PARAMETRIC_TOLERANCE, GLU_SAMPLING_METHOD, GLU_U_STEP, and GLU_V_STEP.
value
A pointer to the location into which the value of the named property is written.

Remarks

Use gluGetNurbsProperty to retrieve properties stored in a NURBS object. These properties affect the way NURBS curves and surfaces are rendered. For information about what the properties are and what they do, see gluNurbsProperty.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluNewNurbsRenderer, gluNurbsProperty


gluGetString

The gluGetString function gets a string that describes the GLU version number or supported GLU extension calls.

const GLubyte *gluGetString(
  GLenum name
);

Parameters

name
Either the version number of GLU (GLU_VERSION) or available vendor-specific extension calls (GLU_EXTENSIONS).

Remarks

The gluGetString function returns a pointer to a static, null-terminated string. When name is GLU_VERSION, the returned string is a value that represents the version number of GLU. The format of the version number is as follows:

<version number><space><vendor-specific information>
(for example, "1.2.11 Microsoft Windows NT")

The version number has the form "major_number.minor_number" or "major_number.minor_number.release_number". The vendor-specific information is optional, and the format and contents depend on the implementation.

When name is GLU_EXTENSIONS, the returned string contains a list of names of supported GLU extensions that are separated by spaces. The format of the returned list of names is as follows:

<extension_name><space><extension_name><space> . . .
(for example, "GLU_NURBS GL_TESSELATION")

The extension names cannot contain any spaces.

The gluGetString function is valid for GLU version 1.1 or later.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.


gluGetTessProperty

The gluGetTessProperty function gets a tessellation object property.

void gluTessProperty(
  GLUtesselator * tess,
  GLenum which,
  GLdouble * value
);

Parameters

tess
The tessellation object (created with gluNewTess).
which
The property whose value is to be retrieved. The following values are valid: GLU_TESS_WINDING_RULE, GLU_TESS_BOUNDARY_ONLY, and GLU_TESS_TOLERANCE.
value
A pointer to the location where the value of the named property is written.

Remarks

Use gluGetTessProperty to retrieve properties stored in a tessellation object. These properties affect the way tessellation objects are interpreted and rendered. For information about what the properties are and what they do, see gluTessProperty.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluNewTess, gluTessProperty


gluLoadSamplingMatrices

The gluLoadSamplingMatrices function loads NURBS sampling and culling matrices.

void gluLoadSamplingMatrices(
  GLUnurbsObj *nobj,
  const GLfloat modelMatrix[16],
  const GLfloat projMatrix[16],
  const GLint viewport[4]
);

Parameters

nobj
The NURBS object (created with gluNewNurbsRenderer).
modelMatrix
A modelview matrix (as from a glGetFloatv call).
projMatrix
A projection matrix (as from a glGetFloatv call).
viewport
A viewport (as from a glGetIntegerv call).

Remarks

The gluLoadSamplingMatrices function uses modelMatrix, projMatrix, and viewport to recompute the sampling and culling matrices stored in nobj. The sampling matrix determines how finely a NURBS curve or surface must be tessellated to satisfy the sampling tolerance (as determined by the GLU_SAMPLING_TOLERANCE property). The culling matrix is used in deciding if a NURBS curve or surface should be culled before rendering (when the GLU_CULLING property is turned on).

The gluLoadSamplingMatrices function is necessary only if the GLU_AUTO_LOAD_MATRIX property is turned off (see gluNurbsProperty). Although it can be convenient to leave the GLU_AUTO_LOAD_MATRIX property turned on, doing so necessitates a round trip to the OpenGL server to get the current values of the modelview matrix, projection matrix, and viewport.)

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

glGetFloatv, glGetIntegerv, gluGetNurbsProperty, gluNewNurbsRenderer


gluLookAt

The gluLookAt function defines a viewing transformation.

void gluLookAt(
  GLdouble eyex,
  GLdouble eyey,
  GLdouble eyez,
  GLdouble centerx,
  GLdouble centery,
  GLdouble centerz,
  GLdouble upx,
  GLdouble upy,
  GLdouble upz
);

Parameters

eyex, eyey, eyez
The position of the eye point.
centerx, centery, centerz
The position of the reference point.
upx, upy, upz
The direction of the up vector.

Remarks

The gluLookAt function creates a viewing matrix derived from an eye point, a reference point indicating the center of the scene, and an up vector. The matrix maps the reference point to the negative z-axis and the eye point to the origin, so that when you use a typical projection matrix, the center of the scene maps to the center of the viewport. Similarly, the direction described by the up vector projected onto the viewing plane is mapped to the positive y-axis so that it points upward in the viewport. The up vector must not be parallel to the line of sight from the eye to the reference point.

The matrix generated by gluLookAt postmultiplies the current matrix.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

glFrustum, gluPerspective


gluNewNurbsRenderer

The gluNewNurbsRenderer function creates a NURBS object.

GLUnurbsObj* gluNewNurbsRenderer(
  void
);

Remarks

The gluNewNurbsRenderer function creates and returns a pointer to a new NURBS object. Refer to this object when calling NURBS rendering and control functions. A return value of zero means there is not enough memory to allocate to the object.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluBeginCurve, gluBeginSurface, gluBeginTrim, gluDeleteNurbsRenderer, gluNurbsCallback, gluNurbsProperty


gluNewQuadric

The gluNewQuadric function creates a quadric object.

GLUquadricObj* gluNewQuadric(
  void
);

Remarks

The gluNewQuadric function creates and returns a pointer to a new quadric object. Refer to this object when calling quadric rendering and control functions. A return value of zero means there is not enough memory to allocate to the object.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluCylinder, gluDeleteQuadric, gluDisk, gluPartialDisk, gluQuadricCallback, gluQuadricDrawStyle, gluQuadricNormals, gluQuadricOrientation, gluQuadricTexture, gluSphere


gluNewTess

The gluNewTess function creates a tessellation object.

GLUtesselator* gluNewTess(
  void
);

Remarks

The gluNewTess function creates and returns a pointer to a new tessellation object. Refer to this object when calling tessellation functions. A return value of zero means there is not enough memory to allocate to the object.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluDeleteTess, gluTessBeginPolygon, gluTessCallback


gluNextContour

The gluNextContour function marks the beginning of another contour.

void gluNextContour(
  GLUtesselator * tess,
  GLenum type
);

Parameters

tess
The tessellation object (created with gluNewTess).
type
The type of the contour being defined. The following values are valid.
Value | Meaning
GLU_EXTERIOR | An exterior contour defines an exterior boundary of the polygon.
GLU_INTERIOR | An interior contour defines an interior boundary of the polygon (such as a hole).
GLU_UNKNOWN | An unknown contour is analyzed by the library to determine whether it is interior or exterior.
GLU_CCW, GLU_CW The first GLU_CCW or GLU_CW contour defined is considered to be exterior. All other contours are considered to be exterior if they are oriented in the same direction (clockwise or counterclockwise) as the first contour, and interior if they are not.

If one contour is of type GLU_CCW or GLU_CW, then all contours must be of the same type (if they are not, then all GLU_CCW and GLU_CW contours will be changed to GLU_UNKNOWN). Note that there is no real difference between the GLU_CCW and GLU_CW contour types.

Remarks

Use the gluNextContour function to describe polygons with multiple contours. After you describe the first contour through a series of gluTessVertex calls, a gluNextContour call indicates that the previous contour is complete and that the next contour is about to begin. Perform another series of gluTessVertex calls to describe the new contour. Repeat this process until all contours have been described.

The type parameter defines what type of contour follows.

To define the type of the first contour, you can call gluNextContour before describing the first contour. If you do not call gluNextContour before the first contour, the first contour is marked GLU_EXTERIOR.

Note The gluNextContour function is obsolete and is provided for backwards compatibility only. The gluNextContour function is mapped to gluTessEndContour followed by gluTessBeginContour.

Example

You can describe a quadrilateral with a triangular hole in it as follows:

gluBeginPolygon(tess);
    gluTessVertex(tess, v1, v1);
    gluTessVertex(tess, v2, v2);
    gluTessVertex(tess, v3, v3);
    gluTessVertex(tess, v4, v4);
gluNextContour(tess, GLU_INTERIOR);
    gluTessVertex(tess, v5, v5);
    gluTessVertex(tess, v6, v6);
    gluTessVertex(tess, v7, v7);
gluEndPolygon(tess);

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluNewTess, gluTessBeginContour, gluTessBeginPolygon, gluTessCallback, gluTessEndContour, gluTessVertex


gluNurbsCallback

The gluNurbsCallback function defines a callback for a NURBS object.

void gluNurbsCallback(
  GLUnurbsObj * nobj,
  GLenum which,
  void (* fn)( )
);

Parameters

nobj
The NURBS object (created with gluNewNurbsRenderer).
which
The callback being defined. The only valid value is GLU_ERROR. The meaning of GLU_ERROR means that the error function is called when an error is encountered. Its single argument is of type GLenum, and it indicates the specific error that occurred. There are 37 errors unique to NURBS, named GLU_NURBS_ERROR1 through GLU_NURBS_ERROR37. Character strings describing these errors can be retrieved with gluErrorString.
fn
The function that the callback calls.

Remarks

Use gluNurbsCallback to define a callback to be used by a NURBS object. If the specified callback is already defined, it is replaced. If fn is NULL, then any existing callback is erased.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluErrorString, gluNewNurbsRenderer


gluNurbsCurve

The gluNurbsCurve function defines the shape of a NURBS curve.

void gluNurbsCurve(
  GLUnurbsObj * nobj,
  GLint nknots,
  GLfloat * knot,
  GLint stride,
  GLfloat * ctlarray,
  GLint order,
  GLenum type
);

Parameters

nobj
The NURBS object (created with gluNewNurbsRenderer).
nknots
The number of knots in knot. The nknots parameter equals the number of control points plus the order.
knot
An array of nknots nondecreasing knot values.
stride
The offset (as a number of single-precision floating-point values) between successive curve control points.
ctlarray
A pointer to an array of control points. The coordinates must agree with type.
order
The order of the NURBS curve. The order parameter equals degree + 1; hence a cubic curve has an order of 4.
type
The type of the curve. If this curve is defined within a gluBeginCurve/gluEndCurve pair, then the type can be any of the valid one-dimensional evaluator types (such as GL_MAP1_VERTEX_3 or GL_MAP1_COLOR_4). Between a gluBeginTrim/gluEndTrim pair, the only valid types are GLU_MAP1_TRIM_2 and GLU_MAP1_TRIM_3.

Remarks

When gluNurbsCurve appears between a gluBeginCurve/gluEndCurve pair, it describes a curve to be rendered. You associate positional, texture, and color coordinates by presenting each as a separate gluNurbsCurve between a gluBeginCurve/gluEndCurve pair. Do not make more than one call to gluNurbsCurve for color, position, and texture data within a single gluBeginCurve/gluEndCurve pair. Make exactly one call to describe the position of the curve (a type of GL_MAP1_VERTEX_3 or GL_MAP1_VERTEX_4).

When gluNurbsCurve appears between a gluBeginTrim/gluEndTrim pair, it describes a trimming curve on a NURBS surface. If type is GLU_MAP1_TRIM_2, it describes a curve in two-dimensional (u and v) parameter space. If it is GLU_MAP1_TRIM_3, it describes a curve in two-dimensional homogeneous (u, v, and w) parameter space. For more discussion about trimming curves, see gluBeginTrim.

Example

The following functions render a textured NURBS curve with normals:

gluBeginCurve(nobj);
    gluNurbsCurve(nobj, ..., GL_MAP1_TEXTURE_COORD_2);
    gluNurbsCurve(nobj, ..., GL_MAP1_NORMAL);
    gluNurbsCurve(nobj, ..., GL_MAP1_VERTEX_4);
gluEndCurve(nobj);

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluBeginCurve, gluBeginTrim, gluEndCurve, gluEndTrim, gluNewNurbsRenderer, gluPwlCurve


gluNurbsProperty

The gluNurbsProperty function sets a NURBS property.

void gluNurbsProperty(
  GLUnurbsObj * nobj,
  GLenum property,
  GLfloat value
);

Parameters

nobj
The NURBS object (created with gluNewNurbsRenderer).
property
The property to be set. The following values are valid: GLU_SAMPLING_TOLERANCE, GLU_DISPLAY_MODE, GLU_CULLING, GLU_AUTO_LOAD_MATRIX, GLU_PARAMETRIC_TOLERANCE, GLU_SAMPLING_METHOD, GLU_U_STEP, and GLU_V_STEP. For the meaning of these values, see the following Remarks section.
value
The value to which to set the indicated property. The value parameter can be a numeric value or one of the following three values: GLU_PATH_LENGTH, GLU_PARAMETRIC_ERROR, or GLU_DOMAIN_DISTANCE. For the meaning of these values, see the following Remarks section.

Remarks

Use gluNurbsProperty to control properties stored in a NURBS object. These properties affect the way a NURBS curve is rendered. The valid values for property are as follows:

GLU_SAMPLING_TOLERANCE
Specifies the maximum length, in pixels, to use when the sampling method is set to GLU_PATH_LENGTH. The default value is 50.0 pixels.
GLU_DISPLAY_MODE
The value parameter defines how a NURBS surface is to be rendered. You can set value to GLU_FILL, GLU_OUTLINE_POLYGON, or GLU_OUTLINE_PATCH. The following table describes the results of these constants.
Constant | Result
GLU_FILL | The surface is rendered as a set of polygons. This is the default value.
GLU_OUTLINE_POLYGON | The NURBS library draws only the outlines of the polygons created by tessellation.
GLU_OUTLINE_PATCH | Only the outlines of patches and trim curves defined by the user are drawn.

GLU_CULLING
The value parameter is a Boolean value. When value is set to GL_TRUE, NURBS curves whose control points lie outside the current viewport are discarded prior to tessellation. The default is GL_FALSE (because a NURBS curve cannot fall entirely within the convex hull of its control points).
GLU_PARAMETRIC_TOLERANCE
Specifies the maximum distance, in pixels, to use when the sampling method is set to GLU_PARAMETRIC_ERROR. The default value is 0.5.
GLU_SAMPLING_METHOD
Specifies how to tessallate a NURBS surface. GLU_SAMPLING_METHOD can have one of the following three values.
Value | Meaning
GLU_PATH_LENGTH | The default value. Specifies that surfaces rendered with the maximum length, in pixels, of the edges of the tessellation polygons are no greater than the value specified by GLU_SAMPLING_TOLERANCE.
GLU_PARAMETRIC_ERROR | Specifies that in rendering the surface, the value of GLU_PARAMETRIC_TOLERANCE specifies the maximum distance, in pixels, between the tessellation polygons and the surfaces they approximate.
GLU_DOMAIN_DISTANCE | Specifies, in parametric coordinates, how many sample points per unit length to take in the u and v dimensions.

GLU_U_STEP
Specifies the number of sample points per unit length taken along the u dimension in parametric coordinates. The value of GLU_U_STEP is used when GLU_SAMPLING_METHOD is set to GLU_DOMAIN_DISTANCE. The default value is 100.
GLU_V_STEP
Specifies the number of sample points per unit length taken along the v dimension in parametric coordinates. The value of GLU_V_STEP is used when GLU_SAMPLING_METHOD is set to GLU_DOMAIN_DISTANCE. The default value is 100.
GLU_AUTO_LOAD_MATRIX
The value parameter is a Boolean value. When set to GL_TRUE, the NURBS code downloads the projection matrix, modelview matrix, and viewport from the OpenGL server to compute sampling and culling matrices for each NURBS curve that is rendered. Sampling and culling matrices are required to determine the tessellation of a NURBS surface into line segments or polygons and to cull a NURBS surface if it lies outside of the viewport. If this mode is set to GL_FALSE, you must provide a projection matrix, modelview matrix, and viewport for the NURBS renderer to use to construct sampling and culling matrices. You can do this with the gluLoadSamplingMatrices function. The default for this mode is GL_TRUE. Changing this mode from GL_TRUE to GL_FALSE does not affect the sampling and culling matrices until you call gluLoadSamplingMatrices.

Note The following property parameters are supported in GLU version 1.1 or later and are not valid for GLU version 1.0:

GLU_PARAMETRIC_TOLERANCE, GLU_SAMPLING_METHOD, GLU_U_STEP, and GLU_V_STEP.

The following value parameters are supported in GLU version 1.1 or later and are not valid for GLU version 1.0:

GLU_PATH_LENGTH, GLU_PARAMETRIC_ERROR, and GLU_DOMAIN_DISTANCE.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluGetNurbsProperty, gluGetString, gluLoadSamplingMatrices, gluNewNurbsRenderer


gluNurbsSurface

The gluNurbsSurface function defines the shape of a NURBS surface.

void gluNurbsSurface(
  GLUnurbsObj * nobj,
  GLint sknot_count,
  GLfloat * sknot,
  GLint tknot_count,
  GLfloat * tknot,
  GLint s_stride,
  GLint t_stride,
  GLfloat * ctlarray,
  GLint sorder,
  GLint torder,
  GLenum type
);

Parameters

nobj
The NURBS object (created with gluNewNurbsRenderer).
sknot_count
The number of knots in the parametric u direction.
sknot
An array of sknot_count nondecreasing knot values in the parametric u direction.
tknot_count
The number of knots in the parametric v direction.
tknot
An array of tknot_count nondecreasing knot values in the parametric v direction.
s_stride
The offset (as a number of single-precision floating-point values) between successive control points in the parametric u direction in ctlarray.
t_stride
The offset (in single-precision floating-point values) between successive control points in the parametric v direction in ctlarray.
ctlarray
An array containing control points for the NURBS surface. The offsets between successive control points in the parametric u and v directions are given by s_stride and t_stride.
sorder
The order of the NURBS surface in the parametric u direction. The order is one more than the degree, hence a surface that is cubic in u has a u order of 4.
torder
The order of the NURBS surface in the parametric v direction. The order is one more than the degree, hence a surface that is cubic in v has a v order of 4.
type
The type of the surface. The type parameter can be any of the valid two-dimensional evaluator types (such as GL_MAP2_VERTEX_3 or GL_MAP2_COLOR_4).

Remarks

Use gluNurbsSurface within a NURBS (Non-Uniform Rational B-Spline) surface definition to describe the shape of a NURBS surface (before any trimming). To mark the beginning of a NURBS surface definition, use the gluBeginSurface function. To mark the end of a NURBS surface definition, use the gluEndSurface function. Call gluNurbsSurface within a NURBS surface definition only.

You associate positional, texture, and color coordinates with a surface by presenting each as a separate gluNurbsSurface between a gluBeginSurface/gluEndSurface pair. Within a single gluBeginSurface/gluEndSurface pair, you can make only one call to gluNurbsSurface for color, position, and texture data. Make exactly one call to describe the position of the surface (a type of GL_MAP2_VERTEX_3 or GL_MAP2_VERTEX_4).

You can trim a NURBS surface by using the gluNurbsCurve and gluPwlCurve functions between calls to gluBeginTrim and gluEndTrim.

Note A gluNurbsSurface with sknot_count knots in the u direction and tknot_count knots in the v direction with orders sorder and torder must have (sknot_count - sorder) multipied by (tknot_count - torder) control points.

Example

The following functions render a textured NURBS surface with normals; the texture coordinates and normals are also NURBS surfaces:

gluBeginSurface(nobj);
    gluNurbsSurface(nobj, . . ., GL_MAP2_TEXTURE_COORD_2);
    gluNurbsSurface(nobj, . . ., GL_MAP2_NORMAL);
    gluNurbsSurface(nobj, . . ., GL_MAP2_VERTEX_4);
gluEndSurface(nobj);

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluBeginSurface, gluBeginTrim, gluEndSurface, gluEndTrim, gluNewNurbsRenderer, gluNurbsCurve, gluPwlCurve


gluOrtho2D

The gluOrtho2D function defines a 2-D orthographic projection matrix.

void gluOrtho2D(
  GLdouble left,
  GLdouble right,
  GLdouble bottom,
  GLdouble top
);

Parameters

left, right
The coordinates for the left and right vertical clipping planes.
bottom, top
The coordinates for the bottom and top horizontal clipping planes.

Remarks

The gluOrtho2D function sets up a two-dimensional orthographic viewing region. This is equivalent to calling glOrtho with near = –1 and far = 1.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

glOrtho, gluPerspective


gluPartialDisk

The gluPartialDisk function draws an arc of a disk.

void gluPartialDisk(
  GLUquadricObj * qobj,
  GLdouble innerRadius,
  GLdouble outerRadius,
  GLint slices,
  GLint loops,
  GLdouble startAngle,
  GLdouble sweepAngle
);

Parameters

qobj
A quadric object (created with gluNewQuadric).
innerRadius
The inner radius of the partial disk (can be zero).
outerRadius
The outer radius of the partial disk.
slices
The number of subdivisions around the z-axis.
loops
The number of concentric rings about the origin into which the partial disk is subdivided.
startAngle
The starting angle, in degrees, of the disk portion.
sweepAngle
The sweep angle, in degrees, of the disk portion.

Remarks

The gluPartialDisk function renders a partial disk on the z = 0 plane. A partial disk is similar to a full disk, except that only the subset of the disk from startAngle through startAngle + sweepAngle is included (where 0 degrees is along the positive y-axis, 90 degrees is along the positive x-axis, 180 degrees is along the negative y-axis, and 270 degrees is along the negative x-axis).

The partial disk has a radius of outerRadius and contains a concentric circular hole with a radius of innerRadius. If innerRadius is zero, then no hole is generated. The partial disk is subdivided around the z-axis into slices (like pizza slices), and also about the z-axis into rings (as specified by slices and loops, respectively).

With respect to orientation, the positive z-side of the partial disk is considered to be outside (see gluQuadricOrientation). This means that if the orientation is set to GLU_OUTSIDE, then any normals generated point along the positive z-axis.

If you have turned on texturing (with gluQuadricTexture), gluPartialDisk generates texture coordinates linearly such that where r = outerRadius, the value at (r, 0, 0) is (1, 0.5); at (0, r, 0) it is (0.5, 1); at (r, 0, 0) it is (0, 0.5); and at (0, r, 0) it is (0.5, 0).

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluCylinder, gluDisk, gluNewQuadric, gluQuadricOrientation, gluQuadricTexture, gluSphere


gluPerspective

The gluPerspective function sets up a perspective projection matrix.

void gluPerspective(
  GLdouble fovy,
  GLdouble aspect,
  GLdouble zNear,
  GLdouble zFar
);

Parameters

fovy
The field of view angle, in degrees, in the y-direction.
aspect
The aspect ratio that determines the field of view in the x-direction. The aspect ratio is the ratio of x (width) to y (height).
zNear
The distance from the viewer to the near clipping plane (always positive).
zFar
The distance from the viewer to the far clipping plane (always positive).

Remarks

The gluPerspective function specifies a viewing frustum into the world coordinate system. In general, the aspect ratio in gluPerspective should match the aspect ratio of the associated viewport. For example, aspect = 2.0 means the viewer's angle of view is twice as wide in x as it is in y. If the viewport is twice as wide as it is tall, it displays the image without distortion.

The matrix generated by gluPerspective is multipled by the current matrix, just as if glMultMatrix were called with the generated matrix. To load the perspective matrix onto the current matrix stack instead, precede the call to gluPerspective with a call to glLoadIdentity.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

glFrustum, glLoadIdentity, glMultMatrix, gluOrtho2D


gluPickMatrix

The gluPickMatrix function defines a picking region.

void gluPickMatrix(
  GLdouble x,
  GLdouble y,
  GLdouble width,
  GLdouble height,
  GLint viewport[4]
);

Parameters

x, y
The center of a picking region in window coordinates.
width, height
The width and height, respectively, of the picking region in window coordinates.
viewport
The current viewport (as from a glGetIntegerv call).

Remarks

The gluPickMatrix function creates a projection matrix you can use to restrict drawing to a small region of the viewport.

To determine which objects are being drawn near the cursor
  1. Use gluPickMatrix to restrict drawing to a small region around the cursor.
  2. Enter selection mode (with glRenderMode), and then rerender the scene.

    All primitives that would have been drawn near the cursor are identified and stored in the selection buffer.

The matrix created by gluPickMatrix is multiplied by the current matrix just as if glMultMatrix were called with the generated matrix.

To effectively use the generated pick matrix for picking
  1. Call glLoadIdentity to load an identity matrix onto the perspective matrix stack.
  2. Call gluPickMatrix.
  3. Call a function (such as gluPerspective) to multiply the perspective matrix by the pick matrix.

When using gluPickMatrix to pick NURBS, be careful to turn off the NURBS property GLU_AUTO_LOAD_MATRIX. If GLU_AUTO_LOAD_MATRIX is not turned off, any NURBS surface rendered is subdivided differently with the pick matrix from how it was subdivided without the pick matrix.

Example

When rendering a scene as follows:

glMatrixMode(GL_PROJECTION);
glLoadIdentity( );
gluPerspective(. . .);
glMatrixMode(GL_MODELVIEW);
/* Draw the scene */

the following code selects a portion of the viewport:

glMatrixMode(GL_PROJECTION);
glLoadIdentity( );
gluPickMatrix(x, y, width, height, viewport);
gluPerspective(. . .);
glMatrixMode(GL_MODELVIEW);
/* Draw the scene */

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

glGetIntegerv, glLoadIdentity, glMultMatrix, glRenderMode, gluPerspective


gluProject

The gluProject function maps object coordinates to window coordinates.

int gluProject(
  GLdouble objx,
  GLdouble objy,
  GLdouble objz,
  const GLdouble modelMatrix[16],
  const GLdouble projMatrix[16],
  const GLint viewport[4],
  GLdouble * winx,
  GLdouble * winy,
  GLdouble * winz
);

Parameters

objx, objy, objz
The object coordinates.
modelMatrix
The current modelview matrix (as from a glGetDoublev call).
projMatrix
The current projection matrix (as from a glGetDoublev call).
viewport
The current viewport (as from a glGetIntegerv call).
winx, winy, winz
The computed window coordinates.

Remarks

The gluProject function transforms the specified object coordinates into window coordinates using modelMatrix, projMatrix, and viewport. The result is stored in winx, winy, and winz. A return value of GL_TRUE indicates success, and GL_FALSE indicates failure.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

glGetDoublev, glGetIntegerv, gluUnProject


gluPwlCurve

The gluPwlCurve function describes a piecewise linear NURBS trimming curve.

void gluPwlCurve(
  GLUnurbsObj * nobj,
  GLint count,
  GLfloat * array,
  GLint stride,
  GLenum type
);

Parameters

nobj
The NURBS object (created with gluNewNurbsRenderer).
count
The number of points on the curve.
array
An array containing the curve points.
stride
The offset (a number of single-precision floating-point values) between points on the curve.
type
The type of curve. Must be either GLU_MAP1_TRIM_2 or GLU_MAP1_TRIM_3.

Remarks

The gluPwlCurve function describes a piecewise linear trimming curve for a NURBS surface. A piecewise linear curve consists of a list of coordinates of points in the parameter space for the NURBS surface to be trimmed. These points are connected with line segments to form a curve. If the curve is an approximation to a real curve, the points should be close enough that the resulting path appears curved at the resolution used in the application.

If type is GLU_MAP1_TRIM_2, it describes a curve in two-dimensional (u and v) parameter space. If it is GLU_MAP1_TRIM_3, then it describes a curve in two-dimensional homogeneous (u, v, and w) parameter space. For more information about trimming curves, see gluBeginTrim.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluBeginCurve, gluBeginTrim, gluNewNurbsRenderer, gluNurbsCurve


gluQuadricCallback

The gluQuadricCallback function defines a callback for a quadric object.

void gluQuadricCallback(
  GLUquadricObj * qobj,
  GLenum which,
  void (* fn)( )
);

Parameters

qobj
The quadric object (created with gluNewQuadric).
which
The callback being defined. The only valid value is GLU_ERROR.
Value | Meaning
GLU_ERROR | The gluQuadricCallback function is called when an error is encountered. Its single argument is of type GLenum, and it indicates the specific error that occurred. Character strings describing these errors can be retrieved with the gluErrorString call.

fn
The function to be called.

Remarks

Use gluQuadricCallback to define a new callback to be used by a quadric object. If the specified callback is already defined, it is replaced. If fn is NULL, any existing callback is erased.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluErrorString, gluNewQuadric


gluQuadricDrawStyle

The gluQuadricDrawStyle function specifies the draw style desired for quadrics.

void gluQuadricDrawStyle(
  GLUquadricObj * qobj,
  GLenum drawStyle
);

Parameters

qobj
The quadric object (created with gluNewQuadric).
drawStyle
The desired draw style. The following values are valid.
Value | Meaning
GLU_FILL | Quadrics are rendered with polygon primitives. The polygons are drawn in a counterclockwise fashion with respect to their normals (as defined with gluQuadricOrientation).
GLU_LINE | Quadrics are rendered as a set of lines.
GLU_SILHOUETTE | Quadrics are rendered as a set of lines, except that edges separating coplanar faces will not be drawn.
GLU_POINT | Quadrics are rendered as a set of points.

Remarks

The gluQuadricDrawStyle function specifies the draw style for quadrics rendered with qobj.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluNewQuadric, gluQuadricNormals, gluQuadricOrientation, gluQuadricTexture


gluQuadricNormals

The gluQuadricNormals function specifies what kind of normals are to be used for quadrics.

void gluQuadricNormals(
  GLUquadricObj *qobj,
  GLenum normals
);

Parameters

qobj
The quadric object (created with gluNewQuadric).
normals
The desired type of normals. The following values are valid.
Value | Meaning
GLU_NONE | No normals are generated.
GLU_FLAT | One normal is generated for every facet of a quadric.
GLU_SMOOTH | One normal is generated for every vertex of a quadric. This is the default value.

Remarks

The gluQuadricNormals function specifies what kind of normals are to be used for quadrics rendered with qobj.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluNewQuadric, gluQuadricDrawStyle, gluQuadricOrientation, gluQuadricTexture


gluQuadricOrientation

The gluQuadricOrientation function specifies inside or outside orientation for quadrics.

void gluQuadricOrientation(
  GLUquadricObj * quadObject,
  GLenum orientation
);

Parameters

quadObject
The quadric object (created with gluNewQuadric).
orientation
The desired orientation. The following values are valid.
Value | Meaning
GLU_OUTSIDE | Draw quadrics with normals pointing outward. This is the default value.
GLU_INSIDE | Draw quadrics with normals pointing inward.

Remarks

The gluQuadricOrientation function specifies what kind of orientation is desired for quadrics rendered with quadObject. The interpretation of outward and inward depends on the quadric being drawn.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluNewQuadric, gluQuadricDrawStyle, gluQuadricNormals, gluQuadricTexture


gluQuadricTexture

The gluQuadricTexture function specifies whether quadrics are to be textured.

void gluQuadricTexture(
  GLUquadricObj * quadObject,
  GLboolean textureCoords
);

Parameters

quadObject
The quadric object (created with gluNewQuadric).
textureCoords
A flag indicating whether texture coordinates are to be generated. The following values are valid.
Value | Meaning
GL_TRUE | Generate texture coordinates.
GL_FALSE | Do not generate texture coordinates. This is the default value.

Remarks

The gluQuadricTexture function specifies whether texture coordinates are to be generated for quadrics rendered with quadObject.

The manner in which texture coordinates are generated depends upon the specific quadric rendered.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluNewQuadric, gluQuadricDrawStyle, gluQuadricNormals


gluScaleImage

The gluScaleImage function scales an image to an arbitrary size.

int gluScaleImage(
  GLenum format,
  GLint widthin,
  GLint heightin,
  GLenum typein,
  const void * datain,
  GLint widthout,
  GLint heightout,
  GLenum typeout,
  void * dataout
);

Parameters

format
The format of the pixel data. The following symbolic values are valid: GL_COLOR_INDEX, GL_STENCIL_INDEX, GL_DEPTH_COMPONENT, GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA, GL_BGR_EXT, GL_BGRA_EXT, GL_LUMINANCE, and GL_LUMINANCE_ALPHA.
widthin, heightin
The width and height, respectively, of the source image that is scaled.
typein
The data type for datain. Must be one of the following: GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, or GL_FLOAT.
datain
A pointer to the source image.
widthout, heightout
The width and height, respectively, of the destination image.
typeout
The data type for dataout. Must be one of the following: GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, or GL_FLOAT.
dataout
A pointer to the destination image.

Return Values

If the function succeeds, the return value is zero.

If the function fails, the return value is a GLU error code (see gluErrorString).

Remarks

The gluScaleImage function scales a pixel image using the appropriate pixel store modes to unpack data from the source image and pack data into the destination image.

When shrinking an image, gluScaleImage uses a box filter to sample the source image and create pixels for the destination image. When magnifying an image, the pixels from the source image are linearly interpolated to create the destination image.

For a description of the acceptable values for the format, typein, and typeout parameters, see glReadPixels.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

glDrawPixels, glReadPixels, gluBuild1DMipmaps, gluBuild2DMipmaps, gluErrorString


gluSphere

The gluSphere function draws a sphere.

void gluSphere(
  GLUquadricObj * qobj,
  GLdouble radius,
  GLint slices,
  GLint stacks
);

Parameters

qobj
The quadric object (created with gluNewQuadric).
radius
The radius of the sphere.
slices
The number of subdivisions around the z-axis (similar to lines of longitude).
stacks
The number of subdivisions along the z-axis (similar to lines of latitude).

Remarks

The gluSphere function draws a sphere of the given radius centered around the origin. The sphere is subdivided around the z-axis into slices and along the z-axis into stacks (similar to lines of longitude and latitude).

If the orientation is set to GLU_OUTSIDE (with gluQuadricOrientation), any normals generated point away from the center of the sphere. Otherwise, they point toward the center of the sphere.

If texturing is turned on (with gluQuadricTexture): texture coordinates are generated so that t ranges from 0.0 at z = –radius to 1.0 at z = radius (t increases linearly along longitudinal lines); and s ranges from 0.0 at the positive y-axis, to 0.25 at the positive x-axis, to 0.5 at the positive y-axis, to 0.75 at the positive x-axis, and back to 1.0 at the positive y-axis.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluCylinder, gluDisk, gluNewQuadric, gluPartialDisk, gluQuadricOrientation, gluQuadricTexture


gluTessBeginContour, gluTessEndContour

The gluTessBeginContour and gluTessEndContour functions delimit a contour description.

void gluTessBeginContour(
  GLUtesselator * tess
);

void gluTessEndContour(
  GLUtesselator * tess
);

Parameters

tess
The tessellation object (created with gluNewTess).

Remarks

The gluTessBeginContour and gluTessEndContour functions delimit the definition of a polygon contour. Within each gluTessBeginContour/gluTessEndContour pair, there can be zero or more calls to gluTessVertex. The vertices specify a closed contour (the last vertex of each contour is automatically linked to the first). You can call gluTessBeginContour only between gluTessBeginPolygon and gluTessEndPolygon.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluNewTess, gluTessBeginPolygon, gluTessCallback, gluTessEndPolygon, gluTessNormal, gluTessProperty, gluTessVertex


gluTessBeginPolygon, gluTessEndPolygon

The gluTessBeginPolygon and gluTessEndPolygon functions delimit a polygon description.

void gluTessBeginPolygon(
  GLUtesselator * tess,
  void * polygon_data
);

void gluTessEndPolygon(
  GLUtesselator * tess
);

Parameters

tess
The tessellation object (created with gluNewTess).
polygon_data
A pointer to user-defined polygon data.

Remarks

The gluTessBeginPolygon and gluTessEndPolygon functions delimit the definition of a nonconvex polygon. Within each gluTessBeginPolygon/gluTessEndPolygon pair, include one or more calls to gluTessBeginContour/gluTessEndContour. Within each contour, there are zero or more calls to gluTessVertex. The vertices specify a closed contour (the last vertex of each contour is automatically linked to the first).

The polygon_data parameter is a pointer to a user-defined data structure. If the appropriate callbacks are specified (see gluTessCallback), this pointer is returned to the callback function or functions, making it a convenient way to store per-polygon information.

When you call gluTessEndPolygon, the polygon is tessellated, and the resulting triangles are described through callbacks. For descriptions of the callback functions, see gluTessCallback.

Example

The following describes a quadrilateral with a triangular hole:

gluTessBeginPolygon(tobj, NULL);
  gluTessBeginContour(tobj);
    gluTessVertex(tobj, v1, v1);
    gluTessVertex(tobj, v2, v2);
    gluTessVertex(tobj, v3, v3);
    gluTessVertex(tobj, v4, v4);
  gluTessEndContour(tobj);
  gluTessBeginContour(tobj);
    gluTessVertex(tobj, v5, v5);
    gluTessVertex(tobj, v6, v6);
    gluTessVertex(tobj, v7, v7);
  gluTessEndContour(tobj);
gluTessEndPolygon(tobj);

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluNewTess, gluTessBeginContour, gluTessCallback, gluTessEndContour, gluTessNormal, gluTessProperty, gluTessVertex


gluTessCallback

The gluTessCallback function defines a callback for a tessellation object.

void gluTessCallback(
  GLUtesselator * tess,
  GLenum which,
  void (* fn)( )
);

Parameters

tess
The tessellation object (created with gluNewTess).
which
The callback being defined. The following values are valid: GLU_TESS_BEGIN, GLU_TESS_BEGIN_DATA, GLU_TESS_EDGE_FLAG, GLU_TESS_EDGE_FLAG_DATA, GLU_TESS_VERTEX, GLU_TESS_VERTEX_DATA, GLU_TESS_END, GLU_TESS_END_DATA, GLU_TESS_COMBINE, GLU_TESS_COMBINE_DATA, GLU_TESS_ERROR, and GLU_TESS_ERROR_DATA.

For more information on these callbacks, see the following Remarks section.

fn
The function to be called.

Remarks

Use gluTessCallback to specify a callback to be used by a tessellation object. If the specified callback is already defined, then it is replaced. If fn is NULL, then the existing callback becomes undefined.

The tessellation object uses these callbacks to describe how a polygon that you specify is broken into triangles.

There are two versions of each callback, one with polygon data that you can define and one without. If both versions of a particular callback are specified, the callback with the polygon data you specify will be used. The polygon_data parameter of gluTessBeginPolygon is a copy of the pointer that was specified when gluTessBeginPolygon was called.

The following are valid callbacks:

GLU_TESS_BEGIN
The GLU_TESS_BEGIN callback is invoked like glBegin to indicate the start of a (triangle) primitive. The function takes a single argument of type GLenum. If you set the GLU_TESS_BOUNDARY_ONLY property to GL_FALSE, the argument is set to either GL_TRIANGLE_FAN, GL_TRIANGLE_STRIP, or GL_TRIANGLES. If you set the GLU_TESS_BOUNDARY_ONLY property to GL_TRUE, the argument is set to GL_LINE_LOOP. The function prototype for this callback is as follows:

void begin (GLenum type);

GLU_TESS_BEGIN_DATA
GLU_TESS_BEGIN_DATA is the same as the GLU_TESS_BEGIN callback except that it takes an additional pointer argument. This pointer is identical to the opaque pointer provided when you call gluTessBeginPolygon. The function prototype for this callback is:

void beginData (GLenum type, void * polygon_data);

GLU_TESS_EDGE_FLAG
The GLU_TESS_EDGE_FLAG callback is similar to glEdgeFlag. The function takes a single Boolean flag that indicates which edges lie on the polygon boundary. If the flag is GL_TRUE, then each vertex that follows begins an edge that lies on the polygon boundary; that is, an edge which separates an interior region from an exterior one. If the flag is GL_FALSE, then each vertex that follows begins an edge that lies in the polygon interior. The GLU_TESS_EDGE_FLAG callback (if defined) is invoked before the first vertex callback is made.

Because triangle fans and triangle strips do not support edge flags, the begin callback is not called with GL_TRIANGLE_FAN or GL_TRIANGLE_STRIP if an edge flag callback is provided. Instead, the fans and strips are converted to independent triangles. The function prototype for this callback is:

void edgeFlag (GLboolean flag);

GLU_TESS_EDGE_FLAG_DATA
The GLU_TESS_EDGE_FLAG_DATA callback is the same as the GLU_TESS_EDGE_FLAG callback except that it takes an additional pointer argument. This pointer is identical to the opaque pointer provided when you call gluTessBeginPolygon. The function prototype for this callback is:

void edgeFlagData (GLboolean flag, void * polygon_data);

GLU_TESS_VERTEX
The GLU_TESS_VERTEX callback is invoked between the begin and end callbacks. It is similar to glVertex, and it defines the vertices of the triangles created by the tessellation process. The function takes a pointer as its only argument. This pointer is identical to the opaque pointer that you provided when you defined the vertex (see gluTessVertex). The function prototype for this callback is:

void vertex (void * vertex_data);

GLU_TESS_VERTEX_DATA
The GLU_TESS_VERTEX_DATA is the same as the GLU_TESS_VERTEX callback except that it takes an additional pointer argument. This pointer is identical to the opaque pointer provided when you call gluTessBeginPolygon. The function prototype for this callback is:

void vertexData (void * vertex_data, void * polygon_data);

GLU_TESS_END
The GLU_TESS_END callback serves the same purpose as glEnd. It indicates the end of a primitive, and it takes no arguments. The function prototype for this callback is:

void end (void);

GLU_TESS_END_DATA
The GLU_TESS_END_DATA callback is the same as the GLU_TESS_END callback except that it takes an additional pointer argument. This pointer is identical to the opaque pointer provided when you call gluTessBeginPolygon. The function prototype for this callback is:

void endData (void * polygon_data);

GLU_TESS_COMBINE
Call the GLU_TESS_COMBINE callback to create a new vertex when the tessellation detects an intersection, or to merge features. The function takes four arguments:
  • An array of three elements, each of type Gldouble.
  • An array of four pointers.
  • An array of four elements, each of type GLfloat.
  • A pointer to a pointer.

The function prototype for this callback is:

void combine(GLdouble coords[3], void * vertex_data[4], GLfloat weight[4], void **outData);

The vertex is defined as a linear combination of up to four existing vertices, stored in vertex_data. The coefficients of the linear combination are given by weight; these weights always sum to 1.0. All vertex pointers are valid even when some of the weights are zero. The coords parameter gives the location of the new vertex.

Allocate another vertex, interpolate parameters using vertex_data and weight, and return the new vertex pointer in outData. This handle is supplied during rendering callbacks. Free the memory sometime after calling gluTessEndPolygon.

For example, if the polygon lies in an arbitrary plane in three-dimensional space, and you associate a color with each vertex, the GLU_TESS_COMBINE callback might look like the following:

void myCombine( GLdouble coords[3], VERTEX *d[4],
                         GLfloat w[4], VERTEX **dataOut )
{
    VERTEX *new = new_vertex();
    new->x = coords[0];
    new->y = coords[1];
    new->z = coords[2];
    new->r = w[0]*d[0]->r + w[1]*d[1]->r + w[2]*d[2]->r +
                w[3]*d[3]->r;
    new->g = w[0]*d[0]->g + w[1]*d[1]->g + w[2]*d[2]->g +
                w[3]*d[3]->g;
    new->b = w[0]*d[0]->b + w[1]*d[1]->b + w[2]*d[2]->b +
                w[3]*d[3]->b;
    new->a = w[0]*d[0]->a + w[1]*d[1]->a + w[2]*d[2]->a +
                w[3]*d[3]->a;
        *dataOut = new;
}

When the tessellation detects an intersection, the GLU_TESS_COMBINE or GLU_TESS_COMBINE_DATA callback (see below) must be defined, and must write a non-NULL pointer into dataOut. Otherwise the GLU_TESS_NEED_COMBINE_CALLBACK error occurs, and no output is generated. (This is the only error that can occur during tessellation and rendering.)

GLU_TESS_COMBINE_DATA
The GLU_TESS_COMBINE_DATA callback is the same as the GLU_TESS_COMBINE callback except that it takes an additional pointer argument. This pointer is identical to the opaque pointer provided when you call gluTessBeginPolygon. The function prototype for this callback is:

void combineData (GLdouble coords[3], void *vertex_data[4], GLfloat weight[4], void **outData, void * polygon_data);

GLU_TESS_ERROR
The GLU_TESS_ERROR callback is called when an error is encountered. The one argument is of type GLenum; it indicates the specific error that occurred and is set to one of the following: GLU_TESS_MISSING_BEGIN_POLYGON, GLU_TESS_MISSING_END_POLYGON, GLU_TESS_MISSING_BEGIN_CONTOUR, GLU_TESS_MISSING_END_CONTOUR, GLU_TESS_COORD_TOO_LARGE, or GLU_TESS_NEED_COMBINE_CALLBACK.

Call gluErrorString to retrieve character strings describing these errors. The function prototype for this callback is as follows:

void error (GLenum errno);

The GLU library recovers from the first four errors by inserting the missing call or calls. GLU_TESS_COORD_TOO_LARGE indicates that some vertex coordinate exceeded the predefined constant GLU_TESS_MAX_COORD in absolute value, and that the value has been clamped. (Coordinate values must be small enough that two can be multiplied together without overflow.) GLU_TESS_NEED_COMBINE_CALLBACK indicates that the tessellation detected an intersection between two edges in the input data, and the GLU_TESS_COMBINE or GLU_TESS_COMBINE_DATA callback was not provided. No output will be generated.

GLU_TESS_ERROR_DATA
The GLU_TESS_ERROR_DATA callback is the same as the GLU_TESS_ERROR callback, except that it takes an additional pointer argument. This pointer is identical to the opaque pointer provided when you call gluTessBeginPolygon. The function prototype for this callback is:

void errorData (GLenum errno, void * polygon_data);

Example

You can directly render tessallated polygons as follows:

gluTessCallback(tess, GLU_TESS_BEGIN, glBegin);
gluTessCallback(tess, GLU_TESS_VERTEX, glVertex3dv);
gluTessCallback(tess, GLU_TESS_END, glEnd);
gluTessBeginPolygon(tess, NULL);
    gluTessBeginContour(tess);
        gluTessVertex(tess, v, v);
        . .
    gluTessEndContour(tess);
gluTessEndPolygon(tess);

Store the tessellated polygon in a display list so that it does not need to be tessellated every time it is rendered.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

glBegin, glEdgeFlag, glEnd, glVertex, gluDeleteTess, gluErrorString, gluNewTess, gluTessBeginPolygon, gluTessEndPolygon, gluTessVertex


gluTessNormal

The gluTessNormal function specifies a normal for a polygon.

void gluTessNormal(
  GLUtesselator * tess,
  GLdouble x,
  GLdouble y,
  GLdouble z
);

Parameters

tess
The tessellation object (created with gluNewTess).
x
The x-coordinate component of a normal.
y
The y-coordinate component of a normal.
z
The z-coordinate component of a normal.

Remarks

The gluTessNormal function describes a normal for a polygon that you define. All input data is projected onto a plane perpendicular to one of the three coordinate axes before tessellation, and all output triangles are oriented counterclockwise with respect to the normal. (To obtain clockwise orientation, reverse the sign of the supplied normal). For example, if you know that all polygons lie in the x-y plane, call gluTessNormal(tess, 0.0, 0.0, 1.0) before rendering any polygons.

If the supplied normal is (0,0,0) (the default value), the normal is determined as follows:

  1. The direction of the normal, up to its sign, is found by fitting a plane to the vertices, without regard to how the vertices are connected. It is expected that the input data lies approximately in the plane; otherwise projection perpendicular to one of the three coordinate axes can change the geometry substantially.
  2. The sign of the normal is chosen so that the sum of the signed areas of all input contours is non-negative (where a counterclockwise contour has positive area).

The supplied normal persists until another call to gluTessNormal changes it.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluNewTess, gluTessBeginPolygon, gluTessEndPolygon


gluTessProperty

The gluTessProperty function sets the property of a tessellation object.

void gluTessProperty(
  GLUtesselator * tess,
  GLenum which,
  GLdouble value
);

Parameters

tess
The tessellation object (created with gluNewTess).
which
The property value to set. The following values are valid: GLU_TESS_WINDING_RULE, GLU_TESS_BOUNDARY_ONLY, and GLU_TESS_TOLERANCE.

For more information about these values, see the following Remarks section.

value
The value of the indicated property.

Remarks

The gluTessProperty function controls properties stored in a tessellation object. These properties affect the way the polygons are interpreted and rendered. The following values are valid:

GLU_TESS_WINDING_RULE
Determines which parts of the polygon are on the interior. The value parameter may be set to one of the following: GLU_TESS_WINDING_ODD, GLU_TESS_WINDING_NONZERO, GLU_TESS_WINDING_POSITIVE, GLU_TESS_WINDING_NEGATIVE, or GLU_TESS_WINDING_ABS_GEQ_TWO.

To understand how the winding rule works, first consider that the input contours partition the plane into regions. The winding rule determines which of these regions are inside the polygon.

For a single-contour C, the winding number of a point x is simply the signed number of revolutions we make around x as we travel once around C (where counterclockwise is positive). When there are several contours, the individual winding numbers are summed. This procedure associates a signed integer value with each point x in the plane. Note that the winding number is the same for all points in a single region.

The winding rule classifies a region as "inside" if its winding number belongs to the chosen category (odd, nonzero, positive, negative, or absolute value of at least two). The previous GLU tessellator (prior to GLU 1.2) used the "odd" rule. The "nonzero" rule (GLU_TESS_WINDING_NONZERO) is another common way to define the interior. The other three rules (GLU_TESS_WINDING_POSITIVE, GLU_TESS_WINDING_NEGATIVE, GLU_TESS_WINDING_ABS_GEQ_TWO) are useful for polygon CSG operations.

GLU_TESS_BOUNDARY_ONLY
Specifies a Boolean value (set value to GL_TRUE or GL_FALSE). When you set value to GL_TRUE, a set of closed contours separating the polygon interior and exterior is returned instead of a tessellation. Exterior contours are oriented counterclockwise with respect to the normal; interior contours are oriented clockwise. The GLU_TESS_BEGIN and GLU_TESS_BEGIN_DATA callbacks use the type GL_LINE_LOOP for each contour.
GLU_TESS_TOLERANCE
Specifies a tolerance for merging features to reduce the size of the output. For example, two vertices that are very close to each other might be replaced by a single vertex. The tolerance is multiplied by the largest coordinate magnitude of any input vertex; this specifies the maximum distance that any feature can move as the result of a single merge operation. If a single feature takes part in several merge operations, the total distance moved can be larger.

Feature merging is completely optional; the tolerance is only a hint. The implementation is free to merge in some cases and not in others, or to never merge features at all. The default tolerance is zero.

The current implementation merges vertices only if they are exactly coincident, regardless of the current tolerance. A vertex is spliced into an edge only if the implementation is unable to distinguish which side of the edge the vertex lies on. Two edges are merged only when both endpoints are identical.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluGetTessProperty, gluNewTess


gluTessVertex

The gluTessVertex function specifies a vertex on a polygon.

void gluTessVertex(
  GLUtesselator * tess,
  GLdouble coords[3],
  void * data
);

Parameters

tess
The tessellation object (created with gluNewTess).
coords
The location of the vertex.
data
An opaque pointer passed back to the user with the vertex callback (as specified by gluTessCallback).

Remarks

The gluTessVertex function describes a vertex on a polygon that the user is defining. Successive gluTessVertex calls describe a closed contour. For example, to describe a quadrilateral, call gluTessVertex four times. You can only call gluTessVertex between gluTessBeginContour and gluTessEndContour.

The data parameter normally points to a structure containing the vertex location, as well as other per-vertex attributes such as color and normal. This pointer is passed back to the user through the GLU_VERTEX callback after tessellation (see gluTessCallback).

Example

The following describes a quadrilateral with a triangular hole:

gluTessBeginPolygon(tess, NULL);
  gluTessBeginContour(tess);
    gluTessVertex(tess, v1, v1);
    gluTessVertex(tess, v2, v2);
    gluTessVertex(tess, v3, v3);
    gluTessVertex(tess, v4, v4);
  gluTessEndContour(tess);
gluNextContour(tess, GLU_INTERIOR);
  gluTessBeginContour(tess);
    gluTessVertex(tess, v5, v5);
    gluTessVertex(tess, v6, v6);
    gluTessVertex(tess, v7, v7);
  gluTessEndContour(tess);
gluTessEndPolygon(tess);

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

gluNewTess, gluTessBeginContour, gluTessBeginPolygon, gluTessCallback, gluTessEndContour, gluTessNormal, gluTessProperty


gluUnProject

The gluUnProject function maps window coordinates to object coordinates.

int gluUnProject(
  GLdouble winx,
  GLdouble winy,
  GLdouble winz,
  const GLdouble modelMatrix[16],
  const GLdouble projMatrix[16],
  const GLint viewport[4],
  GLdouble * objx,
  GLdouble * objy,
  GLdouble * objz
);

Parameters

winx, winy, winz
The window coordinates to be mapped.
modelMatrix
The modelview matrix (as from a glGetDoublev call).
projMatrix
The projection matrix (as from a glGetDoublev call).
viewport
The viewport (as from a glGetIntegerv call).
objx, objy, objz
The computed object coordinates.

Return Values

If the function succeeds, the return value is GL_TRUE.

If the function fails, the return value is GL_FALSE.

Remarks

The gluUnProject function maps the specified window coordinates into object coordinates using modelMatrix, projMatrix, and viewport. The result is stored in objx, objy, and objz.

QuickInfo

Windows NT: Use version 3.5 and later.
Windows: Use Windows 95 and later.
Windows CE: Unsupported.
Header: Declared in glu.h.
Import Library: Link with glu32.lib.

See Also

glGet, glGetDoublev, glGetIntegerv, gluProject


Introduction Tutoriels sur la programmation OpenGL moderne (ogldev en français)

Cette série de tutoriels en C++ vous permettra d'apprendre l'OpenGL moderne (OpenGL > 3, pipeline dynamique). Pour exécuter ceux-ci, vous devez donc avoir un GPU compatible avec OpenGL 3.3 ou plus. Ils ont été développés sous Linux avec NetBeans mais vous pouvez les compiler simplement avec 'make'. Sachant que l'auteur utilise GLEW, vous devez l'installer pour pouvoir compiler les tutoriels. Pour les utilisateurs sous Windows, un fichier de projet pour Visual Studio est aussi disponible.

Tutoriels Vu dans .... http://jeux.developpez.com/tutoriels/OpenGL-ogldev/

  • Ouvrir une fenêtre
  • Hello point
  • Le premier triangle
  • Les shaders
  • Les variables uniformes
  • Les transformations : translation
  • Les transformations : rotation
  • Les transformations : mise à l'échelle
  • Interpolation
  • Dessins indexés
  • Concaténation des transformations
  • Projection en perspective
  • Espace caméra
  • Contrôle de la caméra (au clavier)
  • Contrôle de la caméra (à la souris)
  • Application basique de texture
  • Éclairage ambiant
  • Lumière diffuse
  • Lumière spéculaire
  • Lumières ponctuelles
  • Projecteurs
  • Charger des modèles avec Assimp
  • Shadow-mapping (première partie)

Source

Ces tutoriels sont accompagnés de code d'exemple que vous pouvez télécharger ici.


Tutoriel 1 - Ouvrir une fenêtre

  • I. Contexte
  • III. Explication du code
  • IV. Sources
  • V. Remerciements
  • Navigation

Cet article a l'objectif très simple de vous faire ouvrir une fenêtre permettant le rendu OpenGL et servant de support pour les tutoriels suivants.

II. Contexte

La spécification d'OpenGL ne définit pas de fonctions particulières pour manipuler des fenêtres. Les systèmes modernes de fenêtrage supportant OpenGL incluent généralement un sous-système fournissant la liaison entre un contexte OpenGL et le système de fenêtrage. Dans le système X Window, cette interface est appelée GLX. Microsoft fournit WGL (prononcé Wigueul) pour Windows et MacOS a CGL. Travailler directement avec ces interfaces afin de créer une fenêtre pour gérer l'affichage est généralement un travail fastidieux, c'est pourquoi nous allons utiliser une bibliothèque de haut niveau qui fera abstraction de ces détails.
La bibliothèque que nous allons utiliser ici est appelée « OpenGL utility library », ou GLUT. Elle fournit des fonctions simplifiées pour la gestion de fenêtres ainsi qu'une gestion évènementielle, une gestion des E/S et quelques autres services. De plus, GLUT est multiplateforme, ce qui facilite le portage.
Les alternatives à GLUT sont, entre autres, SDL ou GLFW.

III. Explication du code

Sélectionnez
glutInit(&argc, argv);

Cet appel initialise GLUT.
Les paramètres peuvent être fournis directement depuis la ligne de commande et inclure des options utiles du type « -sync » ou « -gldebug » qui désactive la nature asynchrone de X et vérifie automatiquement les erreurs GL et les affiche (respectivement).

Sélectionnez
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);

Ici nous configurons quelques options de GLUT.
GLUT_DOUBLE active le « double buffering » (dessiner dans un tampon en arrière-plan pendant qu'un autre tampon est affiché).
GLUT_RGBA définit le format des pixels du tampon de couleurs où sont appliqués les rendus (par exemple : l'écran).
On active généralement ces deux options ainsi que d'autres comme nous le verrons plus tard.

Sélectionnez
glutInitWindowSize(1024, 768);
glutInitWindowPosition(100, 100);
glutCreateWindow("Tutorial 01");

Ces appels définissent les paramètres de la fenêtre et la créent. Vous avez aussi la possibilité de spécifier le titre de la fenêtre.

Sélectionnez
glutDisplayFunc(RenderSceneCB);

Comme nous travaillons dans un système fenêtré, les interactions avec le programme vont principalement se faire au travers de callbacks sur des évènements. GLUT prend en charge les interactions avec le système de fenêtres sous-jacent et nous fournit quelques callbacks.
Ici nous en utilisons juste un : le callback « principal » pour effectuer le rendu d'une image. Cette fonction est appelée continuellement par la boucle interne de GLUT.

Sélectionnez
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

C'est notre première rencontre avec le concept d'état en OpenGL.
L'idée d'un état est que le rendu est une tâche tellement complexe qu'elle ne peut être traitée dans un seul appel de fonction avec quelques paramètres (et une fonction correctement conçue ne devrait jamais recevoir beaucoup de paramètres).
Il faut spécifier les shaders, tampons et divers indicateurs qui affectent la manière dont le rendu s'effectue. De plus vous voudrez souvent garder la même configuration au travers de plusieurs opérations de rendu (exemple : si vous ne désactivez jamais le test de profondeur, il n'y a pas lieu de le spécifier à chaque rendu). C'est pourquoi la plus grande partie de la configuration des opérations de rendu s'effectue en définissant des indicateurs et des valeurs dans la machine à états d'OpenGL. Les rendus eux-mêmes sont habituellement limités aux quelques paramètres relatant du nombre de sommets à dessiner et leur décalage de départ.
Après l'appel d'une fonction de changement d'état, cette nouvelle configuration reste inchangée jusqu'au prochain appel de cette même fonction avec une valeur différente. L'appel ci-dessus définit la couleur qui sera utilisée lors du vidage du tampon d'image (décrit plus tard). La couleur a quatre composantes (RGBA) et chaque composante est normalisée entre 0.0 et 1.0.

Sélectionnez
glutMainLoop();

Cet appel donne le contrôle à GLUT qui lance sa boucle interne. Dans cette boucle GLUT écoute les évènements du système de fenêtres et les passe aux callbacks que nous avons définies. Dans notre cas, GLUT va seulement appeler la fonction enregistrée comme callback d'affichage (RenderSceneCB) pour nous donner l'occasion de dessiner une image.

Sélectionnez
glClear(GL_COLOR_BUFFER_BIT);
glutSwapBuffers();

La seule chose que nous faisons dans notre fonction de rendu est de vider le tampon d'image (en utilisant la couleur spécifiée au-dessus - essayez de la changer). Le second appel dit à GLUT d'échanger les rôles du « backbuffer » et du « frontbuffer ». Dans le prochain appel du callback de rendu, nous rendrons dans l'actuel « frontbuffer » et l'actuel « backbuffer » sera affiché.

Image non disponible
Résultat

IV. Sources

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérez les sources


Tutoriel 2 : Hello Point

  • Navigation
  • II. Contexte
    • II-A. Installation de GLEW
  • III. Explication du code
  • IV. Sources
  • V. Remerciements
  • Navigation

Ce tutoriel vous présente GLEW pour la gestion des extensions OpenGL afin d'afficher votre premier point dans votre fenêtre.

II. Contexte

C'est notre première rencontre avec la bibliothèque GLEW, « OpenGL Extension Wrangler Library ». GLEW vous aide à contourner le mal de crâne qui vous attend si vous voulez gérer les extensions dans OpenGL. Une fois initialisé, il récupère les extensions disponibles sur votre plateforme, les charge et fournit un accès facile via un simple fichier d'en-tête.

Dans ce tutoriel, nous allons voir l'utilisation des « Vertex Buffer Objects » (VBO) pour la première fois. Comme son nom l'indique, ils sont utilisés pour stocker des sommets.

Les objets du monde 3D que vous essayez de virtualiser, que ce soit des monstres, des châteaux ou un simple cube tournant, sont toujours construits en connectant des groupes de sommets ensemble.

Les VBO sont la manière la plus efficace de charger des sommets sur le GPU. Ce sont des tampons qui sont stockés sur la mémoire vidéo et ils fournissent le plus faible temps d'accès au GPU, ils sont donc vivement recommandés.

Ce tutoriel et le suivant sont les seuls de cette série qui s'appuient sur le pipeline fixe plutôt que sur le pipeline programmable.

En réalité, aucune transformation ne va avoir lieu dans ces deux tutoriels. Nous nous appuyons simplement sur la manière dont le flux de données transite dans le pipeline.

Une étude plus approfondie du pipeline suivra dans les prochains tutoriels, mais pour l'instant, il suffit de comprendre qu'avant d'atteindre le rasterizer (qui dessine effectivement les points, lignes et triangles en utilisant les coordonnées écran), les sommets visibles ont leurs coordonnées X, Y et Z entre les bornes [-1.0, 1.0]. Le rasterizer fait correspondre les coordonnées à l'espace écran (exemple : si la largeur de l'écran est 1024 alors la coordonnée 0.0 correspond à 0 et 1.0 correspond à 1023).

Finalement, le rasterizer dessine les primitives selon la topologie spécifiée dans la fonction de dessin (voir l'explication de code ci-dessous). Comme nous n'attachons pas de shader au pipeline, nos sommets ne subissent aucune transformation.

Ça signifie que nous avons juste à leur donner une valeur entre les bornes précédentes afin de les rendre visibles. En fait, définir zéro pour X et Y place le sommet au milieu des deux axes et par là même au milieu de l'écran.

II-A. Installation de GLEW

GLEW est disponible sur le site http://glew.sourceforge.net/.

La plupart des distributions Linux fournissent un paquet pour lui.

Sur Ubuntu vous pouvez l'installer en utilisant la ligne de commande suivante :

apt-get install libglew1.6 libglew1.6-dev

III. Explication du code

Sélectionnez
#include <GL/glew.h>

Ici nous incluons l'unique fichier d'en-tête de GLEW.

Si vous incluez d'autres fichiers d'en-têtes OpenGL, vous devez faire attention à inclure ce fichier d'en-tête avant les autres sinon GLEW va s'en plaindre. Afin de lier le programme avec GLEW, vous devez ajouter « -lGLEW » au makefile.

Sélectionnez
#include "math_3d.h"

Dans ce tutoriel nous commençons à utiliser des structures d'aides telles que les vecteurs. Nous étofferons ce fichier d'en-tête au fur et à mesure de nos avancées.

Sélectionnez
GLenum res = glewInit();
if (res != GLEW_OK)
{
    fprintf(stderr, "Error: '%s'\n", glewGetErrorString(res));
    return 1;
}

Ici nous initialisons GLEW et vérifions les erreurs éventuelles. Cela doit être fait après l'initialisation de GLUT.

Sélectionnez
Vector3f Vertices[1];
Vertices[0] = Vector3f(0.0f, 0.0f, 0.0f);

Nous créons un tableau contenant une structure Vector3f (type défini dans math_3d.h) et initialisons XYZ à zéro. Ainsi le point apparaîtra au milieu de l'écran.

Sélectionnez
GLuint VBO;

Nous allouons un GLuint en variable globale au programme pour stocker l'identifiant du VBO.

Vous verrez par la suite que la majorité (si pas tous) des objets OpenGL sont représentés par une variable de type GLuint.

Sélectionnez
glGenBuffers(1, &VBO);

OpenGL définit plusieurs fonctions glGen* pour créer des objets de différents types.

Elles prennent souvent deux paramètres :

  • le premier spécifie le nombre d'objets à créer ;
  • le second est l'adresse d'un tableau de GLuint pour stocker les identifiants alloués par le pilote (faites attention à avoir un tableau assez grand pour récupérer le résultat).

Les futurs appels à cette fonction ne généreront pas les mêmes identifiants à moins qu'ils aient d'abord été détruits avec glDeleteBuffers.

Notez qu'à ce stade vous ne spécifiez pas ce que vous avez l'intention de faire avec les tampons et donc ils peuvent être considérés comme « génériques ». Leur spécification est la tâche de la fonction suivante.

Sélectionnez
glBindBuffer(GL_ARRAY_BUFFER, VBO);

OpenGL a une manière plutôt unique d'utiliser les identifiants.

Dans de nombreuses bibliothèques, l'identifiant est simplement passé à une fonction correspondante et l'action est effectuée sur cet identifiant. En OpenGL, nous attachons l'identifiant à un nom de cible et effectuons ensuite les commandes sur cette cible. Ces commandes affectent l'identifiant attaché jusqu'à ce qu'un autre soit attaché à sa place ou que la fonction ci-dessus soit appelée avec 0 comme identifiant.

La cible GL_ARRAY_BUFFER signifie que le tampon va contenir un tableau de sommets.

Une autre cible utile est GL_ELEMENT_ARRAY_BUFFER qui signifie que le tampon contient les indices des sommets d'un autre tampon.

D'autres cibles sont aussi disponibles et nous verrons leur utilité dans de futurs tutoriels.

Sélectionnez
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);

Après avoir attaché notre objet, nous le remplissons de données.

L'appel ci-dessus prend le nom de la cible (le même que celui utilisé pour l'attachement), la taille des données en octets, l'adresse du tableau de sommets et un indicateur indiquant le modèle d'utilisation de ces données.

Comme nous n'allons pas changer le contenu du tampon, nous spécifions GL_STATIC_DRAW. L'opposé serait GL_DYNAMIC_DRAW.

Bien que ce ne soit qu'un conseil pour OpenGL, il est bon de réfléchir à l'indicateur correct à utiliser. Le pilote peut s'appuyer dessus pour des stratégies d'optimisation (comme la meilleure place en mémoire pour stocker le tampon).

Sélectionnez
glEnableVertexAttribArray(0);

Dans le tutoriel sur les shaders, vous verrez que les attributs de sommet utilisés par le shader (position, normale…) ont un indice associé, qui vous permet de créer un lien entre les données du programme C/C++ et le nom de l'attribut au sein du shader.

De plus vous pouvez aussi activer chaque indice d'attribut de sommet.

Dans ce tutoriel, nous n'utilisons pas encore de shader, mais la position du sommet que nous avons chargé dans le tampon est traitée comme l'indice 0 d'attribut de sommet dans le pipeline fixe (qui devient actif s'il n'y a pas de shader lié).

Vous devez activer chaque attribut de sommet, sinon les données associées ne seront pas accessibles dans le pipeline.

Sélectionnez
glBindBuffer(GL_ARRAY_BUFFER, VBO);

Ici, nous attachons encore notre tampon comme nous nous préparons à appeler la fonction de dessin.

Dans ce petit programme, nous n'avons qu'un seul tampon de sommets donc faire cet appel à chaque image est redondant, mais dans les programmes plus complexes, où il y a de multiples tampons pour stocker vos divers modèles, vous devez mettre à jour l'état du pipeline avec le tampon que vous avez l'intention d'utiliser.

Sélectionnez
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);

Cet appel dit au pipeline comment interpréter les données dans le tampon.

Le premier paramètre spécifie l'indice de l'attribut. Dans notre cas, nous savons que c'est zéro par défaut, mais quand nous commencerons à utiliser les shaders, nous aurons besoin soit de le spécifier explicitement, soit de le demander.

Le second paramètre est le nombre de composantes dans l'attribut (trois pour X, Y et Z).

Le troisième paramètre est le type de données de toutes les composantes.

Le paramètre suivant indique si nous voulons que notre attribut soit normalisé avant son utilisation dans le pipeline. Dans notre cas, nous voulons que nos données passent sans être modifiées.

Le cinquième paramètre (appelé « stride ») est le nombre d'octets entre deux instances de l'attribut dans le tampon. Quand il n'y a qu'un seul attribut (exemple : le tampon contient uniquement des positions) et que les données sont contiguës, nous passons zéro. Si nous avons un tableau de structures qui contiennent une position et une normale (chacune étant un vecteur de trois flottants), nous passerons la taille de la structure en octets (6 * 4 = 24).

Le dernier paramètre est utile dans le cas de l'exemple précédent. Nous devons spécifier le décalage à l'intérieur de la structure où le pipeline va trouver notre attribut. Dans le cas de la structure avec la position et la normale, le décalage de la position est zéro alors que le décalage de la normale est 12.

Sélectionnez
glDrawArrays(GL_POINTS, 0, 1);

Enfin, nous appelons la fonction de dessin de la géométrie.

Toutes les commandes que nous avons vues jusqu'ici sont importantes, mais elles ne font que préparer le terrain pour l'appel de cette fonction. C'est là que le GPU commence réellement son travail. Il va combiner les paramètres de la fonction de dessin avec l'état qui a été construit jusqu'à maintenant et rendre le résultat sur l'écran.

OpenGL fournit plusieurs types de fonctions de dessin et chacune est propre à un cas particulier. En général il est possible de les diviser en deux catégories :

  • les dessins ordonnés ;
  • les dessins indicés.

Les dessins ordonnés sont les plus simples. Le GPU parcourt le tampon de sommets, sommet par sommet et les interprète selon la topologie spécifiée dans la fonction de dessin. Par exemple, si vous spécifiez GL_TRIANGLES alors les sommets 0 à 2 deviennent le premier triangle, 3 à 5 le second…

Si vous voulez que le même sommet apparaisse dans plus d'un triangle, vous devez le spécifier autant de fois dans le tampon de sommets, ce qui est un gâchis de place.

Les dessins indicés sont plus complexes et impliquent un tampon supplémentaire : le tampon d'indices. Le tampon d'indices contient les indices des sommets dans le tampon de sommets. Le GPU scanne le tampon d'indices et de la même manière que dans la description précédente les indices 0 à 2 forment le premier triangle…

Si vous voulez que le même sommet apparaisse dans deux triangles, vous avez juste à spécifier son indice deux fois dans le tampon d'indices. Le tampon de sommets a besoin de n'en contenir qu'une seule copie.

Les dessins indicés sont plus communs dans les jeux, car la majorité des modèles sont créés à partir de triangles qui représentent une surface (peau d'une personne, mur de château…) avec beaucoup de partages de sommets entre eux.

Dans ce tutoriel, nous utilisons la fonction de dessin la plus simple : glDrawArrays. C'est un dessin ordonné où il n'y a pas de tampon d'indices.

Nous spécifions la topologie à « points » ce qui signifie que chaque sommet est un point.

Le paramètre suivant est l'indice du premier sommet à dessiner. Dans notre cas, nous voulons commencer au début du tampon donc nous spécifions zéro, mais ça nous permet de stocker plusieurs modèles dans le même tampon et choisir celui à afficher selon le décalage dans le tampon.

Le dernier paramètre est le nombre de sommets à dessiner.

Sélectionnez
glDisableVertexAttribArray(0);

C'est une bonne pratique de désactiver chaque attribut de sommet quand il n'est pas immédiatement utilisé.

Le laisser activé quand un shader ne l'utilise pas est un moyen sûr de rencontrer des problèmes.

Image non disponible
Résultat

IV. Sources

Vous pouvez télécharger les sources de ce projet en suivant ce lien : http://jeux.developpez.com/tutoriels/OpenGL-ogldev/fichiers/ogldev-source.zip


Tutoriel 3 : Premier triangle

  • II. Contexte
  • III. Explication du code
  • IV. Sources
  • V. Remerciements
  • Navigation

Dans ce tutoriel vous allez apprendre à dessiner votre premier triangle avec OpenGL.

II. Contexte

Ce tutoriel est très court. Nous élargissons juste le tutoriel précédent afin d'afficher un triangle.

Dans ce tutoriel nous allons, une nouvelle fois, nous appuyer sur la boîte normalisée. Les sommets visibles doivent être dans la boîte pour que la transformation de la vue les fasse correspondre à des coordonnées visibles dans la fenêtre. En regardant le long de l'axe Z négatif, cette boîte ressemble à ça :

Image non disponible

Le point (-1.0, 1.0) correspond au coin bas gauche de la fenêtre, (-1.0, 1.0) correspond au coin haut gauche et ainsi de suite. Si vous étirez la position d'un des sommets hors de cette boîte, le triangle sera coupé et vous n'en verrez qu'une partie.

III. Explication du code

Sélectionnez
Vector3f Vertices[3];
Vertices[0] = Vector3f(-1.0f, -1.0f, 0.0f);
Vertices[1] = Vector3f(1.0f, -1.0f, 0.0f);
Vertices[2] = Vector3f(0.0f, 1.0f, 0.0f);

Nous avons agrandi le tableau afin qu'il contienne trois sommets.

glDrawArrays(GL_TRIANGLES, 0, 3);

Deux changements ont été apportés à la fonction de dessin : nous dessinons des triangles au lieu de points et nous dessinons trois sommets au lieu d'un seul.

IV. Sources

Vous pouvez télécharger les sources de ce projet en suivant ce lien : http://jeux.developpez.com/tutoriels/OpenGL-ogldev/fichiers/ogldev-source.zip


Tutoriel 4 - Shaders

  • avigation
  • II. Contexte
  • III. Explication du code
  • IV. Sources
  • V. Remerciements
  • Navigation

Dans ce tutoriel, vous allez apprendre à charger un shader afin qu'il soit exécuter pendant le rendu de votre triangle.

II. Contexte

À partir de ce tutoriel, chaque effet et technique que nous implémenterons le sera en utilisant les shaders.
Les shaders sont la méthode moderne pour faire des graphiques 3D.
D'une certaine manière vous pourriez prétendre que c'est un retour en arrière, en effet la plupart des fonctionnalités 3D qui étaient fournies par le pipeline fixe et ne nécessitaient du développeur que la définition de paramètres de configuration (attributs de lumières, rotations…), doivent maintenant être implémentées par le développeur (via les shaders).
Cependant cette programmation permet une plus grande flexibilité et innovation.

Le pipeline programmable d'OpenGL peut être visualisé comme suit :

Image non disponible

Le processeur de sommets exécute le vertex shader sur chaque sommet qui passe à travers le pipeline (et dont le nombre est déterminé par les paramètres donnés à la fonction de dessin).
Les vertex shaders n'ont aucune connaissance de la topologie des primitives rendues. En outre, vous ne pouvez pas vous débarrasser d'un quelconque sommet dans le processeur de sommets. Chaque sommet entre dans le processeur de sommets exactement une fois, subit les transformations et continue dans le pipeline.

L'étape suivante est le « processeur de géométries ».
Dans cette étape, la connaissance de la primitive complète (c'est-à-dire de tous ses sommets) ainsi que des sommets voisins est fournie au shader. Cela permet des techniques qui doivent prendre en compte les informations additionnelles en plus du sommet lui-même.
Le geometry shader a aussi la capacité de changer la topologie de sortie vers une autre que celle choisie lors de l'appel de la fonction de dessin.
Par exemple, vous pourriez lui fournir une liste de points et générer deux triangles (c'est-à-dire un rectangle) à partir de chaque point (technique connue sous le nom de « billboarding »).
De plus, vous avez la possibilité d'émettre de multiples sommets pour chaque appel du geometry shader et ainsi créer de multiples primitives selon la topologie de sortie choisie.

L'étape suivante dans le pipe est le « clipper ».
C'est une unité fixe avec une tâche précise : il découpe les primitives selon la boîte normalisée décrite dans le tutoriel précédent. Il les découpe aussi aux plans Z proche et lointain.
Il est aussi possible de fournir des plans de clipping et faire que le clipper coupe selon ces plans.
Les positions des sommets qui ont survécu au clipper sont maintenant mappées en coordonnées écran et le rasterizer les affiche sur l'écran selon leur topologie.
Par exemple, dans le cas de triangles, ça signifie trouver tous les points qui sont dans le triangle. Pour chaque point, le rasterizer appelle le processeur de fragment. Ici vous avez la possibilité de déterminer la couleur du pixel en la récupérant depuis une texture ou n'importe quelle technique voulue.

Les trois étapes programmables (processeur de sommets, géométrie et fragment) sont optionnelles. Si vous ne leur attachez pas de shader, la fonctionnalité par défaut sera exécutée.

La gestion de shaders est très similaire à la création d'un programme C/C++.
Tout d'abord vous écrivez le texte du shader et le rendez disponible dans votre application. Ça peut être fait simplement en écrivant le texte dans une chaîne de caractères dans le code source même, ou en le chargeant depuis un fichier texte externe (là aussi dans une chaîne de caractères).
Ensuite vous compilez les shaders un par un dans des objets de shader.
Puis vous liez les shaders dans un unique programme et le chargez sur le GPU. Lier les shaders donne au pilote l'opportunité de les optimiser en fonction des relations qu'ils ont entre eux.
Par exemple, vous pourriez coupler un vertex shader qui émet une normale avec un fragment shader qui l'ignore. Dans ce cas le compilateur GLSL du driver peut supprimer les fonctionnalités liées à la normale et permettre une exécution plus rapide du vertex shader. Si plus tard ce shader est couplé avec un fragment shader qui utilise la normale, alors lier l'autre programme générera un vertex shader différent.

III. Explication du code

Sélectionnez
GLuint ShaderProgram = glCreateProgram();

Nous démarrons le procédé de mise en place de nos shader par la création d'un programme. Nous lierons tous les shaders ensembles à ce programme.

Sélectionnez
GLuint ShaderObj = glCreateShader(ShaderType);

Nous créons deux shaders en utilisant l'appel ci-dessus. L'un d'eux aura pour type de shader GL_VERTEX_SHADER et l'autre aura pour type GL_FRAGMENT_SHADER.

Le procédé pour spécifier la source et compiler le shader est le même pour les deux.

Sélectionnez
const GLchar* p[1];
p[0] = pShaderText;
GLint Lengths[1];
Lengths[0]= strlen(pShaderText);
glShaderSource(ShaderObj, 1, p, Lengths);

Avant la compilation du shader, nous devons spécifier son code source.

La fonction glShaderSource prend le shader comme paramètre et vous laisse une certaine liberté quant à la manière de spécifier la source.

La source peut être définie dans plusieurs chaînes de caractères et vous devez alors fournir un tableau de pointeurs sur ces chaînes ainsi qu'un tableau d'entiers où chaque valeur est la longueur de la chaîne correspondante. Par simplicité, nous utilisons une simple chaîne de caractères pour la source complète et nous utilisons deux tableaux de un élément chacun, pour la source et pour sa longueur.

Le second paramètre de l'appel est le nombre d'emplacements des deux tableaux (juste un dans notre cas).

Sélectionnez
glCompileShader(ShaderObj);

La compilation du shader est très facile…

Sélectionnez
GLint success;
glGetShaderiv(ShaderObj, GL_COMPILE_STATUS, &success);
if (!success)
{
    GLchar InfoLog[1024];
    glGetShaderInfoLog(ShaderObj, sizeof(InfoLog), NULL, InfoLog);
    fprintf(stderr, "Error compiling shader type %d: '%s'\n", ShaderType, InfoLog);
}

… cependant, vous obtiendrez généralement quelques erreurs de compilation. Le bout de code ci-dessus récupère le statut de compilation et affiche toutes les erreurs que le compilateur a rencontrées.

Sélectionnez
glAttachShader(ShaderProgram, ShaderObj);

Finalement nous attachons notre shader au programme. C'est très similaire à la spécification de la liste des objets pour lier dans un makefile. Comme nous n'avons pas de makefile ici, nous émulons ce comportement par programmation. Seuls les shaders attachés prennent part au procédé d'édition de lien.

Sélectionnez
glLinkProgram(ShaderProgram);

Après avoir compilé tous nos shaders et les avoir attachés au programme, nous pouvons finalement le lier.

Notez qu'après la liaison du programme, vous pouvez vous débarrasser des shaders intermédiaires en appelant glDeleteShader pour chacun d'entre eux.

Sélectionnez
glGetProgramiv(ShaderProgram, GL_LINK_STATUS, &Success);
if (Success == 0)
{
    glGetProgramInfoLog(ShaderProgram, sizeof(ErrorLog), NULL, ErrorLog);
    fprintf(stderr, "Error linking shader program: '%s'\n", ErrorLog);
}

Notez que nous vérifions les erreurs en relation avec le programme (comme les erreurs de liaison) d'une manière un peu différente de celles liées aux shaders. Au lieu d'utiliser glGetShaderiv nous utilisons glGetProgramiv et au lieu de glGetShaderInfoLog nous utilisons glGetProgramInfoLog.

Sélectionnez
glValidateProgram(ShaderProgram);

Vous pouvez vous demander pourquoi nous avons besoin de valider le programme après qu'il ait été correctement lié.

La différence est que le l'édition des liens vérifie les erreurs basées sur la combinaison des shaders alors que l'appel ci-dessus vérifie si le programme peut s'exécuter dans l'état actuel du pipeline.

Dans une application complexe avec de multiples shaders et de nombreux changements d'état il est conseillé de valider avant chaque appel de la fonction de dessin. Pour notre application basique, nous ne le vérifions qu'une seule fois. Aussi, vous pouvez vouloir faire cette vérification uniquement pendant le développement et éviter la surcharge dans le produit final.

Sélectionnez
glUseProgram(ShaderProgram);

Finalement, pour utiliser le programme lié, vous l'affectez au pipeline en utilisant l'appel ci-dessus.

Ce programme restera actif pour tous les appels à la fonction de dessin jusqu'à ce que vous le remplaciez par un autre ou que vous le désactiviez explicitement (et activiez le pipeline fixe) en appelant glUseProgram avec NULL.

Si vous avez créé un programme qui contient un seul type de shader alors les autres étapes fonctionnent en utilisant leur fonctionnalité fixe par défaut.

Nous avons terminé l'explication des appels OpenGL relatifs à la gestion de shader.

Le reste de ce tutoriel est relatif au contenu des vertex et fragment shaders (contenus dans les variables pVS et pFS).

Sélectionnez
#version 330

Cette ligne dit au compilateur que nous ciblons la version 3.3 de GLSL. Si le compilateur ne la supporte pas il va émettre une erreur.

Sélectionnez
layout (location = 0) in vec3 Position;

Cette déclaration apparaît dans le vertex shader.

Elle déclare qu'un attribut spécifique au sommet, un vecteur de trois flottants, sera connu sous le nom « Position » dans le shader. « Spécifique au sommet » signifie que pour tous les appels du shader dans le GPU la valeur d'un nouveau sommet venant du tampon sera fournie.

La première section de la déclaration, « layout (location=0) », crée un lien entre le nom de l'attribut et l'attribut dans le tampon. C'est requis pour les cas où notre sommet contient plusieurs attributs (position, normale, coordonnées de texture…). Nous informons le compilateur quel attribut du sommet dans le tampon doit être affecté à l'attribut déclaré dans le shader.

Il y a deux méthodes pour faire cela.

  • Nous pouvons le déclarer explicitement comme nous le faisons ici (à zéro). Dans ce cas nous pouvons utiliser la valeur en dur dans notre application (ce que nous avons fait avec le premier paramètre de l'appel à glVertexAttribPointer).
  • Nous pouvons le laisser indéfini (et simplement déclarer « in vec3 Position » dans le shader) et demander sa position à l'exécution de l'application en utilisant glGetAttribLocation. Dans ce cas nous devrons fournir la valeur retournée à glVertexAttribPointer au lieu de la valeur en dur.

Nous avons choisi la méthode simple mais dans des applications plus complexes il vaut mieux laisser le compilateur déterminer les indices des attributs et les demander à l'exécution. Cela rend plus facile l'intégration de shaders à partir de sources multiples sans avoir à les adapter à la disposition de votre tampon.

Sélectionnez
void main()

Vous pouvez créer votre shader en liant ensemble de multiples shaders. Cependant il n'y a qu'une fonction « main » pour chaque étape de shader (VS, GS ou FS) qui est utilisée comme point d'entrée du shader.

Par exemple vous pouvez créer une bibliothèque de gestion de l'éclairage avec plusieurs fonctions et la lier avec votre shader, si tant est qu'aucune de ses fonctions n'est nommée « main ».

Sélectionnez
gl_Position = vec4(0.5 * Position.x, 0.5 * Position.y, Position.z, 1.0);

Ici nous effectuons des transformations codées en dur sur la position des sommets qui arrivent. Nous divisons par deux les valeurs X et Y et laissons Z inchangé.

gl_Position est une variable prédéfinie spéciale qui est définie pour contenir des coordonnées homogènes (composantes X, Y, Z et W) pour la position du sommet. Le rasterizer va regarder dans cette variable et l'utiliser comme position dans l'espace écran (après quelques transformations supplémentaires). Diviser X et Y par deux signifie que nous allons voir un triangle dont la taille est le quart de celle du triangle du précédent tutoriel.

Notez que nous mettons aussi W à 1.0. C'est extrêmement important afin que le triangle soit affiché correctement.

Effectuer la projection de la 3D vers la 2D est en réalité une action accomplie en deux étapes séparées.

  • Tout d'abord il faut multiplier tous les sommets par la matrice de projection (que nous développerons d'ici quelques tutoriels).
  • Ensuite le GPU effectue ce qui est connu comme le « perspective divide » à l'attribut position avant qu'elle n'atteigne le rasterizer. Cela signifie qu'il divise toutes les composantes de gl_Position par sa composante W.

Dans ce tutoriel, nous ne faisons pas encore de projection dans le vertex shader mais l'étape de « perspective divide » est quelque chose que nous ne pouvons pas désactiver. Quelle que soit la valeur de gl_Position que nous sortons du vertex shader, ses composantes XYZ vont être divisées par le GPU en utilisant sa composante W.

Nous devons nous souvenir que sans celle-ci nous n'aurons pas les résultats que nous attendons. Afin de contourner l'effet de cette division, nous définissons W à 1.0. Une division par 1.0 n'affectera pas les autres composantes de la position du vecteur qui restera alors dans notre boîte normalisée.

Si tout s'est passé correctement, trois sommets avec les valeurs (-0.5, -0.5), (0.5, -0.5) et (0.0, 0.5) atteignent le rasterizer.

Le clipper n'a pas besoin de faire quoi que ce soit, car tous les sommets sont bien dans la boîte normalisée. Ces valeurs sont mappées en coordonnées dans l'espace écran et le rasterizer commence à parcourir tous les points dans le triangle. Pour chaque point le fragment shader est exécuté.

Le code de shader qui suit est pris du fragment shader.

Sélectionnez
out vec4 FragColor;

Habituellement le travail du fragment shader est de déterminer la couleur du fragment (pixel).

En plus, le fragment shader peut complètement se débarrasser du pixel ou changer sa valeur en Z (ce qui affectera le résultat du test de profondeur ultérieur).

La production de la couleur se fait en déclarant la variable ci-dessus. Les quatre composantes représentent R, G, B et A (pour alpha).

La valeur que vous placez dans cette variable sera reçue par le rasterizer et enfin écrite dans le tampon d'image.

Sélectionnez
FragColor = vec4(1.0, 0.0, 0.0, 1.0);

Dans le précédent couple de tutoriels, il n'y avait pas de fragment shader donc tout était dessiné avec la couleur par défaut qui est le blanc. Ici nous définissons FragColor à rouge.

Image non disponible
Résultat


Tutoriel 5 - Variables uniformes

  • Navigation
  • II. Contexte
  • III. Explication du code
  • IV. Sources
  • V. Remerciements
  • Navigation

Dans ce tutoriel, vous allez apprendre à envoyer des informations à votre GPU à travers les variables uniformes.

II. Contexte

Dans ce tutoriel, nous allons rencontrer un nouveau type de variables pour les shaders : les variables uniformes. La différence entre les attributs de sommets et les variables uniformes est que les attributs de sommets contiennent des données qui sont spécifiques aux sommets et sont donc rechargées avec une nouvelle valeur depuis le tampon de sommets à chaque appel du shader.

Au contraire, la valeur des variables uniformes reste constante durant toute la fonction de dessin. Cela signifie que vous chargez la valeur avant d'appeler la fonction de dessin et que vous pouvez accéder à la même valeur à chaque appel du vertex shader.

Les variables uniformes sont utiles pour stocker des données telles que les paramètres de lumière (position, direction…), les matrices de transformation, les identificateurs des objets de texture et ainsi de suite.

Dans ce tutoriel, nous allons enfin avoir quelque chose qui bouge sur l'écran. Nous allons faire cela en utilisant une combinaison de variables uniformes dont nous changerons la valeur à chaque image via le callback d'inactivité fourni par GLUT.

En fait, GLUT n'appelle pas notre callback de rendu de manière répétée, à moins qu'il ne le doive. GLUT doit appeler le callback de rendu à la suite d'événements tels que la minimisation ou maximisation de la fenêtre ou son découvrement par une autre fenêtre.

Si nous ne changeons rien dans l'agencement des fenêtres après le lancement de l'application, le callback de rendu n'est appelé qu'une seule fois. Vous allez voir la sortie une seule fois et vous la verrez ensuite encore si vous minimisez puis maximisez la fenêtre.

Il était suffisant d'enregistrer uniquement le callback de rendu dans GLUT dans les tutoriels précédents mais là, nous souhaitons changer de manière répétée la valeur d'une variable. Nous le faisons en enregistrant un callback d'inactivité. La fonction d'inactivité est appelée par GLUT quand aucun événement venant du système de fenêtrage n'est reçu. Vous pouvez avoir une fonction dédiée pour ce callback, avec laquelle vous pouvez faire n'importe quelle opération de comptage telle que la mise à jour du temps, ou simplement enregistrer votre fonction de rendu en tant que callback d'inactivité.

Dans ce tutoriel nous appliquerons ce dernier choix et mettrons à jour la variable dans la fonction de rendu.

III. Explication du code

Sélectionnez
glutIdleFunc(RenderSceneCB);

Ici, nous enregistrons notre fonction de rendu en tant que callback d'inactivité. Notez que si vous choisissez d'utiliser une fonction d'inactivité spécifique, vous devrez appeler glutPostRedisplay à la fin de celle-ci sinon le callback d'inactivité sera appelé encore et encore mais pas la fonction de rendu. glutPostRedisplay marque la fenêtre actuelle comme devant être redessinée et pendant le prochain tour de la boucle principale de GLUT le callback de rendu sera appelé.

Sélectionnez
ScaleLocation = glGetUniformLocation(ShaderProgram, "gScale");
assert(gScaleLocation != 0xFFFFFFFF);

Après avoir lié le programme, nous lui demandons l'emplacement de la variable uniforme.

C'est un autre exemple du cas où l'environnement d'exécution de l'application C/C++ a besoin d'être associé à l'environnement d'exécution du shader. Vous ne pouvez pas accéder directement au contenu du shader et ne pouvez donc pas mettre directement ses variables à jour.

Quand vous compilez le shader, le compilateur GLSL affecte un indice à chacune des variables uniformes. Dans la représentation interne du shader dans le compilateur, l'accès à la variable est possible grâce à cet indice. Cet indice est récupérable par l'application via glGetUniformLocation.

Vous appelez cette fonction avec l'identificateur du programme et le nom de la variable. Cette fonction retourne l'indice ou -1 s'il y a eu une erreur.

Il est très important de vérifier les erreurs (comme nous le faisons ci-dessus avec l'assertion) ou les futures mises à jour de la variable ne seront pas transmises au shader. Il y a principalement deux raisons pour lesquelles cette fonction peut échouer :

  • vous pouvez avoir mal écrit le nom de la variable ;
  • elle a été supprimée suite à une optimisation du compilateur.

Si le compilateur GLSL voit qu'une variable n'est pas utilisée dans le shader, elle est tout simplement supprimée. Dans ce cas glGetUniformLocation échouera.

Sélectionnez
static float Scale = 0.0f;
Scale += 0.001f;
glUniform1f(gScaleLocation, sinf(Scale));

Nous créons une variable flottante statique que nous incrémentons un peu à chaque appel de la fonction de rendu (vous pouvez jouer sur 0.001 si ça va trop lentement ou trop vite sur votre machine).

La valeur qui est réellement passée au shader est le sinus de la variable « Scale », ceci afin de créer une jolie oscillation entre -1.0 et 1.0.

Notez que sinf() prend des radians et non des degrés en paramètre mais à ce stade ça n'a tout simplement pas d'importance. Nous voulons juste la sinusoïde générée par la fonction.

Le résultat de sinf() est passé au shader en utilisant glUniform1f.

OpenGL fournit de multiples instances de cette fonction avec la forme générale glUniform{1234}{if}. Vous pouvez les utiliser pour charger des valeurs dans des vecteurs 1D, 2D, 3D ou 4D (selon le nombre qui suit glUniform) de flottants ou d'entiers (suffixe « f » ou « i »).

Il y a aussi des versions qui prennent l'adresse d'un vecteur en paramètre, ainsi que des versions spéciales pour les matrices.

Le premier paramètre de cette fonction est l'indice de l'emplacement que nous avons récupéré en utilisant glGetUniformLocation.

Nous allons maintenant regarder les changements qui ont été faits dans le vertex shader (le pixel shader reste inchangé).

Sélectionnez
Uniform float gScale;

Ici nous déclarons la variable uniforme dans le shader.

Sélectionnez
gl_Position = vec4(gScale * Position.x, gScale * Position.y, Position.z, 1.0);

Nous multiplions les valeurs X et Y de la position du vecteur avec la valeur qui est changée par l'application à chaque image. Pouvez-vous expliquer pourquoi le triangle est retourné au milieu

de la boucle ?

Image non disponible
Résultat

IV. Sources

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources


Tutoriel 06 - Transformations

  • Navigation
  • II. Contexte
  • III. Explication du code
  • IV. Sources
  • V. Remerciements
  • Navigation

Dans ce tutoriel, nous commençons à étudier les diverses transformations que peut subir un objet en 3D, lui permettant d'être affiché sur l'écran tout en maintenant l'impression de profondeur dans la scène.

II. Contexte

Dans ce tutoriel, nous commençons à étudier les diverses transformations que peut subir un objet en 3D, lui permettant d'être affiché sur l'écran tout en maintenant l'impression de profondeur dans la scène.

La méthode commune pour réaliser cela est de représenter chaque transformation en utilisant une matrice, de multiplier les matrices une à une, puis de multiplier la position du sommet par le produit final.

Chacun de ces tutoriels sera dédié à l'étude d'une transformation.

Ici nous étudions la transformation de translation qui est responsable du déplacement d'un objet le long d'un vecteur de taille et de direction quelconques.

Supposons que vous vouliez déplacer le triangle de l'image de gauche à l'emplacement de droite :

Image non disponible

Une façon d'y arriver serait de fournir le vecteur de déplacement (dans notre cas (1, 1)) en tant que variable uniforme au shader et tout simplement de l'ajouter à chaque sommet traité. Cependant cela casse la méthode de multiplication d'un groupe de matrice de manière à obtenir une seule transformation complète.

En outre, nous verrons par la suite que la translation n'est généralement pas la première transformation appliquée, donc il faudra alors multiplier la position par la matrice des transformations antérieures à la translation, puis ajouter la translation et enfin multiplier le résultat par la matrice des transformations postérieures à la translation…

C'est pour le moins alambiqué.

Une meilleure façon de faire est de construire une matrice qui représente la translation et de l'intégrer au processus de multiplication des matrices.

Mais peut-on trouver une matrice qui, multipliée par le point (0, 0) - sommet en bas à gauche du triangle de gauche - donne le point (1, 1) ?

En réalité ce n'est pas possible avec une matrice 2D (ni une matrice 3D, pour le point (0, 0, 0)).

De manière générale, on peut dire qu'il nous faut une matrice M qui selon un point P(x, y, z) et un vecteur V(v1, v2, v3) donnés fournit M * P = P1(x + v1, y + v2, z + v3). Autrement dit, cela signifie que cette matrice M déplace P vers l'emplacement P+V.

Nous voyons que chaque composante de P1 est la somme d'une composante de P et de la composante correspondante de V. La partie gauche de chaque équation de la somme est fournie par la matrice identité :

I * P = P(x, y, z).

Il semble donc que nous devrions commencer par la matrice identité et déterminer les changements qui vont compléter la partie droite de l'équation de la somme de chaque composante (… + V1… + V2… + V3).

Voyons à quoi ressemblerait cette matrice identité améliorée :

Image non disponible

De ce calcul nous pouvons tirer deux conclusions :

  • a, b, c, d, e et f doivent valoir zéro, sinon chaque composante va être influencée par les deux autres (nous sommes donc de retour avec la matrice identité) ;
  • comme x, y et z ont un effet sur les trois composantes du résultat, quand ils valent zéro le résultat vaut le vecteur nul (et nous ne pourrons pas déplacer le vecteur nul)

Nous voulons une matrice qui fournisse la partie droite suivante du calcul :

Image non disponible

Nous devons donc trouver un moyen d'ajouter v1 - v3 comme nous le voyons ci-dessus et a […] f peuvent valoir zéro. Le résultat final sera notre vecteur déplacé.

C'est comme si nous voulions ajouter une quatrième colonne à la matrice mais alors notre calcul serait invalide. Nous ne pouvons pas multiplier une matrice 3 x 4 et un vecteur 3 x 1.

La règle est que l'on ne peut multiplier des matrices que si elles sont de la forme N x M et M x N.

Donc nous devons ajouter une quatrième composante au vecteur. Une bonne quatrième composante sera 1 car nous pouvons mettre v1 - v3 sur la quatrième colonne de la matrice et elles seront inchangées dans le résultat, car multipliées par 1.

Cependant notre matrice est toujours invalide, selon la règle ci-dessus. En effet, multiplier une matrice 3 x 4 par un vecteur 4 x 1 est toujours invalide, ajouter une quatrième ligne à la matrice et la mettre 4 x 4 la rend valide.

Finalement voici notre matrice de translation :

Image non disponible

Maintenant, même si x, y et z valent zéro, nous pouvons toujours les déplacer à n'importe quel emplacement.

La représentation d'un vecteur 3D en vecteur 4D de cette manière, est appelée « coordonnées homogènes » et est très populaire et utile au graphisme 3D. La quatrième composante est appelée « w ».

En fait le symbole interne gl_Position du vertex shader que nous avons vu dans le tutoriel précédent est un vecteur 4D et la composante « w » tient un rôle très important pour transformer la projection 3D en 2D.

La notation commune est d'utiliser w = 1 pour les points et w = 0 pour les vecteurs. La raison est que les points peuvent être déplacés mais pas les vecteurs.

On peut changer la taille d'un vecteur ou sa direction mais tous les vecteurs de même taille et direction sont considérés comme égaux, quelle que soit leur position d'origine. On peut simplement utiliser l'origine du repère pour tous les vecteurs.

Définir w = 0 et multiplier une matrice de translation par le vecteur retournera le même vecteur.

III. Explication du code

Sélectionnez
struct Matrix4f
{
    float m[4][4];
};

Nous avons ajouté la définition d'une matrice 4 x 4 au fichier math_3d.h. Elle sera utilisée pour la plupart des matrices de transformation à partir de maintenant.

Sélectionnez
GLuint gWorldLocation;

Nous utilisons cet identificateur pour accéder à la variable uniforme contenant la matrice monde dans le shader. Nous l'appelons « monde », car ce que nous allons faire à l'objet est le déplacer là où nous le voulons, dans le système de coordonnées de notre monde virtuel.

Sélectionnez
Matrix4f World;
World.m[0][0] = 1.0f;
World.m[0][1] = 0.0f;
World.m[0][2] = 0.0f;
World.m[0][3] = sinf(Scale);
World.m[1][0] = 0.0f;
World.m[1][1] = 1.0f;
World.m[1][2] = 0.0f;
World.m[1][3] = 0.0f;
World.m[2][0] = 0.0f;
World.m[2][1] = 0.0f;
World.m[2][2] = 1.0f;
World.m[2][3] = 0.0f;
World.m[3][0] = 0.0f;
World.m[3][1] = 0.0f;
World.m[3][2] = 0.0f;
World.m[3][3] = 1.0f;

Dans la fonction de rendu nous préparons une matrice 4 x 4 et la remplissons en accord avec l'explication ci-dessus. Nous définissons v2 et v3 à zéro pour ne pas avoir de changement sur les composantes Y et Z de l'objet et définissons v1 au résultat du sinus.

Ainsi la coordonnée X sera déplacée d'une valeur qui oscillera entre -1 et 1. Tout ce qu'il nous reste à faire est de charger cette matrice dans le shader.

Sélectionnez
glUniformMatrix4fv(gWorldLocation, 1, GL_TRUE, &World.m[0][0]);

Voici un autre exemple de fonction glUniform* pour charger des données dans les variables uniformes du shader. Cette fonction-ci charge une matrice 4 x 4 et il existe aussi des versions pour les matrices 2 x 2, 2 x 3, 2 x 4, 3 x 2, 3 x 3, 3 x 4, 4 x 2 et 4 x 3.

Le premier paramètre est l'emplacement de la variable uniforme (récupéré après la compilation du shader, via glGetUniformLocation).

Le second paramètre indique le nombre de matrices que nous mettons à jour. Nous y mettons 1 pour une matrice mais nous pouvons aussi utiliser cette fonction pour mettre à jour toutes les matrices de multiplication en un appel.

Le troisième paramètre déroute parfois les nouveaux venus. Il indique si la matrice fournie est exprimée ligne par ligne ou colonne par colonne.

Le fait est que les matrices C/C++ sont exprimées ligne par ligne par défaut. Cela signifie que lorsque l'on remplit un tableau bidimensionnel avec des valeurs, elles sont rangées en mémoire ligne après ligne avec le « haut » à l'adresse la plus basse.

Par exemple, en regardant le tableau suivant :

Sélectionnez
int a[2][3];
a[0][0] = 1;
a[0][1] = 2;
a[0][2] = 3;
a[1][0] = 4;
a[1][1] = 5;
a[1][2] = 6;

Visuellement, le tableau ressemble à la matrice suivante :

1 2 3

4 5 6

Et l'organisation en mémoire ressemble ça : 1 2 3 4 5 6 (avec 1 à l'adresse la plus basse).

Donc notre troisième paramètre de glUniformMatrix4fv est GL_TRUE, car nous fournissons une matrice ligne par ligne. Nous pourrions aussi le mettre à GL_FALSE, mais nous devrions alors transposer notre matrice (l'organisation en mémoire resterait identique, mais OpenGL interpréterait les quatre premières valeurs comme la première colonne et ainsi de suite, et agirait en conséquence).

Le quatrième paramètre est simplement l'adresse de début de la matrice en mémoire.

Le reste de la source provient du code du shader.

Sélectionnez
uniform mat4 gWorld;

Ceci est la variable uniforme d'une matrice 4 x 4. mat2 et mat3 sont aussi disponibles.

Sélectionnez
gl_Position = gWorld * vec4(Position, 1.0);

La position des sommets du triangle dans le tampon de sommets est un vecteur à trois composantes, mais nous avons convenu de la nécessité d'une quatrième composante ayant pour valeur 1.

Nous avons donc deux options :

  • placer des sommets avec quatre composantes dans le tampon de sommets ;
  • ajouter la quatrième composante dans le vertex shader.

Il n'y aucun avantage à utiliser la première option : chaque position de sommet consommera quatre octets supplémentaires en mémoire pour une composante qui vaudra toujours 1.

Il est plus efficace de rester avec un vecteur à trois composantes et concaténer la composante « w » dans le shader. En GLSL cela est fait en utilisant vec4(Position, 1.0).

Nous multiplions la matrice par ce vecteur et stockons le résultat dans gl_Position.

En résumé, à chaque image nous générons une matrice de translation qui déplace la coordonnée X d'une valeur qui oscille entre -1 et 1. Le shader multiplie la position de chaque sommet par cette matrice ce qui se traduit par le déplacement d'un objet de gauche à droite et de droite à gauche.

Dans la plupart de cas, l'un des côtés du triangle sortira de la boîte normalisée après le vertex shader et le clipper le découpera. Nous ne verrons donc que la partie qui se trouve dans la boîte normalisée.

Image non disponible
Résultat

IV. Sources

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources


Tutoriel 08 - Transformations

Mise à l'échelle

  • II. Contexte
  • III. Explication du code
  • IV. Sources
  • V. Remerciements
  • Navigation

Nous allons ici étudier la dernière transformation de base : la mise à l'échelle (scaling).

II. Contexte

La transformation d'échelle est très simple. Son but est d'augmenter ou réduire la taille de l'objet. Par exemple, vous pouvez vouloir créer une différenciation entre plusieurs objets avec le même modèle (des arbres de taille différente) ou faire correspondre la taille d'un objet avec celle du monde.

Pour les exemples ci-dessus, vous voudrez certainement mettre à l'échelle les positions des sommets pour les trois axes uniformément.

Cependant dans d'autres cas vous voudrez peut-être mettre à l'échelle seulement un ou deux axes, rendant le modèle plus gros ou plus mince.

Le développement de la matrice de transformation est très animé…

Rappelons-nous qu'avec la matrice identité, la raison pour laquelle la multiplication d'un vecteur le laisse inchangé est que chacun des 1 de la diagonale est multiplié par la composante correspondante du vecteur.

Aucune composante ne peut en affecter une autre. Ainsi, remplacer n'importe lequel de ces 1 par une autre valeur impliquera une augmentation sur cet axe, si la valeur est supérieure à 1 ou une diminution sur cet axe sinon.

III. Explication du code

Sélectionnez
World.m[0][0]=sinf(Scale);
World.m[0][1]=0.0f;
World.m[0][2]=0.0f;
World.m[0][3]=0.0f;
World.m[1][0]=0.0f;
World.m[1][1]=sinf(Scale);
World.m[1][2]=0.0f;
World.m[1][3]=0.0f;
World.m[2][0]=0.0f;
World.m[2][1]=0.0f;
World.m[2][2]=sinf(Scale);
World.m[2][3]=0.0f;
World.m[3][0]=0.0f;
World.m[3][1]=0.0f;
World.m[3][2]=0.0f;
World.m[3][3]=1.0f;

Le seul changement depuis le tutoriel précédent est que nous remplaçons la matrice monde en concordance avec l'explication ci-dessus.

Comme vous pouvez le voir, nous effectuons la mise à l'échelle sur les trois axes avec une valeur oscillant entre -1 et 1.

Dans l'ensemble de valeurs [0, 1] la taille du triangle est entre « toute petite » et « taille originale », et quand la diagonale vaut zéro il disparaît entièrement.

Entre les bornes [-1, 0], c'est le résultat inverse, car la valeur d'échelle dans la diagonale a changé le signe de la position.

Image non disponible
Résultat

IV. Sources

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources


Tutoriel 9 - Interpolation

  • II. Contexte
  • III. Explication du code
  • IV. Sources
  • V. Remerciements
  • Navigation

Ce tutoriel montre une partie très importante du pipeline 3D : l'interpolation effectuée par le rasterizer sur les variables qui sortent du vertex shader.

II. Contexte

Comme vous l'avez déjà vu, afin d'obtenir quelque chose de significatif à l'écran, vous devez utiliser une des variables de sortie du vertex shader : gl_Position. Il s'agit d'un vecteur à quatre dimensions qui contient les coordonnées homogènes du sommet.

Les composantes XYZ de ce vecteur sont divisées par la composante W (procédé connu sous le nom (anglais) de « perspective divide » et que nous expliquons dans le tutoriel dédié à ce sujet) et toutes les composantes qui sortent de la boîte normalisée ([-1, 1] pour XY ; [0, 1] pour Z) sont découpées.

Le résultat est transformé dans le système de coordonnées écran et ensuite le triangle (ou toute autre type de primitive supporté) est rendu à l'écran par le rasterizer.

Le rasterizer effectue une interpolation entre les trois sommets du triangle (soit de ligne en ligne ou par une autre technique) et « visite » chaque pixel dans le triangle en exécutant le fragment shader.

Le fragment shader doit renvoyer une couleur de pixel que le rasterizer place dans le tampon de couleur pour l'affichage (après plusieurs étapes additionnelles telles que le test de profondeur, etc.).

N'importe quelle autre variable sortant du vertex shader ne passe pas par les étapes décrites ci-dessus.

Si le fragment shader ne requiert pas explicitement cette variable (et vous pouvez mixer et associer plusieurs fragment shaders avec le même vertex shader), alors une optimisation commune sera de supprimer toutes les instructions, dans le vertex shader, qui n'affectent que cette variable (pour ce programme spécifique qui combine cette paire de vertex shader et fragment shader).

Cependant si le fragment shader utilise cette variable, le rasterizer l'interpole durant la rasterization et fournit à chaque invocation du fragment shader la valeur interpolée qui correspond à cet emplacement spécifique.

Cela signifie généralement que les valeurs pour les pixels qui sont proches les uns des autres seront un peu différentes (même si, le triangle s'éloignant de plus en plus de la caméra, cela devient moins probable).

Deux variables très communes se basent sur l'interpolation : la normale du triangle et les coordonnées de texture.

La normale d'un sommet est habituellement calculée comme la moyenne des normales de tous les triangles qui contiennent ce sommet. Si cet objet n'est pas complètement plat, cela signifie habituellement que les normales des trois sommets de chaque triangle seront différentes les unes des autres

Dans ce cas, nous nous fions à l'interpolation pour calculer la normale de chaque pixel. Cette normale est utilisée dans les calculs d'éclairage afin de générer une représentation plus crédible des effets de lumières.

Le cas des coordonnées de texture est similaire. Ces coordonnées font partie du modèle et sont spécifiées par sommet. Afin de couvrir le triangle avec une texture, vous devez effectuer l'opération d'échantillonnage pour chaque pixel et spécifier les coordonnées de texture correctes pour ce pixel. Ces coordonnées sont le résultat de l'interpolation.

Dans ce tutoriel nous allons voir les effets de l'interpolation en interpolant différentes couleurs au travers de la face triangulaire.

Comme je suis fainéant, je vais générer la couleur dans le vertex shader.

Une approche plus fastidieuse serait de la fournir dans le tampon de sommets. Généralement nous ne fournissons pas de couleurs à partir du tampon de sommets, nous fournissons plutôt des coordonnées de texture et échantillonnons la couleur à partir de la texture.

Cette couleur sera ensuite utilisée par les calculs d'éclairage.

III. Explication du code

Les paramètres passés entre les étapes du pipeline doivent être déclarés en utilisant le mot clé out dans le contexte global du shader.

La couleur est un vecteur à quatre dimensions car les composantes XYZ contiennent les composantes RGB (respectivement) et la composante W correspond à la valeur d'alpha (transparence du pixel).

Sélectionnez
Color = vec4(clamp(Position, 0.0, 1.0), 1.0);

La couleur, dans les pipelines graphiques, est généralement représentée en utilisant des valeurs en nombres flottants dans l'ensemble [0.0, 1.0].

Cette valeur est ensuite mise en correspondance avec un entier entre 0 et 255 pour chaque canal de la couleur (pour un total de 16 M de couleurs).

Nous définissons la couleur du sommet comme fonction de sa position. Tout d'abord nous utilisons la fonction intégrée clamp() afin d'être sûr que les valeurs ne sortent pas des bornes [0.0, 1.0].

La raison est que le sommet en bas à gauche du triangle est positionné en (-1, -1). Si nous prenons cette valeur telle quelle, elle sera interpolée par le rasterizer et, jusqu'à ce que X et Y dépassent zéro, nous ne verrons rien car chaque valeur inférieure ou égale à zéro sera rendue en noir.

Cela signifie que la moitié du bord dans chaque direction sera noire avant que la couleur dépasse zéro et devienne significative. En fixant la valeur, nous faisons en sorte que seule la partie en bas à gauche soit noire, mais comme nous nous en éloignons rapidement, la couleur devient rapidement plus claire.

Essayez de jouer avec la fonction clamp(), en l'enlevant ou en modifiant ses paramètres, pour en voir l'effet.

Le résultat de la fonction clamp() ne va pas directement dans la variable de sortie, car c'est un vecteur à quatre dimensions, alors que la position est un vecteur à trois dimensions (clamp() ne change pas le nombre de composantes, seulement leur valeur.).

Du point de vue de GLSL il n'y a pas de conversion par défaut pour cela et nous devons donc le faire explicitement.

Nous le faisons en utilisant la notation vec4(vec3, W) qui crée un vecteur 4D en concaténant un vecteur 3D avec la valeur W fournie. Dans notre cas nous utilisons 1.0, car elle s'inscrit dans la composante alpha de la couleur et nous voulons que le pixel soit complètement opaque.

Sélectionnez
in vec4 Color;

La partie opposée de la couleur sortie du vertex shader est la couleur en entrée du fragment shader. Cette variable subit l'interpolation par le rasterizer afin que chaque fragment shader soit (probablement) exécuté avec une couleur différente.

Sélectionnez
FragColor = Color;

Nous utilisons la couleur interpolée comme couleur du fragment sans autre changement et cela termine ce tutoriel.

Image non disponible
Résultat

IV. Sources

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources


Tutoriel 10 - Dessins indexés

  • II. Contexte
  • III. Explication du code
  • IV. Sources
  • V. Remerciements
  • Navigation

OpenGL fournit diverses fonctions de dessins. glDrawArrays, que nous avons utilisé jusqu'à présent, est classée dans la catégorie des « dessins ordonnés ». Cela signifie que le tampon de sommets est parcouru à partir de l'indice spécifié et, pour chaque X sommets (1 pour les points, 2 pour les lignes…), une primitive est générée. C'est très simple d'utilisation, cependant le revers de la pièce est que, si un sommet est utilisé dans plusieurs primitives, alors il doit être présent plusieurs fois dans le tampon de sommets. C'est-à-dire qu'il n'y a pas de concept de partage de sommets.

II. Contexte

OpenGL fournit diverses fonctions de dessins. glDrawArrays, que nous avons utilisé jusqu'à présent, est classée dans la catégorie des « dessins ordonnés ». Cela signifie que le tampon de sommets est parcouru à partir de l'indice spécifié et, pour chaque X sommets (1 pour les points, 2 pour les lignes…), une primitive est générée. C'est très simple d'utilisation, cependant le revers de la pièce est que, si un sommet est utilisé dans plusieurs primitives, alors il doit être présent plusieurs fois dans le tampon de sommets. C'est-à-dire qu'il n'y a pas de concept de partage de sommets.

Le partage de sommets est fourni par les fonctions de dessins appartenant à la catégorie des « dessins indexés ». Là, en plus du tampon de sommets, il y a aussi un tampon d'indices qui contient les indices du tampon de sommets. Le parcours du tampon d'indices est similaire au parcours du tampon de sommets : tous les X indices, une primitive est générée. Pour vous exercer au partage de sommets, vous pouvez simplement répéter l'indice du sommet partagé plusieurs fois. Le partage de sommets est très important en termes d'occupation mémoire, car la plupart des objets sont représentés par des maillages fermés de triangles, et la plupart des sommets prennent part dans la composition de plus d'un triangle.

Voici un exemple de dessin ordonné :

Image non disponible

Si nous effectuons un rendu de triangles, le GPU va générer la liste suivante : V0/1/2, V3/4/5 V6/7/8, etc.

Voici un exemple de dessin indexé :

Image non disponible

Dans ce cas le GPU va générer les triangles suivants : V4/0/1, V5/2/1, V6/1/7, etc.

L'utilisation de dessins indexés en OpenGL requiert la génération et le remplissage d'un tampon d'indices. Ce tampon doit être lié, en plus du tampon de sommets, avant la fonction de dessin et une fonction différente doit être utilisée.

III. Explication du code

Sélectionnez
GLuint IBO;

Nous avons ajouté un nouvel identifiant pour le tampon d'indices.

Sélectionnez
Vertices[0] = Vector3f(-1.0f, -1.0f, 0.0f);
Vertices[1] = Vector3f(0.0f, -1.0f, 1.0f);
Vertices[2] = Vector3f(1.0f, -1.0f, 0.0f);
Vertices[3] = Vector3f(0.0f, 1.0f, 0.0f);

Pour montrer le partage de sommets, nous avons besoin d'un maillage un peu plus complexe. De nombreux tutoriels utilisent le fameux cube tournant pour cela. Il requiert huit sommets et douze triangles. Comme je suis fainéant, j'utilise la pyramide tournante à la place. Elle requiert uniquement quatre sommets et quatre triangles, ce qui est beaucoup plus rapide à générer manuellement.

Lorsque l'on regarde ces sommets de dessus (le long de l'axe Y), nous voyons le schéma suivant :

Image non disponible
Sélectionnez
unsigned int Indices[] = { 0, 3, 1,
                           1, 3, 2,
                           2, 3, 0,
                           0, 1, 2 };

Le tampon d'indices est rempli en utilisant un tableau d'indices. Les indices correspondent à la position des sommets dans le tampon d'indices. Lorsque l'on regarde le tableau et le schéma au-dessus, nous pouvons voir que le dernier triangle est la base de la pyramide, tandis que les trois autres constituent ses faces. La pyramide n'est pas symétrique mais est très facile à spécifier.

Sélectionnez
glGenBuffers(1, &IBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);

Nous créons et remplissons le tampon d'indices en utilisant notre tableau. Vous pouvez voir que la seule différence, entre la création d'un tampon de sommets et la création d'un tampon d'indices, est que le tampon de sommets prend GL_ARRAY_BUFFER en tant que type de tampon, alors que le tampon d'indices prend GL_ELEMENT_ARRAY_BUFFER.

Sélectionnez
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);

En plus de lier le tampon de sommets, nous devons aussi lier le tampon d'indices avant de dessiner. Là encore, nous utilisons GL_ELEMENT_ARRAY_BUFFER comme type de tampon.

Sélectionnez
glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_INT, 0);

Nous utilisons glDrawElements au lieu de glDrawArrays. Le premier paramètre est le type de primitives à dessiner (comme pour glDrawArrays). Le second paramètre est le nombre d'indices, parmi ceux contenus dans le tampon d'indices, à utiliser pour la génération des primitives. Le paramètre suivant est le type de chaque indice. Le GPU doit être informé de la taille de chaque indice, sinon il ne saura pas comment parcourir le tampon. Les valeurs possibles ici sont GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, GL_UNSIGNED_INT. Si l'intervalle d'indices est petit, nous voudrons le plus petit type de données possible, qui prend moins de place en mémoire, et si l'intervalle d'indices est grand, nous voudrons les types les plus larges. Le dernier paramètre indique au GPU le point de départ en octets à partir duquel il commence le parcours du tampon. C'est utile quand le même tampon d'indices contient les indices de plusieurs objets. En spécifiant un point de départ et un nombre d'indices, nous précisons au GPU quel objet dessiner. Dans notre cas nous voulons commencer au début, donc nous spécifions zéro. Notons que le type du dernier paramètre est Glvoid*, ainsi, si nous voulons spécifier autre chose que zéro, nous devrons le convertir dans ce type.

IV. Sources

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources


Tutoriel 11 - Concaténation de transformations

  • II. Contexte
  • III. Explication du code
  • IV. Sources
  • V. Remerciements
  • Navigation

Dans ce tutoriel, vous allez apprendre à combiner vos matrices de transformation pour les appliquer à vos objets.

II. Contexte

Dans les derniers tutoriels, nous avions mis en place diverses transformations nous donnant la possibilité de déplacer un objet n'importe où dans un monde en 3D.

Nous en avons encore quelques-unes à apprendre (contrôle de caméra et projection en perspective), mais comme vous l'avez certainement deviné, une combinaison des transformations est nécessaire. Dans la plupart des cas, vous voudrez mettre un objet à l'échelle afin qu'il s'intègre correctement à votre monde 3D, le tourner dans l'orientation voulue, le déplacer quelque part, etc.

Jusqu'à présent, nous nous sommes exercés sur une seule transformation à la fois. Afin de mettre en place la série de transformations, nous devons multiplier la première matrice de transformation par la position du sommet, puis multiplier la matrice suivante par le résultat de cette multiplication, et ainsi de suite jusqu'à ce que toutes les matrices de transformation soient appliquées au sommet.

Une manière triviale de faire cela est de fournir toutes les matrices de transformation au shader et de le laisser faire les multiplications. Cependant, c'est très inefficace, car les matrices sont les mêmes pour tous les sommets et seule la position du sommet change. Par chance, l'algèbre linéaire fournit un ensemble de règles qui vont nous rendre la vie plus facile. Il est dit que pour un ensemble de matrices M0…Mn et un vecteur V, l'équation suivante est vraie :

Sélectionnez
Mn * Mn-1 * ... * M0 * V = (Mn* Mn-1 * ... * M0) * V

Donc, si on calcule :

Sélectionnez
N = Mn * Mn-1 * ... * M0

alors :

Sélectionnez
Mn * Mn-1 * ... * M0 * V = N * V

Cela signifie que nous pouvons calculer N une fois, puis l'envoyer au shader via une variable uniforme où elle sera multipliée à chaque sommet. Cela requiert donc une seule multiplication matrice/vecteur par sommet au niveau du GPU.

Comment ordonner les matrices lors de la génération de N ? La première chose dont il faut se souvenir est que le vecteur est d'abord multiplié par la matrice la plus à droite de la série (dans notre cas M0). Puis, le vecteur est transformé par chaque matrice de droite à gauche. En graphisme 3D, vous voudrez généralement mettre l'objet à l'échelle, puis l'orienter, le déplacer, lui appliquer la transformation de caméra et enfin le projeter en 2D. Voyons ce qui arrive lorsque vous tournez avant de déplacer :

Image non disponible

Voyons maintenant ce qui arrive lorsque vous déplacez avant de tourner :

Image non disponible

Comme vous pouvez le voir, il est très difficile de définir la position de l'objet dans le monde s'il est d'abord déplacé, car si vous le déplacez de son point d'origine, puis que vous le tournez, il tourne autour de l'origine, ce qui signifie que vous le déplacez encore. Le second déplacement est une chose que vous voulez éviter. En le tournant avant de le déplacer, vous retirez la connexion entre les deux opérations. C'est pour cela qu'il est toujours mieux de modéliser un objet autour de l'origine de la manière la plus symétrique possible. De cette manière, lorsque plus tard vous mettrez à l'échelle ou tournerez votre objet, il n'y aura pas d'effet de bord et l'objet mis à l'échelle ou tourné restera aussi symétrique qu'auparavant.

Maintenant que nous commençons à manipuler plusieurs transformations, nous devons perdre l'habitude de mettre à jour la matrice directement dans la fonction de rendu. Cette méthode n'est pas évolutive et est source d'erreur. À la place, une classe de pipeline est introduite. Cette classe masque les détails des manipulations de matrice au travers d'une interface simple afin de changer le déplacement, la rotation, etc. Après avoir défini tous les paramètres, vous extrayez simplement la matrice combinant toutes les transformations. Cette matrice peut alors être directement donnée au shader.

III. Explication du code

Sélectionnez
#define ToRadian(x) ((x) * M_PI / 180.0f)
#define ToDegree(x) ((x) * 180.0f / M_PI)

Nous commençons à utiliser les valeurs réelles des angles dans ce tutoriel. Les fonctions de la bibliothèque standard du C prennent des radians en paramètres. Les macros ci-dessus prennent un angle soit en radians, soit en degrés et le convertissent dans l'autre notation.

Sélectionnez
inline Matrix4f operator*(const Matrix4f& Right) const
{
    Matrix4f Ret;
    for (unsigned int i = 0 ; i < 4 ; i++) {
        for (unsigned int j = 0 ; j < 4 ; j++) {
            Ret.m[i][j] = m[i][0] * Right.m[0][j] +
                m[i][1] * Right.m[1][j] +
                m[i][2] * Right.m[2][j] +
                m[i][3] * Right.m[3][j];
        }
    }

    return Ret;
}

Cet opérateur prend en charge la multiplication de matrices. Comme vous pouvez le voir, chaque valeur de la matrice résultat est définie comme le produit scalaire de sa ligne dans la matrice de gauche avec sa colonne de la matrice de droite. Cet opérateur est la clef de l'implémentation de la classe du pipeline.

Sélectionnez
class Pipeline
{
public:
    Pipeline()
    { ...  }

    void Scale(float ScaleX, float ScaleY, float ScaleZ)
    { ... }

    void WorldPos(float x, float y, float z)
    { ... }

    void Rotate(float RotateX, float RotateY, float RotateZ)
    { ... }

    const Matrix4f* GetTrans();
private:
    Vector3f m_scale;
    Vector3f m_worldPos;
    Vector3f m_rotateInfo;
    Matrix4f m_transformation;
};

Le pipeline cache les détails de la récupération de la combinaison de toutes les transformations requises pour un simple objet. Il y a actuellement trois vecteurs membres privés qui contiennent la mise à l'échelle, la position dans l'espace monde et la rotation sur chaque axe. Il y a en plus les interfaces pour les définir et une fonction pour récupérer la matrice représentant la somme de ces transformations.

Sélectionnez
const Matrix4f* Pipeline::GetTrans()
{
        Matrix4f ScaleTrans, RotateTrans, TranslationTrans;
        InitScaleTransform(ScaleTrans);
        InitRotateTransform(RotateTrans);
        InitTranslationTransform(TranslationTrans);
        m_transformation = TranslationTrans * RotateTrans * ScaleTrans;
        return &m_transformation;
}

Cette fonction initialise trois matrices différentes, une par transformation, contenant la configuration actuelle. Elle les multiplie une par une et retourne le produit final. Notez que l'ordre est codé en dur et suit la description ci-dessus. Si vous avez besoin de plus de flexibilité ici, vous pouvez utiliser un masque de bits qui spécifiera l'ordre. Notez aussi que la transformation finale est stockée en tant que membre. Vous pouvez essayer d'optimiser cette fonction en vérifiant un indicateur de modification et en retournant la matrice stockée s'il n'y a pas eu de modifications dans la configuration depuis la dernière fois que cette fonction a été appelée.

Cette fonction utilise des méthodes privées pour générer les différentes transformations à partir de ce que nous avons appris lors des précédents tutoriels. Dans les tutoriels suivants, cette classe sera étendue pour gérer les contrôles de caméra et la perspective de projection.

Sélectionnez
Pipeline p;
p.Scale(sinf(Scale * 0.1f), sinf(Scale * 0.1f), sinf(Scale * 0.1f));
p.WorldPos(sinf(Scale), 0.0f, 0.0f);
p.Rotate(sinf(Scale) * 90.0f, sinf(Scale) * 90.0f, sinf(Scale) * 90.0f);
glUniformMatrix4fv(gWorldLocation, 1, GL_TRUE, (const Glfloat*)p.GetTrans());

Voici les changements dans la fonction de rendu. Nous allouons un objet de pipeline, le configurons et envoyons la transformation résultante au shader. Vous pouvez jouer avec les paramètres pour voir leur effet sur l'image finale.

IV. Sources

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources


Tutoriel 12 - Projection en perspective

  • II. Contexte
  • III. Explication du code
  • IV. Sources
  • V. Remerciements
  • Navigation

Dans ce tutoriel, vous allez découvrir comment projeter vos objets 3D sur une fenêtre 2D.

II. Contexte

Nous avons enfin atteint l'élément qui représente le mieux les graphismes 3D : la projection du monde 3D au monde 2D tout en maintenant l'impression de profondeur. Un bon exemple est l'image d'une route ou d'un rail de chemin de fer qui converge vers un point distant à l'horizon.

Nous allons générer la transformation qui satisfait les contraintes ci-dessus et une contrainte supplémentaire : nous allons rendre la vie plus facile au clipper en représentant les coordonnées projetées dans l'espace normalisé de -1 à +1. Cela signifie que le clipper peut faire son travail sans avoir connaissance ni des dimensions de l'écran, ni de la position des plans Z (plan Z proche et plan Z lointain).

La transformation de projection en perspective va nous imposer de fournir quatre paramètres :

  1. Le ratio d'aspect : le ratio entre la largeur et la hauteur de la zone rectangulaire qui sera la cible de la projection ;
  2. Le champ vertical : l'angle vertical de la caméra au travers duquel nous regardons le monde ;
  3. La position du plan Z proche qui nous permet de découper les objets trop proches de la caméra ;
  4. La position du plan Z lointain qui nous permet de découper les objets trop loin de la caméra.

Le ratio d'aspect est requis, car nous allons représenter toutes les coordonnées dans un espace normalisé dans lequel la hauteur est égale à la largeur. Comme c'est rarement le cas avec un écran où la largeur est généralement plus grande que la hauteur, il faut les représenter dans la transformation en « condensant » les points sur la ligne horizontale par rapport à la ligne verticale. Cela nous permettra d'intégrer plus de coordonnées en termes de composante X dans l'espace normalisé, ce qui satisfera la contrainte de « voir » plus en largeur qu'en hauteur sur l'image finale.

Le champ vertical nous permet de zoomer en avant et en arrière sur le monde. Considérons l'exemple suivant. Dans l'image de gauche, l'angle est plus grand, ce qui rend les objets plus petits alors que sur l'image de droite, l'angle est plus petit et fait paraître le même objet plus grand. Notez que cela a un effet sur la position de la caméra, ce qui est un peu contre intuitif. Sur l'image de droite (où nous zoomons avec un plus petit champ), la caméra doit être placée plus loin, et sur l'image de gauche, elle est plus proche du plan de projection. Cependant, il faut savoir que cela n'a pas d'effet réel, car les coordonnées projetées sont mappées sur l'écran et la position de la caméra n'a aucun effet dans ce calcul.

Image non disponible

Nous commençons par déterminer la distance du plan de projection par rapport à la caméra. Le plan de projection est parallèle au plan XY. Évidemment, le plan entier n'est pas visible, car il est trop grand. Nous voyons uniquement les objets dans une zone rectangulaire (appelée fenêtre de projection) qui a les mêmes proportions que notre écran. Le ratio d'aspect est calculé comme suit :

Sélectionnez
ar = largeur écran / hauteur écran
Image non disponible

Pour plus de simplicité, nous définissons la hauteur de la fenêtre de projection à 2, ce qui signifie que la largeur vaut exactement deux fois le ratio d'aspect (selon l'équation ci-dessus). Si nous plaçons la caméra à l'origine et regardons la zone de derrière la caméra, nous voyons ceci :

Tout ce qui se trouve au-dehors de ce rectangle sera découpé et nous voyons déjà que les points à l'intérieur de ce rectangle auront leurs coordonnées dans l'intervalle voulu. La composante X est pour l'instant un peu plus grande, mais nous allons fournir une correction plus tard.

Maintenant, jetons un œil là-dessus, vu de côté (en regardant le plan YZ) :

Image non disponible
Image non disponible

Nous calculons la distance entre la caméra et le plan de projection en utilisant le champ vertical (représenté par l'angle alpha) :

L'étape suivante est de calculer les coordonnées projetées pour X et Y. Considérons l'image suivante (là aussi en regardant le plan YZ) :

Image non disponible

Nous avons un point dans le monde 3D avec les coordonnées (x, y, z). Nous voulons trouver (xp, yp) qui représente les coordonnées projetées sur le plan de projection. Comme la composante X n'est pas dans la portée de ce diagramme (il pointe hors de la page), nous allons commencer par Y. À partir du théorème de Thalès, nous pouvons déterminer l'équation suivante :

Image non disponible

De la même manière, pour la composante X :

Image non disponible

Comme les dimensions de notre fenêtre de projection sont 2 * ar (largeur) par 2 (hauteur), nous savons qu'un point du monde 3D dans la fenêtre est projeté sur un point dont la composante X projetée est entre -ar et +ar et la composante Y projetée entre -1 et +1. Donc, la composante Y est normalisée, mais pas la composante X. Nous pouvons normaliser la composante Xp en la divisant par le ratio d'aspect. Cela signifie qu'un point dont la composante X projetée valait +ar vaut maintenant +1, ce qui la place tout à droite de la boîte normalisée. Si sa composante X projetée valait +0,5 et le ratio valait 1,333 (ce que l'on obtient lorsque nous avons une résolution de 1024 x 768), la nouvelle coordonnée X projetée vaut 0,375. En résumé, la division par le ratio d'aspect a pour effet de condenser les points sur l'axe X.

Nous avons atteint les équations suivantes pour la projection des composantes X et Y.

Image non disponible

Avant d'aller plus loin, essayons de voir à quoi ressemble la matrice de projection à ce stade. Cela signifie que nous allons représenter les équations ci-dessus en utilisant une matrice. Nous rencontrons maintenant un problème. Dans les deux équations, nous avons besoin de diviser X, Y et Z qui font partie du vecteur position. Cependant, la valeur de Z change d'un sommet à l'autre, donc nous ne pouvons pas le placer dans une seule matrice afin de projeter tous les sommets. Pour mieux comprendre cela, prenons le premier vecteur ligne de la matrice (a, b, c, d). Nous avons besoin de choisir les valeurs de ce vecteur de manière à ce que l'équation suivante se vérifie :

Image non disponible

C'est le produit scalaire entre le premier vecteur ligne de la matrice avec le vecteur position qui calcule la composante X finale. Nous pouvons définir « b » et « d », mais nous ne pouvons pas trouver de « a » ou de « c » qui puisse correspondre. La solution adoptée par OpenGL est de séparer la transformation en deux parties : la multiplication par la matrice de projection suivie de la division par Z en étape indépendante. La matrice fournie par l'application et le shader doivent inclure la multiplication de la position par Z. La division par la composante Z est effectuée en dur au niveau du GPU et prend effet dans le rasterizer (quelque part entre le vertex shader et le fragment shader). Comment le GPU connaît-il la sortie du vertex shader qu'il doit diviser par la composante Z ? Très simplement grâce à la variable gl_Position qui est là pour cela. Maintenant, nous devons juste trouver une matrice qui représente les équations de projection de X et Y ci-dessus.

Après la multiplication par cette matrice, le GPU peut diviser par Z automatiquement pour nous et nous récupérons le résultat que nous voulons. Mais il y a une autre complexité : si nous multiplions la matrice par la position du sommet, puis le divisons par Z, nous perdons complètement la valeur Z, car elle devient égale à 1 pour tous les sommets. La valeur Z originale doit être sauvegardée afin de pouvoir effectuer le test de profondeur par la suite. L'astuce est de copier la valeur Z originale dans la composante W du vecteur résultat, et de diviser seulement X, Y et Z par W au lieu de Z. W garde alors la valeur originale de Z qui peut être utilisée pour le test de profondeur. L'étape automatique de division de gl_Position par sa composante W est appelée « perspective divide ».

Image non disponible

Nous pouvons maintenant générer une matrice intermédiaire qui représente nos deux équations ainsi que la copie de Z dans la composante W :

Comme je l'ai dit plus tôt, nous voulons aussi inclure la normalisation de la valeur Z, car c'est plus facile pour le clipper de travailler sans connaître les plans Z proche et lointain. Cependant, la matrice ci-dessus met Z à zéro. Sachant qu'après la transformation du vecteur, le système va automatiquement faire l'étape de « perspective divide », nous avons besoin de définir les valeurs de la troisième ligne de la matrice afin que la division par Z dans l'intervalle de vue (c'est-à-dire : NearZ <= Z <= FarZ) mappe ces valeurs dans l'intervalle [-1, 1]. Cette opération de mappage est décomposée en deux parties. Tout d'abord, nous réduisons l'intervalle [NearZ, FarZ] à un intervalle de largeur 2. Puis, nous déplaçons cet intervalle de manière à ce qu'il débute à -1. La mise à l'échelle de la valeur Z, puis son déplacement sont représentés par la fonction générale :

Image non disponible

Mais après l'étape de « perspective divide », la partie droite de la fonction devient :

Image non disponible

Maintenant, nous devons trouver les valeurs de A et de B qui vont effectuer le mappage vers [-1, 1]. Nous savons que si Z vaut NearZ, le résultat doit valoir -1, et si Z vaut FarZ, le résultat doit valoir 1. Donc nous pouvons écrire :

Image non disponible

Maintenant, nous devons définir la troisième ligne de la matrice et le vecteur (a, b, c, d) qui satisfait l'équation suivante :

Image non disponible

Nous définissons tout de suite « a » et « b » à zéro, car nous ne voulons pas que X et Y aient un effet quelconque sur la transformation de Z. Donc, notre valeur A peut devenir « c » et notre valeur B peut devenir « d » (car W vaut 1).

Image non disponible

Ainsi, la matrice de transformation finale est :

Après la multiplication du vecteur position par la matrice projection, les coordonnées sont dites dans le « Clip Space ». Après l'étape de « perspective divide », les coordonnées sont dans le « NDC Space » (Normalized Device Coordinates).

Le chemin que nous avons suivi dans cette série de tutoriels devrait maintenant être clair. Sans faire de projection, nous pouvons simplement sortir du vertex shader des sommets dont les composantes XYZ (du vecteur position) sont dans l'intervalle [-1, 1]. Cela permet d'être certain qu'ils finissent quelque part sur l'écran. En s'assurant que W vaut toujours 1, nous nous prémunissons contre le fait que l'étape de « perspective divide » ait un effet. Après que les coordonnées soient transformées en coordonnées écran, nous aurons fini. En utilisant la matrice de projection, l'étape de « perspective divide » devient partie intégrante du processus de projection de 3D vers 2D.

III. Explication du code

Sélectionnez
void Pipeline::InitPerspectiveProj(Matrix4f& m) const
{
    const float ar = m_persProj.Width / m_persProj.Height;
    const float zNear = m_persProj.zNear;
    const float zFar = m_persProj.zFar;
    const float zRange = zNear - zFar;
    const float tanHalfFOV = tanf(ToRadian(m_persProj.FOV / 2.0));

    m.m[0][0] = 1.0f / (tanHalfFOV * ar);
    m.m[0][1] = 0.0f;
    m.m[0][2] = 0.0f;
    m.m[0][3] = 0.0f;

    m.m[1][0] = 0.0f;
    m.m[1][1] = 1.0f / tanHalfFOV;
    m.m[1][2] = 0.0f;
    m.m[1][3] = 0.0f;

    m.m[2][0] = 0.0f;
    m.m[2][1] = 0.0f;
    m.m[2][2] = (-zNear - zFar) / zRange;
    m.m[2][3] = 2.0f * zFar * zNear / zRange;

    m.m[3][0] = 0.0f;
    m.m[3][1] = 0.0f;
    m.m[3][2] = 1.0f;
    m.m[3][3] = 0.0f;
}

Une structure appelée m_persProj a été ajoutée à la classe Pipeline. Cette structure contient la configuration de la projection en perspective. La méthode ci-dessus génère la matrice que nous avons développée dans la section « Contexte ».

Sélectionnez
m_transformation = PersProjTrans * TranslationTrans * RotateTrans * ScaleTrans;

Nous ajoutons la matrice de projection en tant que première opérande de la multiplication qui génère la transformation complète. Souvenez-vous que comme le vecteur position est multiplié de droite à gauche, cette matrice est donc en fait la dernière. D'abord, nous mettons à l'échelle, puis nous tournons, déplaçons et enfin projetons.

Sélectionnez
p.SetPerspectiveProj(30.0f, WINDOW_WIDTH, WINDOW_HEIGHT, 1.0f, 1000.0f);

Dans la fonction de rendu, nous définissons les paramètres de la projection. Vous pouvez jouer avec ceux-ci afin de constater leur effet.

IV. Sources

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources


Tutoriel 13 - Espace caméra

  • I. Contexte
  • II. Explication du code
  • III. Sources
  • IV. Remerciements
  • Navigation

Dans ce tutoriel, nous allons voir comment placer une caméra n'importe où dans le monde 3D.

I. Contexte

Dans les derniers tutoriels, nous avons vu deux types de transformations. Les transformations du premier type sont celles qui changent la position (déplacement), l'orientation (rotation) ou la taille (mise à l'échelle) d'un objet. Ces transformations nous permettent de positionner un objet n'importe où dans un monde en 3D. Les transformations du second type sont les transformations de projection en perspective, qui prennent la position d'un sommet dans le monde en 3D et le projettent dans un monde en 2D (c'est-à-dire : un plan). Une fois que les coordonnées sont en 2D, il est très facile de les mapper directement en coordonnées de l'espace écran. Ces coordonnées sont utilisées pour transformer les primitives dont l'objet est composé (que ce soit des points, lignes ou triangles).

La pièce manquante au puzzle est la position de la caméra. Dans tous les tutoriels précédents, nous avons implicitement considéré que la caméra était simplement positionnée à l'origine de notre espace 3D. En réalité nous voulons avoir la liberté de placer la caméra n'importe où dans le monde et projeter les sommets sur un plan 2D en face d'elle. Cela reflétera la relation réelle entre la caméra et un objet sur l'écran.

Dans l'image suivante nous voyons la caméra positionnée quelque part, nous tournant le dos. On trouve aussi un plan 2D virtuel devant elle et une balle est projetée sur le plan. La caméra est inclinée et le plan suit cette inclinaison. Comme la vue de la caméra est limitée par son angle de vue, la partie visible du plan 2D (infini) est le rectangle. Tout ce qui se trouve hors de ce rectangle sera découpé. Notre objectif est de rendre le rectangle sur l'écran.

Image non disponible

Théoriquement, il est possible de générer les transformations qui projetteraient un objet du monde 3D sur un plan 2D face à une caméra placée à une position arbitraire du monde. Cependant les mathématiques associées seraient bien plus complexes que ce que nous avons vu précédemment. Il est beaucoup plus simple de faire cela lorsque la caméra est placée à l'origine du monde 3D, regardant le long de l'axe Z. Par exemple, un objet est placé en (0, 0, 5) et la caméra est en (0, 0, 1), regardant le long de l'axe Z (donc directement vers l'objet). Si nous déplaçons en même temps la caméra et l'objet d'une unité vers l'origine, alors la distance et l'orientation relatives (en termes de direction de la caméra) restent les mêmes, mais la caméra est alors placée à l'origine. Déplacer tous les objets de la scène de cette manière va nous permettre de rendre la scène correctement en utilisant les méthodes préalablement apprises.

L'exemple ci-dessus était simple car la caméra regardait déjà le long de l'axe X et était alignée sur les axes du système de coordonnées. Mais qu'arrive-t-il lorsque la caméra regarde ailleurs ? Regardons l'image suivante. Par simplicité, c'est un système de coordonnées à deux dimensions et nous regardons la caméra du dessus.

Image non disponible

À l'origine, la caméra regardait le long de l'axe X mais elle a tourné de 45° dans le sens des aiguilles d'une montre. Comme vous pouvez le voir, la caméra définit son propre système de coordonnées qui peut être identique à celui du monde (image du haut) ou être différente (image du bas). Il y a donc deux systèmes de coordonnées simultanément. Il y a le « système de coordonnées monde » dans lequel les objets sont spécifiés, et le système de coordonnées de la caméra qui est aligné sur les axes de la caméra (cible, haut et droite). Ces deux systèmes de coordonnées sont connus sous le nom de « espace monde » et « espace caméra/vue ».

La balle verte est située en (0, y, z) dans l'espace monde. Dans l'espace caméra elle est située quelque part dans le quart en haut à gauche du système de coordonnées (c'est-à-dire qu'elle a un X négatif et un Z positif). Nous devons déterminer la position de la balle dans l'espace caméra. Nous pourrons alors oublier l'espace monde et utiliser uniquement l'espace caméra. Dans l'espace caméra, la caméra est placée à l'origine et regarde le long de l'axe Z. Les objets sont exprimés de façon relative à la caméra et peuvent être rendus en utilisant les outils que nous avons mis en place.

Dire que la caméra a tourné de 45° dans le sens des aiguilles d'une montre est la même chose que dire que la balle verte a tourné de 45° dans le sens inverse des aiguilles d'une montre. Le mouvement des objets est toujours l'opposé du mouvement de la caméra. Donc plus généralement, nous devons ajouter deux nouvelles transformations et les brancher au pipeline de transformation que nous avions jusqu'à maintenant. Nous devons déplacer les objets de telle manière qu'ils maintiennent leur distance à la caméra pendant son déplacement à l'origine, et nous devons les tourner dans la direction opposée à la direction que la caméra prend.

Déplacer la caméra est très simple si elle est située en (x, y, z), alors la transformation de déplacement est (-x, -y, -z). La raison est directe : la caméra était placée dans le monde en utilisant une transformation de déplacement basée sur le vecteur (x, y, z), donc pour la replacer à l'origine nous devons utiliser la transformation de déplacement opposée. C'est pourquoi la matrice de transformation ressemble à ça :

Image non disponible

L'étape suivante est de tourner la caméra vers une cible dont les coordonnées sont exprimées dans l'espace monde. Nous voulons trouver la position des sommets dans le nouveau système de coordonnées défini par la caméra. La vraie question est donc : comment transformer des coordonnées d'un système à un autre ?

Regardons une fois encore l'image ci-dessus. Nous pouvons dire que le système de coordonnées monde est défini par trois vecteurs linéairement indépendants (1, 0, 0), (0, 1, 0) et (0, 0, 1). Linéairement indépendants signifie qu'il n'existe pas de coordonnées x, y et z différentes de zéro telles que x(1, 0, 0) + y(0, 1, 0) + z(0, 0, 1) = (0, 0, 0). En termes géométriques, cela signifie que chaque paire de vecteurs parmi ces trois, définit un plan qui est orthogonal au troisième vecteur (le plan XY est orthogonal à l'axe Z, etc.). Il est facile de voir que le système de coordonnées de la caméra est défini par les vecteurs (1, 0, -1), (0, 1, 0) et (1, 0, 1). Après normalisation de ces vecteurs, on obtient (0.7071, 0, -0.7071), (0, 1, 0) et (0.7071, 0, 0.7071).

L'image suivante montre comment la position d'un vecteur est exprimée dans deux systèmes de coordonnées différents :

Image non disponible

Nous savons comment récupérer les vecteurs unitaires qui représentent les axes de la caméra dans l'espace monde et nous connaissons la position du vecteur dans l'espace monde (x, y, z). Nous recherchons le vecteur (x', y', z'). Nous allons maintenant profiter d'une propriété du produit scalaire connue comme « projection orthogonale ». La projection orthogonale est le résultat du produit scalaire entre un vecteur quelconque A et un vecteur unitaire B, dont le résultat est la longueur de A dans la direction de B. En d'autres termes, la projection du vecteur A sur le vecteur B. Dans l'exemple ci-dessus, si nous faisons un produit scalaire entre (x, y, z) et le vecteur unitaire qui représente l'axe X de la caméra, nous obtenons x'. En procédant de la même manière nous pouvons obtenir y' et z'.(x', y', z') est la position de (x, y, z) dans l'espace caméra.

Voyons comment organiser cette connaissance en solution complète pour orienter la caméra. La solution est appelée « UVN camera » et est un système parmi d'autres permettant d'exprimer l'orientation d'une caméra. L'idée est que la caméra est définie par trois vecteurs.

  1. N : le vecteur de la caméra vers sa cible. Il est aussi connu sous le nom de « look at » dans certaines documentations 3D. Ce vecteur correspond à l'axe Z.
  2. V : pour une personne debout, il s'agit du vecteur pointant de la tête vers le ciel. Si vous écrivez un simulateur de vol et que l'avion est à l'envers, ce vecteur peut aussi ponter vers le sol. Ce vecteur correspond à l'axe Y.
  3. U : Ce vecteur pointe de la caméra vers son côté « droit ». Il correspond à l'axe X.

Afin de transformer une position de l'espace monde vers l'espace caméra défini par les vecteurs UVN, nous devons effectuer un produit scalaire entre la position et les vecteurs UVN. Une matrice représente mieux cela :

Image non disponible

Dans le code qui accompagne ce tutoriel, vous remarquerez que la variable globale de shader « gWorld » a été renommée « gWVP ». Ce changement reflète la façon par laquelle la série de transformations est connue dans de nombreux livres. WVP signifie « World-View-Projection ».

II. Explication du code

Dans ce tutoriel j'ai décidé de modifier un peu la conception et ai déplacé le code bas niveau de manipulation des matrices de la classe Pipeline vers la classe Matrix4f. La classe Pipeline demande maintenant à la classe Matrix4f de s'initialiser elle-même de diverses manières et concatène les multiples matrices pour créer la transformation finale.

pipeline.h
Sélectionnez
85.
86.
87.
88.
89.
struct {
    Vector3f Pos;
    Vector3f Target;
    Vector3f Up;
} m_camera;

La classe Pipeline a quelques membres pour stocker les paramètres de la caméra. Notez que l'axe qui pointe de la caméra vers sa droite est manquant (l'axe U). Il est calculé à la volée en utilisant un produit vectoriel entre les axes cible et haut. De plus il y a une nouvelle fonction nommée SetCamera pour définir ces valeurs.

Math3d.h
Sélectionnez
21.
22.
23.
24.
25.
26.
27.
28.
Vector3f Vector3f::Cross(const Vector3f& v) const
{
    const float _x = y * v.z - z * v.y;
    const float _y = z * v.x - x * v.z;
    const float _z = x * v.y - y * v.x;

    return Vector3f(_x, _y, _z);
}

La classe Vector3f possède une nouvelle méthode permettant de calculer le produit vectoriel entre deux Vector3f. Un produit vectoriel entre deux vecteurs, produit un vecteur qui est orthogonal au plan défini par les vecteurs. Cela devient plus intuitif lorsque l'on se souvient que les vecteurs ont une direction et une longueur mais pas de position. Tous les vecteurs ayant les mêmes directions et longueurs sont considérés comme égaux, quelque soit l'emplacement où ils « commencent ». Donc vous pourriez aussi bien faire que ces deux vecteurs commencent à l'origine. Cela signifie que vous pouvez créer un triangle qui a un sommet à l'origine et deux sommets au bout des vecteurs. Ce triangle définit un plan et le produit vectoriel est un vecteur qui est orthogonal à ce plan.

Math3d.h
Sélectionnez
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
Vector3f& Vector3f::Normalize()
{
    const float Length = sqrtf(x * x + y * y + z * z);

    x /= Length;
    y /= Length;
    z /= Length;

    return *this;
}

Pour générer la matrice UVN nous allons devoir mettre ces vecteurs à l'unité. Cette opération est connue comme « normalisation de vecteur » et est exécutée en divisant chaque composante du vecteur par la longueur du vecteur.

Math3d.h
Sélectionnez
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
void Matrix4f::InitCameraTransform(const Vector3f& Target, const Vector3f& Up)
{
    Vector3f N = Target;
    N.Normalize();
    Vector3f U = Up;
    U.Normalize();
    U = U.Cross(Target);
    Vector3f V = N.Cross(U);

    m[0][0] = U.x; m[0][1] = U.y; m[0][2] = U.z; m[0][3] = 0.0f;
    m[1][0] = V.x; m[1][1] = V.y; m[1][2] = V.z; m[1][3] = 0.0f;
    m[2][0] = N.x; m[2][1] = N.y; m[2][2] = N.z; m[2][3] = 0.0f;
    m[3][0] = 0.0f; m[3][1] = 0.0f; m[3][2] = 0.0f; m[3][3] = 1.0f;
}

Cette fonction génère la matrice de transformation de caméra, qui sera utilisée plus tard par la classe Pipeline. Les vecteurs U, V et N sont calculés et mis dans la matrice en tant que lignes. Comme la position du sommet va être multipliée en tant qu'opérande droit (et donc en tant que vecteur colonne), cela représente un produit scalaire entre U, V et N et la position. Cela génère les trois projections orthogonales qui deviennent les valeurs XYZ de la position en espace caméra.

La fonction est renseignée avec les vecteurs « cible » et « haut ». Le vecteur « droite » est calculé comme le produit vectoriel entre eux. Notez que nous ne faisons pas confiance à l'appelant pour le passage de vecteurs unitaires, donc nous les normalisons de toute façon. Après avoir généré le vecteur U, nous recalculons le vecteur « haut » comme le produit vectoriel entre le vecteur « cible » et le vecteur « droite ». La raison deviendra plus claire dans le futur, lorsque nous commencerons à déplacer la caméra. Il est plus simple de mettre à jour uniquement le vecteur « cible » et de laisser le vecteur « haut » non modifié. Cependant cela signifie que l'angle entre le vecteur « cible » et le vecteur « haut » ne sera pas de 90°, ce qui nous générera un système de coordonnées invalide. En recalculant le vecteur « droite » avec un produit vectoriel entre les vecteurs « cible » et « haut », puis en recalculant le vecteur « haut » avec un produit vectoriel entre les vecteurs « cible » et « droite », nous obtenons un système de coordonnées avec des angles droits entre chaque paire d'axes (un système orthonormé).

Pipeline.cpp
Sélectionnez
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
const Matrix4f* Pipeline::GetTrans()
{
    Matrix4f ScaleTrans, RotateTrans, TranslationTrans, CameraTranslationTrans, CameraRotateTrans, PersProjTrans;

    ScaleTrans.InitScaleTransform(m_scale.x, m_scale.y, m_scale.z);
    RotateTrans.InitRotateTransform(m_rotateInfo.x, m_rotateInfo.y, m_rotateInfo.z);
    TranslationTrans.InitTranslationTransform(m_worldPos.x, m_worldPos.y, m_worldPos.z);
    CameraTranslationTrans.InitTranslationTransform(-m_camera.Pos.x, -m_camera.Pos.y, -m_camera.Pos.z);
    CameraRotateTrans.InitCameraTransform(m_camera.Target, m_camera.Up);
    PersProjTrans.InitPersProjTransform(m_persProj.FOV, m_persProj.Width, m_persProj.Height, m_persProj.zNear, m_persProj.zFar);

    m_transformation = PersProjTrans * CameraRotateTrans * CameraTranslationTrans * TranslationTrans * RotateTrans * ScaleTrans;
    return &m_transformation;
}

Mettons à jour la fonction qui génère la matrice de transformation complète pour un objet. Elle devient assez complexe avec deux nouvelles matrices qui fournissent la partie caméra. Après le calcul de la transformation monde (la combinaison du déplacement, de la rotation et de la mise à l'échelle de l'objet), nous commençons la transformation de la caméra, en « déplaçant » la caméra à l'origine. Cela est fait par le déplacement en utilisant le vecteur négatif de la position de la caméra. Donc si la caméra est positionnée en (1, 2, 3) nous devons déplacer l'objet de (-1, -2, -3) afin d'avoir la caméra à l'origine. Après cela, nous générons la matrice de rotation de la caméra basée sur les vecteurs « cible » et « haut » de la caméra. Cela termine la partie caméra. Enfin, nous projetons les coordonnées.

Tutorial13,cpp
Sélectionnez
76.
77.
78.
79.
Vector3f CameraPos(1.0f, 1.0f, -3.0f);
Vector3f CameraTarget(0.45f, 0.0f, 1.0f);
Vector3f CameraUp(0.0f, 1.0f, 0.0f);
p.SetCamera(CameraPos, CameraTarget, CameraUp);

Nous utilisons la nouvelle fonctionnalité dans la boucle de rendu. Pour placer la caméra nous reculons par rapport à l'origine sur l'axe Z négatif, puis nous la déplaçons sur la droite et vers le haut. Le vecteur « haut » est tout simplement l'axe Y positif. Nous définissons tout cela dans l'objet Pipeline, la classe Pipeline s'occupe du reste.

III. Sources

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources


Tutoriel 14 - Contrôle de la caméra

Partie 1

  • I. Contexte
  • II. Explication du code
  • III. Sources
  • IV. Remerciements
  • Navigation

Ce tutoriel initie au contrôle de la caméra par l'utilisateur. Il se focalise sur le contrôle au clavier.

I. Contexte

Dans le tutoriel précédent, nous avions vu comment placer la caméra n'importe où dans le monde 3D. L'étape suivante est logiquement de permettre à l'utilisateur de la contrôler. Les mouvements ne seront pas restreints : l'utilisateur sera capable de bouger dans toutes les directions. Le contrôle de la caméra sera effectué en utilisant deux périphériques d'entrée : le clavier pour contrôler la position et la souris pour changer notre point de vue. C'est similaire à ce que la plupart des « First Person Shooter » font. Ce tutoriel va se concentrer sur le clavier et le suivant se focalisera sur la souris.

Nous allons gérer les quatre flèches directionnelles de manière conventionnelle. Rappelez-vous que notre transformation de caméra est définie par la position, par le vecteur cible et par le vecteur haut. Quand nous nous déplaçons en utilisant le clavier, nous changeons uniquement la position. Nous ne pouvons pas incliner la caméra, ni la tourner ; donc les vecteurs cible et haut restent inchangés.

Pour contrôler le clavier, nous utilisons une autre fonction de la bibliothèque GLUT : glutSpecialFunc(). Cette fonction enregistre un callback qui est appelé quand une touche « spéciale » est appuyée. Les touches spéciales comprennent : les touches F1 à F12, les flèches directionnelles, Page Haut, Page Bas, Origine, Fin et Insert. Si vous voulez récupérer les touches classiques (caractères et numériques), utilisez glutKeyboardFunc().

II. Explication du code

Les fonctionnalités de la caméra sont encapsulées dans la classe Camera. Cette classe regroupe les propriétés de la caméra et peut les modifier en fonction des événements de mouvement qu'elle reçoit. Les propriétés sont récupérées par la classe Pipeline qui génère la matrice de transformation basée dessus.

Camera.h
Sélectionnez
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
class Camera
{
public:

 Camera();
 Camera(const Vector3f& Pos, const Vector3f& Target, const Vector3f& Up);
 bool OnKeyboard(int Key);
    const Vector3f& GetPos() const
    const Vector3f& GetTarget() const
    const Vector3f& GetUp() const

private:

    Vector3f m_pos;
    Vector3f m_target;
    Vector3f m_up;
};

Ceci est la déclaration de la classe Camera. Elle stocke les trois propriétés définissant la caméra : les vecteurs position, cible et haut. Deux constructeurs sont disponibles. Celui par défaut place simplement la caméra à l'origine, regardant le long de l'axe Z positif, avec un vecteur haut pointant vers le ciel (0, 1, 0). Il est aussi possible de créer la caméra avec des propriétés spécifiques. La fonction OnKeyboard() transmet les événements clavier à la caméra. Elle retourne un booléen qui indique si l'événement a été consommé par la classe. Si la touche est appropriée (une des flèches directionnelles), la valeur de retour est true, sinon false. De cette manière, vous pouvez construire une chaîne de clients qui reçoivent les événements clavier et vous arrêter après le premier qui fait réellement quelque chose de cet événement.

Camera.cpp
Sélectionnez
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
bool Camera::OnKeyboard(int Key)
{
    bool Ret = false;

    switch (Key) {

    case GLUT_KEY_UP:
    {
        m_pos += (m_target * StepSize);
        Ret = true;
    }
    break;

    case GLUT_KEY_DOWN:
    {
        m_pos -= (m_target * StepSize);
        Ret = true;
    }
    break;

    case GLUT_KEY_LEFT:
    {
        Vector3f Left = m_target.Cross(m_up);
        Left.Normalize();
        Left *= StepSize;
        m_pos += Left;
        Ret = true;
    }
    break;

    case GLUT_KEY_RIGHT:
    {
        Vector3f Right = m_up.Cross(m_target);
        Right.Normalize();
        Right *= StepSize;
        m_pos += Right;
        Ret = true;
    }
    break;
    }

    return Ret;
}

Cette fonction déplace la caméra en fonction des événements clavier. GLUT définit des macros correspondant aux flèches directionnelles, c'est ce sur quoi l'instruction « switch » est basée. Malheureusement ces macros sont de simples « int » au lieu d'une énumération.

Les mouvements avant et arrière sont les plus simples. Comme le mouvement est toujours le long du vecteur cible, nous avons seulement besoin d'ajouter ou de soustraire celui-ci de la position. Le vecteur cible lui-même reste inchangé. Notez qu'avant d'ajouter ou de soustraire le vecteur cible, nous le multiplions par une valeur constante appelée StepSize. Nous faisons cela pour chaque flèche directionnelle. StepSize fournit un point unique pour modifier la vitesse de déplacement (plus tard nous envisagerons de la transformer en propriété de classe). Pour rendre StepSize constante, nous devons nous assurer de toujours la multiplier à des vecteurs unitaires (c'est-à-dire que nous devons nous assurer que les vecteurs cible et haut sont toujours unitaires).

Le déplacement sur le côté est un peu plus complexe. Il est défini comme un mouvement le long d'un vecteur orthogonal au plan créé par les vecteurs cible et haut. Ce plan divise l'espace 3D en deux parties. Il y a deux vecteurs qui y sont orthogonaux et ils sont opposés l'un à l'autre. Nous pouvons appeler l'un « gauche » et l'autre « droite ». Ils sont générés en utilisant un produit vectoriel des vecteurs cible et haut dans les deux combinaisons possibles : cible fois haut ou haut fois cible (le produit vectoriel n'est pas une opération commutative : changer l'ordre des paramètres génère un résultat différent). Après avoir récupéré le vecteur gauche/droite, nous le normalisons, le multiplions par StepSize et l'ajoutons au vecteur position (ce qui le déplace vers la gauche ou la droite). Là aussi, les vecteurs cible et haut sont inchangés.

Notez que les opérations de cette fonction utilisent quelques nouveaux opérateurs tels que += et -= qui ont été ajoutés à la classe Vector3f.

tutorial14.cpp
Sélectionnez
73.
74.
75.
76.
77.
78.
static void InitializeGlutCallbacks()
{
    glutDisplayFunc(RenderSceneCB);
    glutIdleFunc(RenderSceneCB);
    glutSpecialFunc(SpecialKeyboardCB);
}

Ici, nous enregistrons un nouveau callback afin de traiter les événements clavier spéciaux. Le callback reçoit la touche et la position de la souris au moment où la touche est appuyée. Nous ignorons la position de la souris et donnons l'événement à une instance de la classe caméra allouée dans la section globale du fichier.

Tutorial14.cpp
Sélectionnez
55.
p.SetCamera(GameCamera.GetPos(), GameCamera.GetTarget(), GameCamera.GetUp());

Précédemment, nous avions initialisé les paramètres de la caméra dans la classe Pipeline en utilisant des vecteurs codés en dur. Maintenant nous avons supprimé ces vecteurs et nous utilisons les propriétés de la caméra directement à partir de la classe Camera.

III. Sources

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources


Tutoriel 15 - Contrôle de la caméra

Partie 2

  • I. Contexte
  • II. Explication du code
  • III. Sources
  • IV. Remerciements
  • Navigation

Ce tutoriel poursuit l'implémentation du contrôle de la caméra par l'utilisateur. Il se focalise sur le contrôle à la souris. Il introduit aussi la notion de quaternion.

I. Contexte

Dans ce tutoriel, nous allons terminer l'implémentation de la caméra en activant le contrôle de la direction par la souris. Il y a divers degrés de liberté associés à la conception de caméra. Nous allons permettre le degré de contrôle que l'on pourrait attendre dans un jeu à la première personne. Cela signifie que vous pourrez tourner la caméra à 360° (autour de l'axe Y), ce qui correspondrait à tourner la tête ou faire un tour complet avec le corps. Nous serons de plus capables d'incliner la caméra en haut et en bas pour pouvoir regarder au-dessus ou en dessous. Nous ne serons pas capables d'incliner la caméra jusqu'à faire un tour complet, ou de l'incliner dans l'autre sens (en vrille). Ces degrés de liberté sont du domaine des simulateurs de vol, qui sont hors de la portée de ce tutoriel. Néanmoins, nous aurons une caméra nous permettant d'explorer confortablement le monde en 3D que nous développerons dans les tutoriels suivants.

Le canon sol-air de la Seconde Guerre mondiale suivant montre le type de caméra que nous allons construire :

Image non disponible

Ce canon possède deux axes de contrôle :

  1. Il peut tourner à 360 degrés autour de l'axe (0, 1, 0). Cet angle est appelé « angle horizontal » et le vecteur est « l'axe vertical » ;
  2. Il peut s'incliner vers le haut et vers le bas autour d'un vecteur parallèle au sol. Ce mouvement est limité et le canon ne peut pas effectuer de cercle complet. Cet angle est appelé « angle vertical » et le vecteur est « l'axe horizontal ». Notez qu'alors que l'axe vertical est constant (0, 1, 0), l'axe horizontal quant à lui tourne avec le canon et est toujours perpendiculaire à la cible du canon. C'est un point-clef à intégrer pour comprendre correctement les formules à venir.

L'objectif est de suivre le mouvement de la souris, de changer l'angle horizontal quand la souris se déplace à gauche et à droite, et l'angle vertical quand la souris se déplace en haut et en bas. Étant donné ces deux angles, nous voulons calculer le vecteur cible et le vecteur haut.

Tourner le vecteur cible selon l'angle horizontal est évident. En utilisant de la trigonométrie basique, nous pouvons voir que la composante Z du vecteur cible est le sinus de l'angle horizontal tandis que la composante X en est le cosinus (à ce stade la caméra regarde droit devant donc Y vaut zéro. Retournez au septième tutoriel pour avoir une image de cela.

Tourner le vecteur cible selon l'angle vertical est plus complexe, car l'axe horizontal tourne avec la caméra. L'axe horizontal peut être calculé en faisant un produit vectoriel entre l'axe vertical et le vecteur cible après qu'il a été tourné selon l'angle horizontal, mais tourner autour d'un vecteur quelconque (pour lever ou baisser le canon) peut être ardu.

Heureusement nous avons un outil mathématique extrêmement utile pour ce problème : le quaternion. Les quaternions ont été découverts en 1843 par Sir William Rovan Hamilton, un mathématicien irlandais, et sont basés sur l'ensemble des nombres complexes. Le quaternion « Q » est défini comme suit :

Image non disponible

où i, j et k sont des nombres complexes et l'équation suivante est vraie :

Image non disponible

En pratique, nous définissons un quaternion comme un vecteur 4D (x, y, z, w). Le conjugué du quaternion « Q » est défini comme suit :

Image non disponible

La normalisation d'un quaternion est la même opération que pour un vecteur. Je vais décrire les étapes requises afin de tourner un vecteur autour d'un autre vecteur quelconque, en utilisant un quaternion. Plus de détails sur la preuve mathématique à l'origine de ces étapes peuvent être trouvés sur internet.

La fonction générale utilisée pour calculer un quaternion « W » représentant le vecteur « V » tourné selon un angle « a » est :

Image non disponible

Où Q est le quaternion de rotation défini comme suit :

Image non disponible

Après avoir calculé « W », le vecteur orienté est simplement (W.x, W.y, W.z). Un point important à noter dans le calcul de « W » est que nous devons d'abord multiplier « Q » par « V », ce qui est une multiplication quaternion par vecteur qui a pour résultat un quaternion, puis nous devons faire une multiplication quaternion par quaternion (le résultat de Q*V multiplié par le conjugué de Q). Les deux types de multiplications ne sont pas les mêmes. Le fichier math3d.cpp intègre les implémentations de ces types de multiplications.

Nous devons maintenir les angles horizontal et vertical à jour, car l'utilisateur déplace la souris dans l'écran et nous devons décider de comment les initialiser. Le choix logique est de les initialiser en fonction du vecteur cible fourni au constructeur de la caméra. Commençons par l'angle horizontal. Regardons l'image suivante, qui regarde le plan XZ vu de dessus :

Image non disponible

Le vecteur cible est (x, z) et nous voulons calculer l'angle horizontal qui est représenté par la lettre alpha (la composante Y n'étant utile que pour l'angle vertical). Comme la longueur du cercle vaut 1, il est assez simple de voir que le sinus d'alpha est exactement z. Donc calculer l'arc sinus de z donnera alpha. En avons-nous terminé ? Pas encore. Comme z peut être dans l'intervalle [-1, 1], le résultat de l'arc sinus va de -90° à +90°. Cependant l'angle horizontal peut valoir jusque 360°. De plus, notre quaternion tourne dans le sens des aiguilles d'une montre. Cela signifie que lorsque nous tournons de 90° avec le quaternion, nous finissons à -1 sur l'axe Z, ce qui est l'opposé du sinus de 90° (qui vaut 1). À mes yeux, la manière la plus simple d'obtenir le bon résultat est d'utiliser l'arc sinus avec la valeur absolue de Z et de combiner le résultat avec le quartier du cercle dans lequel le vecteur se trouve. Par exemple, quand notre vecteur cible est (0, 1), nous calculons l'arc sinus de 1 qui est donc 90 et le retranchons de 360. Le résultat est 270. L'intervalle d'arc sinus pour des valeurs entre 0 et 1 est de 0 à 90°. En combinant cela avec le quartier spécifique du cercle, nous obtenons l'angle horizontal final.

Calculer l'angle vertical est un peu plus simple. Nous allons limiter l'intervalle de rotation de -90° (égal à 270° : regarder verticalement en haut) à +90° (regarder verticalement vers le bas). Cela signifie que nous avons juste besoin de la valeur négative de l'arc sinus de la composante Y dans le vecteur cible. Quand Y vaut 1 (regarder en haut) l'arc sinus vaut 90, nous avons juste à inverser le signe. Quand Y vaut -1 (regarder en bas), l'arc sinus vaut alors -90 et inverser son signe fait donc 90. Si vous êtes troublé, regardez une nouvelle fois l'image en remplaçant simplement Z par Y et X par Z.

II. Explication du code

Camera.cpp
Sélectionnez
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
Camera::Camera(int WindowWidth, int WindowHeight, const Vector3f& Pos, const Vector3f& Target, const Vector3f& Up)
{
    m_windowWidth = WindowWidth;
    m_windowHeight = WindowHeight;
    m_pos = Pos;

    m_target = Target;
    m_target.Normalize();

    m_up = Up;
    m_up.Normalize();

    Init();
}

Le constructeur de la caméra récupère maintenant les dimensions de la fenêtre. Nous en avons besoin afin de déplacer la souris au centre de l'écran. De plus, notez l'appel à la méthode Init() qui définit les propriétés internes à la caméra.

camera.cpp
Sélectionnez
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
void Camera::Init()
{
    Vector3f HTarget(m_target.x, 0.0, m_target.z);
    HTarget.Normalize();

    if (HTarget.z >= 0.0f)
    {
        if (HTarget.x >= 0.0f)
        {
            m_AngleH = 360.0f - ToDegree(asin(HTarget.z));
        }
        else
        {
            m_AngleH = 180.0f + ToDegree(asin(HTarget.z));
        }
    }
    else
    {
        if (HTarget.x >= 0.0f)
        {
            m_AngleH = ToDegree(asin(-HTarget.z));
        }
        else
        {
            m_AngleH = 90.0f + ToDegree(asin(-HTarget.z));
        }
    }

    m_AngleV = -ToDegree(asin(m_target.y));

    m_OnUpperEdge = false;
    m_OnLowerEdge = false;
    m_OnLeftEdge = false;
    m_OnRightEdge = false;
    m_mousePos.x = m_windowWidth / 2;
    m_mousePos.y = m_windowHeight / 2;

    glutWarpPointer(m_mousePos.x, m_mousePos.y);
}

Dans la fonction Init(), nous commençons par calculer l'angle horizontal. Nous créons un nouveau vecteur cible appelé HTarget (cible horizontale) qui est une projection du vecteur cible original sur le plan XZ. Puis nous le normalisons (car les formules décrites plus tôt utilisent un vecteur unitaire sur le plan XZ). Enfin nous vérifions à quel quartier du cercle le vecteur cible appartient et calculons l'angle final, basé sur la valeur positive de la composante Z. Ensuite nous calculons l'angle vertical, ce qui est beaucoup plus simple.

La caméra a quatre nouveaux indicateurs pour indiquer si la souris est placée sur un des bords de l'écran. Nous allons implémenter une rotation automatique dans la direction correspondante lorsque cela arrive. Cela nous permettra de tourner à 360°. Nous initialisons ces indicateurs à FALSE, car la souris commence au milieu de l'écran. Les deux lignes suivantes calculent où se trouve le centre de l'écran (basé sur les dimensions de la fenêtre), et la fonction glutWarpPointer() déplace effectivement la souris. Commencer avec la souris au centre de l'écran nous rend la vie beaucoup plus simple.

camera.cpp
Sélectionnez
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
void Camera::OnMouse(int x, int y)
{
    const int DeltaX = x - m_mousePos.x;
    const int DeltaY = y - m_mousePos.y;

    m_mousePos.x = x;
    m_mousePos.y = y;

    m_AngleH += (float)DeltaX / 20.0f;
    m_AngleV += (float)DeltaY / 20.0f;

    if (DeltaX == 0) {
        if (x <= MARGIN) {
            m_OnLeftEdge = true;
        }
        else if (x >= (m_windowWidth - MARGIN)) {
            m_OnRightEdge = true;
        }
    }
    else {
        m_OnLeftEdge = false;
        m_OnRightEdge = false;
    }

    if (DeltaY == 0) {
        if (y <= MARGIN) {
            m_OnUpperEdge = true;
        }
        else if (y >= (m_windowHeight - MARGIN)) {
            m_OnLowerEdge = true;
        }
    }
    else {
        m_OnUpperEdge = false;
        m_OnLowerEdge = false;
    }

    Update();
}

Cette fonction est utilisée pour notifier à la caméra que la souris a bougé. Les paramètres correspondent à la nouvelle position de la souris. Nous commençons par calculer la différence avec la position précédente pour les axes X et Y. Puis nous stockons ces valeurs pour le prochain appel de cette fonction. Nous mettons à jour les angles horizontal et vertical en divisant les deltas. J'utilise des facteurs qui fonctionnent bien pour moi, mais sur d'autres ordinateurs vous pouvez avoir besoin d'autres valeurs pour ces facteurs. Nous améliorerons cela dans un tutoriel à venir quand nous mettrons la fréquence d'images de l'application comme facteur.

Le groupe de tests suivant met à jour les indicateurs « m_On*Edge » en fonction de la position de la souris. Il y a une marge qui enclenche le comportement de « bord » si la souris s'approche d'un des bords de l'écran. Cette marge vaut par défaut 10 pixels. Enfin, nous appelons Update() pour recalculer les vecteurs cible et haut basés sur les nouvelles valeurs des angles horizontal et vertical.

camera.cpp
Sélectionnez
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
void Camera::OnRender()
{
    bool ShouldUpdate = false;

    if (m_OnLeftEdge) {
        m_AngleH -= 0.1f;
        ShouldUpdate = true;
    }
    else if (m_OnRightEdge) {
        m_AngleH += 0.1f;
        ShouldUpdate = true;
    }

    if (m_OnUpperEdge) {
        if (m_AngleV > -90.0f) {
            m_AngleV -= 0.1f;
            ShouldUpdate = true;
        }
    }
    else if (m_OnLowerEdge) {
        if (m_AngleV < 90.0f) {
            m_AngleV += 0.1f;
            ShouldUpdate = true;
        }
    }

    if (ShouldUpdate) {
        Update();
    }
}

Cette fonction est appelée à partir de la boucle principale. Nous en avons besoin pour les cas où la souris est placée sur les bords de l'écran et ne bouge pas. Dans ce cas, il n'y a pas d'événement de souris, mais nous voulons malgré tout que la caméra continue à bouger (jusqu'à ce que la souris quitte le bord). Nous vérifions si un des indicateurs est à TRUE et mettons à jour l'angle correspondant. S'il y a eu un changement dans un des angles, nous appelons Update() pour mettre à jour les vecteurs cible et haut. Quand la souris quitte l'écran, nous le détectons dans le gestionnaire d'événements souris et mettons l'indicateur à FALSE. Notez la façon de limiter l'angle vertical entre -90° et +90°. C'est pour empêcher de faire des tours complets en regardant en haut ou en bas.

camera.cpp
Sélectionnez
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
void Camera::Update()
{
    const Vector3f Vaxis(0.0f, 1.0f, 0.0f);

    // Tourne le vecteur de vue avec l'angle horizontal autour de l'axe vertical
    Vector3f View(1.0f, 0.0f, 0.0f);
    View.Rotate(m_AngleH, Vaxis);
    View.Normalize();

    // Tourne le vecteur de vue avec l'angle vertical autour de l'axe horizontal
    Vector3f Haxis = Vaxis.Cross(View);
    Haxis.Normalize();
    View.Rotate(m_AngleV, Haxis);
    View.Normalize();

    m_target = View;
    m_target.Normalize();

    m_up = m_target.Cross(Haxis);
    m_up.Normalize();
}

Cette fonction met à jour les vecteurs cible et haut en fonction des angles vertical et horizontal. Nous commençons par un vecteur de vue dans l'état « reset ». Cela signifie qu'il est parallèle au sol (son angle vertical est zéro) et il regarde à droite (angle vertical à zéro, regardez l'image au-dessus). Nous mettons l'axe vertical vers le haut, puis nous tournons notre angle de vue autour, selon l'angle horizontal. Le résultat est un vecteur qui pointe dans la direction générale de la cible voulue, mais pas nécessairement à la bonne hauteur (par exemple, il est sur le plan XZ). En faisant un produit vectoriel de ce vecteur avec l'axe vertical, nous obtenons un autre vecteur sur le plan XZ, qui est orthogonal au plan créé par le vecteur vue et l'axe vertical. C'est notre nouvel axe horizontal et il est maintenant temps de tourner le vecteur vue autour de cet axe, en haut ou en bas, en fonction de l'angle vertical. Le résultat est le vecteur cible final et nous le mettons dans la variable membre correspondante. Maintenant, nous devons corriger le vecteur haut. Par exemple, si la caméra regarde en haut, le vecteur haut doit s'incliner vers l'arrière pour compenser (il doit être à 90° du vecteur cible). C'est similaire à la manière dont la tête s'incline en arrière lorsque l'on regarde haut dans le ciel. Le nouveau vecteur est calculé simplement, en faisant un autre produit vectoriel entre le vecteur cible final et l'axe horizontal. Si l'angle vertical vaut toujours zéro, alors le vecteur cible reste sur le plan XZ et le vecteur haut reste à (0, 1, 0). Si le vecteur cible est incliné en haut ou en bas, le vecteur haut va s'incliner en arrière ou en avant, respectivement.

tutorial15.cpp
Sélectionnez
209.
210.
glutGameModeString("1920x1200@32");
glutEnterGameMode();

Ces fonctions GLUT permettent à notre application de s'exécuter en plein écran dans le mode haute performance, appelé « mode de jeu ». Cela permet de tourner plus simplement la caméra sur 360°, car tout ce que nous avons à faire est amener la souris sur un des bords de l'écran. Notez que la résolution et le nombre de bits par pixel sont configurés en mode de jeu via une chaîne de caractères. 32 bits par pixel fournit le maximum de couleurs pour le rendu.

tutorial15.cpp
Sélectionnez
214.
pGameCamera = new Camera(WINDOW_WIDTH, WINDOW_HEIGHT);

La caméra est maintenant allouée dynamiquement à cet endroit, car elle effectue un appel GLUT (glutWarpPointer). Cet appel échouera si GLUT n'a pas encore été initialisé.

tutorial15.cpp
Sélectionnez
99.
100.
glutPassiveMotionFunc(PassiveMouseCB);
glutKeyboardFunc(KeyboardCB);

Ici, nous enregistrons deux nouvelles fonctions de callback. Une pour la souris et l'autre pour les touches classiques du clavier (le callback pour les touches spéciales récupère les flèches directionnelles et les touches des fonctions). « Passive motion » signifie que la souris se déplace sans qu'un seul de ses boutons soit appuyé.

tutorial.cpp
Sélectionnez
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
static void KeyboardCB(unsigned char Key, int x, int y)
{
    switch (Key) {
        case 'q':
            exit(0);
    }
}

static void PassiveMouseCB(int x, int y)
{
    pGameCamera->OnMouse(x, y);
}

Maintenant que nous utilisons le mode plein écran, il est plus difficile de quitter l'application. Le callback de clavier récupère l'appui sur la touche « q » pour quitter. Le callback de la souris transfère simplement la position de la souris à la caméra.

tutorial15.cpp
Sélectionnez
44.
45.
46.
static void RenderSceneCB()
{
    pGameCamera->OnRender();

À chaque fois que nous passons dans la boucle de rendu, nous devons en informer la caméra. Cela permet à celle-ci de tourner lorsque la souris ne bouge pas et est sur un bord de l'écran.

III. Sources

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources


Tutoriel 16 - Application basique de texture

  • I. Contexte
  • II. Explication du code
  • III. Exercice
  • IV. Sources
  • V. Remerciements
  • Navigation

Ce tutoriel présente le placage de textures.

I. Contexte

L'application de texture consiste en l'application d'une image sur une ou plusieurs faces d'un modèle 3D. L'image (nommée « texture ») peut être n'importe quoi, mais représente généralement un motif, tel que des briques, du feuillage, de la terre, etc. Cela ajoute du réalisme à une scène. Comparons par exemple les images suivantes :

Image non disponible

Pour que le placage de textures fonctionne, nous avons besoin de trois choses : charger la texture dans OpenGL, fournir les coordonnées de texture avec les sommets (pour appliquer la texture dessus) et effectuer une opération d'échantillonnage à partir de la texture, en utilisant les coordonnées de texture, pour récupérer la couleur du pixel. Comme un triangle est mis à l'échelle, tourné, déplacé et enfin projeté, il peut apparaître sur l'écran d'une multitude de façons possibles et sembler différent, selon son orientation par rapport à la caméra. Le GPU doit faire en sorte que la texture suive le mouvement des sommets du triangle afin que cela semble réaliste (si la texture semble « nager » en travers du triangle, ça ne sera pas bien). Pour cela, le développeur fournit un ensemble de coordonnées, connu sous le nom de « coordonnées de texture » pour chaque sommet. Quand le GPU dessine le triangle, il interpole les coordonnées de texture sur toute la face du triangle et, dans le fragment shader, le développeur affecte ces coordonnées à la texture. Cette action est nommée « échantillonnage » et son résultat est un texel (pixel dans la texture). Le texel contient souvent une couleur qui est utilisée pour peindre le pixel correspondant à l'écran. Dans les tutoriels à venir, nous verrons que le texel peut contenir différents types de données qui peuvent être utilisées pour divers effets.

OpenGL supporte divers types de texture tels que 1D, 2D, 3D, cube… qui peuvent être utilisés dans des techniques différentes. Nous nous contenterons d'étudier les textures 2D pour l'instant. Une texture 2D possède une largeur et une hauteur qui peuvent valoir n'importe quelle valeur, dans la limite de la spécification. La multiplication de la largeur par la hauteur nous donne le nombre de texels de la texture. Comment pouvons-nous spécifier les coordonnées de texture d'un sommet ? Non, ce ne sont pas les coordonnées du texel dans la texture. Ce serait trop limitant, car remplacer la texture par une autre ayant des dimensions différentes signifie que nous devrons mettre à jour les coordonnées de texture de chaque sommet pour qu'elles correspondent à la nouvelle texture. Le scénario idéal est d'être capable de changer de texture sans changer les coordonnées de texture. Ainsi, les coordonnées de texture sont exprimées dans « l'espace texture », qui est simplement l'intervalle normalisé [0,1]. Ce qui signifie que les coordonnées de texture utilisent généralement une fraction et, en multipliant cette fraction par la hauteur/largeur correspondante, on récupère les coordonnées du texel dans la texture. Par exemple, si les coordonnées de texture sont [0.5, 0.1] et que la texture a pour largeur 320 et pour hauteur 200, la position du texel sera (160, 20) (0.5 * 320 = 160 et 0.1 * 200 = 20).

La convention habituelle est d'utiliser U et V comme axes de l'espace texture, où U correspond à l'axe X du système cartésien de coordonnées 2D, et V correspond à l'axe Y. OpenGL traite les valeurs des axes UV, de gauche à droite pour l'axe U et de bas en haut pour l'axe V. Regardons l'image suivante :

Image non disponible

Cette image représente l'espace texture et nous pouvons voir que l'origine de cet espace est le coin en bas à gauche. U augmente vers la droite et V augmente vers le haut. Considérons maintenant un triangle dont les coordonnées de texture sont définies comme sur l'image suivante :

Image non disponible

Disons que nous appliquons une texture de sorte que lorsque nous utilisons ces coordonnées de texture, nous voyons l'image de la petite maison à l'emplacement ci-dessus. Maintenant le triangle subit diverses transformations et lorsque vient le moment de le rastériser, il ressemble à ceci :

Image non disponible

Comme nous pouvons le voir, les coordonnées de texture « collent » aux sommets, car elles sont des attributs fondamentaux et ne changent pas avec les transformations. Lors de l'interpolation des coordonnées de texture, la plupart des pixels reçoivent la même coordonnée de texture que l'image originale (car ils sont restés à la même position, par rapport aux sommets) et, comme le triangle a été retourné, la texture a subi le même changement. Cela signifie que lorsque le triangle original est tourné, étiré ou écrasé, la texture suit sagement ces changements. Notons qu'il y a aussi des techniques, qui changent les coordonnées de texture, afin de déplacer la texture au travers de la face du triangle, de manière contrôlée. Cependant nos coordonnées resteront fixes pour l'instant.

Un autre concept important, associé au placage de texture est le « filtrage ». Nous avons discuté de comment associer les coordonnées de texture à un texel. La position du texel est toujours spécifiée en nombres entiers, mais qu'arrive-t-il lorsque les coordonnées de texture (qui sont, rappelons-le, des fractions entre 0 et 1) nous mappent un texel aux coordonnées (152.34, 745.14) ? La réponse triviale est de tronquer les valeurs pour obtenir (152, 745). Bien, cela fonctionne et produit des résultats adéquats, cependant, dans certains cas, cela n'est pas très joli. Une meilleure approche est de récupérer les texels en quadrilatères de deux sur deux ( (152, 745), (153, 745), (152, 746) et (153, 746) ), et de faire une interpolation linéaire entre leurs couleurs. Cette interpolation linéaire doit refléter la distance relative entre (152.34, 745.124) et chacun des autres texels. Plus la coordonnée est proche d'un texel, plus la participation de ce texel sur la couleur finale est importante, et plus elle est loin d'un texel, plus sa participation sera faible. Cela rend bien plus joli que l'approche initiale.

La méthode par laquelle la valeur du texel final est choisie est connue sous le nom de « filtrage ». La simple approche de troncation de la valeur est connue comme « filtrage au plus proche », et l'approche plus complexe que nous avons vue s'appelle « filtrage linéaire ». Un autre nom que vous pourrez rencontrer pour le filtre linéaire est « filtrage par point ». OpenGL supporte divers types de filtres, et vous avez la possibilité de choisir celui que vous voulez appliquer. Habituellement, les filtres qui fournissent les meilleurs résultats demandent plus de calculs au GPU, et peuvent donc avoir un effet sur la fréquence d'affichage. Le choix du type de filtrage est donc une question d'équilibrage, entre le résultat voulu et la capacité de la plate-forme cible.

Maintenant que nous comprenons le système de coordonnées de texture, il est temps d'étudier la manière dont le placage de texture est effectué avec OpenGL. L'application de texture signifie, dans OpenGL, la manipulation des connexions imbriquées de quatre concepts :

  • la texture ;
  • l'unité de texture ;
  • l'objet d'échantillonnage ;
  • la variable uniforme de l'échantillonneur dans le shader.

La texture contient les données de l'image elle-même (par exemple : les texels). La texture peut être de divers types (1D, 2D, etc.), avec diverses résolutions et le type de données sous-jacent peut être dans divers formats (RGB, RGBA, etc.). OpenGL fournit une manière de définir le point d'entrée des données en mémoire, ainsi que tous les attributs précédents et de charger ces données dans le GPU. Il y a aussi de multiples paramètres que nous pouvons contrôler, tels que le type de filtrage, etc. D'une manière très proche de celle utilisée pour les tampons de sommets, l'objet de texture est associé à un identifiant. Après la création de cet identifiant et le chargement des données de la texture ainsi que de ses paramètres, nous pouvons facilement échanger les textures, à la volée en liant les différents identifiants dans l'état OpenGL. Nous n'avons plus besoin de charger les données. À partir de maintenant, c'est au pilote OpenGL de s'assurer que les données sont chargées à temps sur le GPU, avant que le rendu ne commence.

La texture n'est pas liée directement dans le shader (où l'échantillonnage s'effectue réellement). À la place, elle est liée à une « unité de texture » dont l'index est donné au shader. Donc le shader accède à la texture au travers de l'unité de texture. Il y a généralement de multiples unités de texture disponibles, leur nombre exact dépendant des capacités de la carte graphique. Afin de lier une texture A à l'unité de texture 0 nous devons d'abord activer cette unité 0 puis lui lier la texture A. Nous pouvons maintenant activer l'unité de texture 1 pour lui lier une texture différente (ou la même d'ailleurs). L'unité de texture 0 reste liée à la texture A.

Il y a une petite astuce, dans le fait que chaque unité de texture peut être liée à plusieurs textures, tant que ces textures sont de types différents. C'est appelé la « cible » de la texture. Lorsque nous lions une texture à une unité de texture, nous spécifions la cible (1D, 2D, etc.). Nous pouvons donc avoir une texture A liée à la cible 1D alors que la texture B est liée à la cible 2D, et ce sur la même unité de texture.

L'opération d'échantillonnage a (généralement) lieu dans le fragment shader et il existe une fonction spéciale qui le fait. La fonction d'échantillonnage doit connaître l'unité de texture à laquelle elle doit accéder, car nous pouvons échantillonner plusieurs unités de texture dans un même fragment shader. Il y a un groupe de variables uniformes particulières pour cela, selon la cible de la texture : « sampler1d », « sampler2D », « sampler3D », « samplerCube », etc. Nous pouvons créer autant de variables uniformes d'échantillonnage que nous le souhaitons, et affecter à chacune la valeur d'une unité de texture, à partir de l'application. À chaque fois que nous appelons la fonction d'échantillonnage sur une variable uniforme d'échantillonnage, l'unité de texture correspondante (et la texture) sera utilisée.

Le dernier concept est l'objet d'échantillonnage, à ne pas confondre avec la variable uniforme d'échantillonnage ! Ce sont deux entités séparées. La texture contient aussi bien les données de la texture, que les paramètres qui configurent l'opération d'échantillonnage. Ces paramètres font partie de l'état d'échantillonnage. Cependant, nous pouvons créer un objet d'échantillonnage, le configurer avec un état d'échantillonnage, puis le lier à l'unité de texture. Lorsque nous faisons ainsi, l'objet d'échantillonnage remplace tous les états définis dans l'objet de texture. Pas d'inquiétude, pour l'instant nous n'utiliserons pas les objets d'échantillonnage, mais il est intéressant de savoir qu'ils existent.

Le digramme suivant résume les relations entre les différents concepts d'application de texture que nous venons d'apprendre :

Image non disponible

II. Explication du code

OpenGL sait comment charger les données d'une texture de différents formats, à partir d'une adresse mémoire, mais ne fournit aucun moyen de charger la texture en mémoire, à partir de fichiers d'images tels que PNG ou JPG. Nous allons utiliser une bibliothèque externe à cette fin. Il y a de nombreuses options pour cela et nous allons utiliser ImageMagick, une bibliothèque libre, qui supporte de nombreux types d'images et est portable sur divers systèmes d'exploitation.

La majeure partie de la gestion de texture est encapsulée dans la classe suivante :

ogldev_texture.h
Sélectionnez
27.
28.
29.
30.
31.
32.
33.
34.
35.
class Texture
{
public:
    Texture(GLenum TextureTarget, const std::string& FileName);

    bool Load();

    void Bind(GLenum TextureUnit);
};

Lors de la création d'un objet Texture, nous devons définir une cible (nous utilisons GL_TEXTURE_2D) et un nom de fichier. Après cela, nous appelons la fonction Load(). Elle peut échouer, par exemple, si le fichier n'existe pas ou si ImageMagick a rencontré une erreur quelconque. Lorsque nous voulons utiliser une instance spécifique de Texture, nous devons la lier à une des unités de texture.

ogldev_texture.cpp
Sélectionnez
31.
32.
33.
34.
35.
36.
37.
38.
try {
    m_pImage = new Magick::Image(m_fileName);
    m_pImage->write(&m_blob, "RGBA");
}
catch (Magick::Error& Error) {
    std::cout << "Error loading texture '" << m_fileName << "': " << Error.what() << std::endl;
    return false;
}

Voici comment nous utilisons ImageMagick pour charger la texture depuis une image, puis la préparons en mémoire pour être chargée avec OpenGL. Nous commençons par charger un membre de classe dont le type est Magick::Image, en utilisant le nom de fichier de la texture. Cet appel charge la texture dans une représentation mémoire interne à ImageMagick et inutilisable directement par OpenGL. Ensuite nous écrivons l'image dans un objet de type Magick::Blob, en utilisant le format RVBA (rouge, vert, bleu et alpha). Le BLOB (« Binary Large Object ») est un mécanisme utile pour stocker l'image encodée en mémoire, de manière à pouvoir l'utiliser dans des programmes externes. S'il y a une erreur, une exception sera lancée, donc nous devons nous y préparer.

ogldev_texture.cpp
Sélectionnez
40.
glGenTextures(1, &m_textureObj);

Cette fonction OpenGL est similaire à glGenBuffers()Gluint avec laquelle nous sommes déjà familiers. Elle génère le nombre demandé de textures et place leur identifiant dans le pointeur sur tableau de Gluint donné (le second paramètre). Dans notre cas, nous avons juste besoin d'une texture.

ogldev_texture.cpp
Sélectionnez
41.
glBindTexture(m_textureTarget, m_textureObj);

Nous allons faire plusieurs appels relatifs aux textures et de manière similaire à ce que nous faisions déjà avec les tampons de sommets, nous devons dire à OpenGL sur quel objet il opère. C'est le but de la fonction glBindTexture()GL_TEXTURE_1DGLTEXTURE_2DGL_TEXTURE_2D. Elle dit à OpenGL quel est l'objet auquel nous nous référons dans tous les appels relatifs aux textures, jusqu'à ce qu'une nouvelle texture soit liée. En plus de l'identifiant (second paramètre), nous spécifions aussi la cible de la texture, qui peut valoir GL_TEXTURE_1D, GLTEXTURE_2D, etc. Il peut y avoir une texture différente liée à chacune des cibles simultanément. Dans notre implémentation, la cible est donnée en tant que paramètre du constructeur (et pour l'instant nous utilisons GL_TEXTURE_2D).

ogldev_texture.cpp
Sélectionnez
42.
glTexImage2D(m_textureTarget, 0, GL_RGBA, m_pImage->columns(), m_pImage->rows(), 0, GL_RGBA, GL_UNSIGNED_BYTE, m_blob.data());

Cette fonction, assez complexe, est utilisée afin de charger la partie principale de la texture, c'est-à-dire les données elles-mêmes. Il y a plusieurs fonctions glTexImage* disponibles, chacune couvrant quelques types de cibles de texture. La cible de la texture est toujours le premier paramètre. Le second paramètre est le LOD ou « Level Of Detail ». Une texture peut contenir la même image dans des résolutions différentes, un concept connu sous le nom de « mip-mapping ». Chaque « mip-map » possède un index de LOD différent, en partant de 0 jusqu'à la résolution la plus haute, croissant alors que la résolution décroit. Pour l'instant nous avons un seul « mip-map » donc nous donnons zéro.

Le paramètre suivant est le format interne dans lequel OpenGL stocke la texture. Par exemple, nous donner une texture avec le panel de couleurs complet (rouge, vert, bleu et alpha), mais si nous spécifions GL_RED, nous aurons une texture avec juste le canal rouge, ce qui apparaîtra… rouge (essayez !). Nous utilisons GL_RGBA pour récupérer toutes les couleurs de la texture correctement. Les deux paramètres suivants sont la largeur et la hauteur de la texture, en texels. La bibliothèque ImageMagick stocke gentiment ces informations pour nous lorsqu'elle charge l'image, nous pouvons les récupérer en utilisant les fonctions Image::columns()/rows(). Le cinquième paramètre est la bordure, que nous laissons à zéro pour le moment.

Les trois derniers paramètres spécifient la source des données en entrée. Les paramètres sont le format, le type et l'adresse mémoire. Le format nous indique le nombre de canaux et doit correspondre au BLOB que nous avons en mémoire. Le type décrit le type de données que nous avons par canal. OpenGL supporte de nombreux types de données, mais le BLOB ImageMagick définit un octet par canal, donc nous utilisons GL_UNSIGNED_BYTE. Enfin vient l'adresse mémoire des données que nous extrayons depuis le BLOB, en utilisant la fonction Blob::data().

ogldev_texture.cpp
Sélectionnez
43.
44.
glTexParameterf(m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

La fonction générale glTexParameterf() contrôle de nombreux aspects de l'opération d'échantillonnage. Ces aspects font partie de l'état d'échantillonnage de la texture. Ici nous spécifions le filtre à utiliser pour le grossissement et le rétrécissement. Chaque texture possède une largeur et une hauteur, mais celles-ci sont rarement appliquées à un triangle dans les mêmes proportions. Dans la plupart des cas le triangle est soit plus petit soit plus grand que la texture. Dans ces cas le type de filtre détermine comment gérer le cas du grossissement ou du rétrécissement de la texture pour qu'elle corresponde aux proportions du triangle. Quand le triangle rastérisé est plus grand que la texture (par exemple : très proche de la caméra), nous pouvons avoir plusieurs pixels couverts par le même texel (grossissement). Quand il est plus petit (par exemple : très loin de la caméra), plusieurs texels peuvent être couverts par le même pixel (rétrécissement). Ici nous choisissons le filtre d'interpolation linéaire pour les deux cas. Comme nous l'avons vu précédemment, l'interpolation linéaire fournit des résultats ayant un bon rendu, en mélangeant les couleurs d'un carré de 2x2 texels, basé sur la proximité du texel actuel (calculé en multipliant les coordonnées de texture par les dimensions de la texture).

ogldev_texture.cpp
Sélectionnez
49.
50.
51.
52.
53.
void Texture::Bind(GLenum TextureUnit)
{
   glActiveTexture(TextureUnit);
   glBindTexture(m_textureTarget, m_textureObj);
}

Comme notre application 3D devient plus complexe, nous pouvons utiliser plusieurs textures différentes dans divers appels de dessin dans la boucle de rendu. Avant chaque appel à la fonction de dessin, nous devons lier la texture que nous voulons à une des unités de texture afin qu'elle soit échantillonnée dans le fragment shader. Cette fonction prend l'index de l'unité de texture (GL_TEXTURE0, GL_TEXTURE1, etc.) comme paramètre. Elle l'active en utilisant la fonction glActiveTexture()Texture::Bind() puis lui lie la texture. Cette texture restera liée à cette unité de texture jusqu'au prochain appel de Texture::Bind() avec la même unité de texture.

shader.vs
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
#version 330

layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;

uniform mat4 gWVP;

out vec2 TexCoord0;

void main()
{
   gl_Position = gWVP * vec4(Position, 1.0);
   TexCoord0 = TexCoord;
};

Ceci est le code du vertex shader mis à jour. Il y a un paramètre additionnel, nommé TexCoord qui est un vecteur 2D. Au lieu de définir la couleur de sortie, ce shader transfère les coordonnées de texture vers le fragment shader, sans y toucher. La rastérisationva effectuer une interpolation des coordonnées de texture à travers la face du triangle et chaque fragment shader sera appelé avec ses propres coordonnées de texture.

shader.fs
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
in vec2 TexCoord0;

out vec4 FragColor;

uniform sampler2D gSampler;

void main()
{
    FragColor = texture2D(gSampler, TexCoord0.st);
};

Et voici le code du fragment shader mis à jour. Il possède une variable en entrée appelée TexCoord0 qui contient les coordonnées de texture interpolées, que nous récupérons depuis le vertex shader. On trouve une variable uniforme nommée gSampler qui a comme type sampler2D. L'application doit définir comme valeur pour cette variable l'index de l'unité de texture, afin que le fragment shader soit capable d'accéder à la texture. La fonction principale fait une seule chose : elle utilise la fonction interne texture2D pour échantillonner la texture. Le premier paramètre est la variable uniforme de l'échantillonneur et le second paramètre reçoit les coordonnées de texture. La valeur retournée est le texel échantillonné (qui dans notre cas contient une couleur), après avoir été filtré. C'est la couleur finale du pixel dans ce tutoriel. Dans les prochains tutoriels, nous verrons que l'éclairage change simplement cette couleur, en se basant sur les paramètres des sources lumineuses.

Télécharger Sélectionnez
128.
129.
130.
131.
132.
Vertex Vertices[4] = {
    Vertex(Vector3f(-1.0f, -1.0f, 0.5773f), Vector2f(0.0f, 0.0f)),
    Vertex(Vector3f(0.0f, -1.0f, -1.15475), Vector2f(0.5f, 0.0f)),
    Vertex(Vector3f(1.0f, -1.0f, 0.5773f), Vector2f(1.0f, 0.0f)),
    Vertex(Vector3f(0.0f, 1.0f, 0.0f), Vector2f(0.5f, 1.0f)) };

Jusqu'à ce tutoriel, notre tampon de sommets était une simple suite de Vector3f contenant les positions. Maintenant nous avons la structure Vertex qui contient aussi les coordonnées de texture en tant que Vector2f.

tutorial16.cpp
Sélectionnez
80.
81.
82.
83.
84.
85.
86.
87.
88.
...
glEnableVertexAttribArray(1);
...
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)12);
...
pTexture->Bind(GL_TEXTURE0);
...
glDisableVertexAttribArray(1);

Voici les changements et ajouts effectués au niveau de la boucle de rendu. Nous commençons par activer l'attribut de sommet 1 pour les coordonnées de texture, en plus de l'attribut 0 qui est déjà activé pour la position. Cela correspond à la disposition des déclarations dans le vertex shader. Ensuite nous appelons glVertexAttribPointerVertexsizeof( Vector3f )VertexVertexGlvoid* pour définir la position des coordonnées de texture dans le tampon de sommets. Une coordonnée de texture est composée de deux valeurs en nombre flottant, ce qui correspond aux deuxième et troisième paramètres. Regardons le cinquième paramètre. Il s'agit de la taille de la structure Vertex et il est spécifié pour la position et les coordonnées de texture. Ce paramètre est connu sous le nom de « pas » et dit à OpenGL le nombre d'octets entre le début d'un attribut d'un sommet et le début du même attribut dans le sommet suivant. Dans notre cas, le tampon contient : position0, texture0, position1, texture1, etc. Dans les tutoriels précédents, nous avions uniquement la position dans le tampon, nous pouvions donc sans soucis mettre ce paramètre à zéro ou sizeof( Vector3f ). Maintenant que nous avons plus d'un attribut, le pas peut uniquement valoir le nombre d'octets de la structure Vertex. Le dernier paramètre est le décalage en octets, depuis le début de la structure Vertex, jusqu'aux attributs de texture. Nous devons faire la conversion en Glvoid*, car c'est ainsi que la fonction attend de recevoir ce décalage.

Avant l'appel à la fonction de dessin, nous devons lier la texture que nous voulons utiliser à une unité de texture. Nous avons une seule texture donc n'importe quelle unité suffirait. Nous avons seulement besoin de nous assurer que la même unité de texture est définie dans le shader (voir plus bas). Après l'appel à la fonction de dessin, nous désactivons l'attribut.

tutorial16.cpp
Sélectionnez
253.
254.
255.
glFrontFace(GL_CW);
glCullFace(GL_BACK);
glEnable(GL_CULL_FACE);

Ces appels OpenGL ne sont pas vraiment relatifs à l'application de texture, je les ai juste ajoutés afin que cela rende plus joli (essayez de les désactiver…). Ils activent l'élimination des faces cachées, une optimisation commune, utilisée afin d'ignorer des triangles avant le lourd procédé de rastérisation. La motivation ici est qu'il y a souvent 50 % de la surface d'un objet qui nous est cachée (le dos d'une personne, maison, voiture, etc.). La fonction glFrontFace() () dit à OpenGL, que les sommets du tampon de sommets sont ordonnés dans le sens des aiguilles d'une montre. C'est-à-dire, lorsque nous regardons la face avant du triangle, nous trouvons les sommets dans le tampon rangés dans le sens des aiguilles d'une montre. La fonction glCullFace() dit au GPU d'ignorer les triangles à l'arrière du triangle. Cela signifie que l'intérieur d'un objet n'a pas besoin d'être rendu, seulement l'extérieur. Enfin l'élimination des faces arrière en elle-même est activée (par défaut elle est désactivée). Notons que, dans ce tutoriel, j'ai inversé l'ordre des index dans le triangle du bas. La manière dont c'était fait auparavant faisait apparaître le triangle comme s'il était tourné vers l'intérieur de la pyramide (ligne 170 de tutorial16.cpp).

tutorial16.cpp
Sélectionnez
264.
glUniform1i(gSampler, 0);

Ici nous définissons l'index de l'unité de texture que nous allons utiliser, dans la variable uniforme d'échantillonnage du shader. gSampler est une variable uniforme dont la valeur a été récupérée auparavant, en utilisant la fonction glGetUniformLocation()GL_TEXTURE0. Il est important de noter que l'index effectif de l'unité de texture est utilisé ici et pas l'énumération GL_TEXTURE0 (qui a une valeur différente).

tutorial16.cpp
Sélectionnez
264.
265.
266.
267.
268.
pTexture = new Texture(GL_TEXTURE_2D, "test.png");

if (!pTexture->Load()) {
    return 1;
}

Ici nous créons l'objet Texture et le chargeons. « test.png » est inclus avec les sources de ce tutoriel, mais ImageMagick devrait être capable de gérer presque n'importe quel fichier que nous y mettrions.

III. Exercice

Si vous exécutez le code fourni avec ce tutoriel, vous remarquerez que les faces de la pyramide ne sont pas identiques. Essayez de comprendre ce qu'il se passe et ce qui devrait être modifié pour les rendre identiques.

IV. Sources

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources


Tutoriel 17 - Éclairage ambiant

  • I. Contexte
  • II. Explication du code
  • III. Sources
  • IV. Remerciements
  • V. Navigation

Ce tutoriel est le premier de la série sur l'éclairage.

I. Contexte

L'éclairage est un des sujets les plus importants dans le domaine de la 3D. Le modéliser correctement ajoute beaucoup à l'aspect visuel de la scène rendue. La raison du mot « modéliser » vient du fait que nous ne pouvons pas simuler exactement ce que fait la nature. La lumière réelle est faite d'une énorme quantité de particules, appelées « photons », qui agissent à la fois comme des ondes et des particules (la dualité onde-particule de la lumière). Si nous essayons de calculer l'effet de chaque photon dans notre programme, nous allons très rapidement être à court de puissance de calcul.

Par conséquent divers modèles d'éclairage ont été développés au fil des années pour reproduire l'effet principal de la lumière sur les objets qu'elle atteint, en les rendant visibles. Ces modèles d'éclairage sont devenus de plus en plus complexes, au fur et à mesure de la progression des connaissances en rendu 3D et de l'augmentation de la puissance de calcul. Pendant l'étude des quelques tutoriels suivants, nous verrons le modèle d'éclairage de base, plus simple à implémenter, mais qui contribue énormément à l'atmosphère globale d'une scène.

Le modèle d'éclairage de base est appelé « Ambiante/Diffuse/Spéculaire ». La lumière ambiante est le type de lumière que l'on voit lorsque nous allons dehors, en un jour de soleil habituel. Même si le soleil traverse le ciel et que les rayons lumineux qu'il projette arrivent à différents angles, au cours de la journée, la plupart des choses seront visibles, même dans l'ombre. Comme la lumière rebondit sur chaque chose touchée, elle finit par tout toucher, ainsi les objets qui ne sont pas directement face au soleil finissent par être éclairés. Même une ampoule dans une pièce agit comme le soleil en ce sens et répand une lumière ambiante, car si la pièce n'est pas trop grande, tout est éclairé uniformément. La lumière ambiante est modélisée comme une lumière sans origine, sans direction et qui a un effet identique sur tous les objets de la scène.

La lumière diffuse accentue le fait que l'angle dans lequel la lumière touche la surface affecte l'intensité de l'éclairage de l'objet. Quand la lumière atteint un objet sur un côté, ce côté est plus clair que l'autre (celui qui n'est pas directement en face de la lumière). Nous venons de voir que le soleil répand une lumière ambiante qui n'a pas de direction spécifique. Cependant, la lumière du soleil a aussi des propriétés de diffusion. Quand elle touche un bâtiment, nous pouvons généralement observer qu'un côté du bâtiment est plus éclairé qu'un autre. La propriété la plus importante de la lumière diffuse est sa direction.

La lumière spéculaire est plus une propriété de l'objet, que de la lumière elle-même. C'est ce qui fait que des portions d'objets brillent, lorsque la lumière les atteint dans un angle spécifique et que l'on regarde depuis un point spécifique. Les objets métalliques ont souvent une propriété spéculaire. Par exemple, une voiture pendant une journée ensoleillée brille parfois au niveau de ses arêtes. Pour calculer la lumière spéculaire, nous devons prendre en considération la direction d'arrivée de la lumière (et de son rebond), ainsi que la position de l'observateur.

Dans les applications 3D, nous ne créons généralement pas directement de lumière ambiante, diffuse ou spéculaire. Nous utilisons plutôt des sources lumineuses tels le soleil (en extérieur), une ampoule (à l'intérieur) ou une lampe de poche (dans une grotte). Ces sources lumineuses peuvent avoir différentes combinaisons d'intensités ambiante, diffuse et spéculaire, ainsi que d'autres propriétés spécifiques. Par exemple la lampe de poche a un cône lumineux et les objets qui en sont éloignés ne sont pas éclairés du tout.

Dans les tutoriels suivants, nous développerons quelques types de sources lumineuses très utiles et étudierons le modèle d'éclairage de base, tout en progressant.

Nous allons commencer par une source lumineuse appelée « lumière directionnelle ». Une lumière directionnelle a une direction, mais pas d'origine spécifique. Cela signifie que tous les rayons lumineux sont parallèles les uns aux autres. La direction d'une lumière est définie par un vecteur et ce vecteur est utilisé pour calculer la lumière sur tous les objets de la scène, quelle que soit leur position. Le soleil rentre particulièrement bien dans la catégorie des lumières directionnelles. Si nous essayons de calculer l'angle précis avec lequel la lumière du soleil atteint deux bâtiments mitoyens, nous retrouverons deux valeurs presque identiques (la différence entre elles sera une fraction minuscule). C'est parce que le soleil est situé à quelque 150 millions de kilomètres de distance. Par conséquent, nous ne nous attardons pas sur sa position et prenons uniquement la direction en compte.

Une autre propriété importante d'une lumière directionnelle est que l'éclairage reste le même, quelle que soit la distance de l'objet éclairé. C'est une différence avec une autre source lumineuse que nous étudierons dans les tutoriels à venir, la lumière ponctuelle, dont l'éclairage est de plus en plus faible avec l'éloignement (par exemple : une ampoule).

L'image suivante illustre une lumière directionnelle :

Image non disponible

Nous venons de voir que la lumière du soleil possède des propriétés ambiantes ainsi que de diffusion. Nous allons développer la partie ambiante ici et la partie diffusion dans le tutoriel suivant.

Dans le tutoriel précédent, nous avons appris comment échantillonner la couleur d'un pixel à partir d'une texture. La couleur possède trois canaux (rouge, vert et bleu) et chaque canal est représenté par un octet. Cela signifie que la valeur d'un canal peut aller de 0 à 255. Différentes combinaisons de canaux représentent donc différentes couleurs. Quand tous les canaux sont à zéro, la couleur est noire. Quand ils sont tous à 255, la couleur est blanche. Toutes les autres couleurs se situent entre les deux. En multipliant tous les canaux par la même valeur, nous obtenons la même couleur de base, mais pouvons la rendre plus claire ou plus sombre (en fonction de la valeur).

Lorsque la lumière blanche atteint une surface, la couleur reflétée est simplement la couleur de la surface. Elle peut être plus claire ou plus sombre, en fonction de la puissance de la source lumineuse, mais cela reste la même couleur de base. Si la source lumineuse est rouge pur (255, 0, 0), la couleur reflétée ne pourra être qu'une sorte de rouge. C'est parce que la lumière n'a pas de canaux vert et bleu qui pourraient être reflétés par la surface. Si la surface est bleu pur, le résultat sera donc noir. La conclusion est que la lumière peut uniquement exposer la couleur d'un objet, pas le « peindre ».

Nous allons définir la couleur des sources lumineuses comme un trio de nombres flottants dans l'intervalle [0,1]. En multipliant la couleur de la lumière par la couleur d'un objet, nous obtiendrons la couleur reflétée. Cependant, nous souhaitons aussi prendre en compte l'intensité ambiante de la lumière. Par conséquent, l'intensité ambiante sera définie par un simple nombre flottant dans l'intervalle [0,1], qui sera lui aussi multiplié par tous les canaux de la lumière reflétée que nous venons de calculer. Ce sera la couleur finale. L'équation suivante résume le calcul de la lumière ambiante :

⎛⎝⎜RpGpBp⎞⎠⎟=couleur pixel(RpGpBp)=couleur pixel
⎛⎝⎜RlGlBl⎞⎠⎟=couleur lumière(RlGlBl)=couleur lumi è re
A=intensité ambiante A=intensit é ambiante
⎛⎝⎜RpGpBp⎞⎠⎟×⎛⎝⎜RlGlBl⎞⎠⎟×⎛⎝⎜AAA⎞⎠⎟=⎛⎝⎜RpRlAGpGlABpBlA⎞⎠⎟=couleur finale du pixel(RpGpBp)×(RlGlBl)×(AAA)=(RpRl AGpGl ABpBl A)=couleur finale du pixel

Dans le code d'exemple de ce tutoriel, nous pourrons jouer avec les touches « a » et « s », pour augmenter ou réduire l'intensité de la lumière ambiante et voir l'effet que ça a sur la pyramide texturée du tutoriel précédent. Comme ce n'est que la partie ambiante de la lumière directionnelle, la direction elle-même n'a pas encore été impliquée. Cela changera dans le prochain tutoriel où nous étudierons la lumière diffuse. Pour l'instant nous allons voir que la pyramide est éclairée de la même manière, quelle que soit la manière dont on la regarde.

La lumière ambiante est considérée par beaucoup comme quelque chose à éviter autant que possible. C'est parce qu'elle semble quelque peu artificielle et la simplicité de son implémentation ne contribue pas vraiment au réalisme de la scène. En utilisant des méthodes avancées, telles que l'illumination globale, on peut éliminer le besoin de lumière ambiante, car la lumière qui est reflétée sur des objets et qui atteint d'autres objets peut aussi être prise en compte. Comme nous ne sommes pas encore là, nous aurons généralement besoin d'un peu de lumière ambiante, pour éviter les cas où un côté d'un objet est éclairé et l'autre côté est dans le noir complet. Pour simuler une lumière de fin de journée, il faut beaucoup jouer avec les paramètres et beaucoup de travail d'affinage.

II. Explication du code

Nos exemples de code vont devenir de plus en plus complexes avec le temps et cette tendance va s'accentuer. Dans ce tutoriel, en plus d'implémenter l'éclairage ambiant, nous allons aussi restructurer le code. Cela va placer le code dans une meilleure forme pour les tutoriels à venir. Les changements majeurs sont :

  1. encapsulation de la gestion des shaders dans la classe Technique. Cela inclut les actions telles que la compilation et la liaison. À partir de maintenant, nous implémenterons nos effets visuels dans des classes dérivées de la classe Technique ;
  2. déplacement de l'initialisation de GLUT et de la gestion des callbacks, dans le composant GLUTBackend. Ce composant s'enregistre lui-même pour recevoir les appels aux callbacks GLUT et les transfère à l'application, en utilisant une interface C++ appelée ICallbacks ;
  3. déplacements des fonctions et variables globales, depuis le ficher cpp principal vers une classe, qui peut être considérée comme « l'application ». Dans le futur, nous étendrons ceci à une classe de base pour toutes les applications et fournirons les fonctionnalités communes à toutes. Cette approche est très populaire dans de nombreux moteurs de jeux et frameworks.

La majorité du code de ce tutoriel (excepté le code spécifique à la lumière) n'est pas neuf, mais a juste été réorganisé conformément aux principes du plan décrit ci-dessus. Par conséquent, seuls les nouveaux fichiers d'en-têtes sont revus.

Télécharger Sélectionnez
24.
25.
26.
void GLUTBackendInit(int argc, char** argv);

bool GLUTBackendCreateWindow(unsigned int Width, unsigned int Height, unsigned int bpp, bool isFullScreen, const char* pTitle);

Beaucoup de code spécifique à GLUT a été déplacé vers le composant « GLUT backend », qui rend plus faciles l'initialisation de GLUT et la création d'une fenêtre en utilisant les fonctions ci-dessus.

Télécharger Sélectionnez
28.
void GLUTBackendRun(ICallbacks* pCallbacks);

Après que GLUT a été initialisé et qu'une fenêtre a été créée, l'étape suivante est d'exécuter la boucle principale de GLUT, dans la fonction suivante. L'ajout ici est l'utilisation de l'interface ICallbacks, qui aide à l'enregistrement des fonctions de callback GLUT. Au lieu de demander à chaque application d'enregistrer ses callbacks de son côté, le composant « GLUT backend » enregistre ses propres fonctions privées et transmet les événements à l'objet spécifié dans l'appel à la fonction ci-dessus. La classe principale de l'application va souvent implémenter de son côté cette interface et se passer elle-même en tant que paramètre de l'appel à GLUTBackendRun. Cette approche a aussi été choisie pour ce tutoriel.

Télécharger Sélectionnez
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
class Technique
{
public:

   Technique();

   ~Technique();

   virtual bool Init();

   void Enable();

protected:

   bool AddShader(GLenum ShaderType, const char* pShaderText);

   bool Finalize();

   GLint GetUniformLocation(const char* pUniformName);

private:

   GLuint m_shaderProg;

   typedef std::list<GLuint> ShaderObjList;
   ShaderObjList m_shaderObjList;
};

Dans les tutoriels précédents, toute la corvée de compiler et lier les shaders était le lot de l'application. La classe Technique aide en intégrant toutes les fonctionnalités communes et en permettant aux classes dérivées de se concentrer sur le cœur de l'effet (la technique).

Chaque technique doit d'abord être initialisée en appelant la fonction Init(). Une technique dérivée doit appeler Init() de la classe de base (qui crée l'objet du programme OpenGL) et peut ajouter ses initialisations privées ici.

Après qu'un objet Technique a été créé et initialisé, la séquence habituelle pour la classe de technique dérivée est d'appeler la fonction protégée AddShader() sur autant de shaders GLSL (fournis dans une chaîne de caractères) que nécessaire. Enfin, Finalize() est appelée pour lier les objets. La fonction Enable() ne fait pour l'instant qu'appeler glUseProgram() () et doit donc être appelée à chaque fois que l'on change de technique et que l'on appelle la fonction de dessin.

Cette classe garde les objets intermédiaires compilés et les détruit après la liaison, en utilisant glDeleteShader(). Cela aide à réduire le nombre de ressources consommées par notre application. Pour de meilleures performances, les applications OpenGL compilent souvent tous les shaders pendant le chargement et non pendant l'exécution. En supprimant les objets directement après la liaison, nous aidons à garder basse la consommation de ressources OpenGL par notre application. L'objet du programme lui-même est supprimé dans le destructeur, en utilisant glDeleteProgram().

Télécharger Sélectionnez
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
class Tutorial17 : public ICallbacks
{
public:

    Tutorial17()
    {
        ...
    }

    ~Tutorial17()
    {
        ...
    }

    bool Init()
    {
        ...
    }

    void Run()
    {
       GLUTBackendRun(this);
    }

    virtual void RenderSceneCB()
    {
        ...
    }

    virtual void IdleCB()
    {
        ...
    }

    virtual void SpecialKeyboardCB(int Key, int x, int y)
    {
        ...
    }

    virtual void KeyboardCB(unsigned char Key, int x, int y)
    {
        ...
    }

    virtual void PassiveMouseCB(int x, int y)
    {
        ...
    }

private:

    void CreateVertexBuffer()
    {
        ...
    }
    void CreateIndexBuffer()
    {
        ...
    }

    GLuint m_VBO;
    GLuint m_IBO;
    LightingTechnique* m_pEffect;
    Texture* m_pTexture;
    Camera* m_pGameCamera;
    float m_scale;
    DirectionalLight m_directionalLight;
};

C'est un squelette de la classe de l'application principale, qui encapsule le code restant qui nous est familier. Init() prend en charge la création de l'effet, le chargement de la texture et la création des tampons d'indices et de sommets. Run() appelle GLUTBackendRun() et passe l'instance elle-même en paramètre. Comme cette classe implémente l'interface ICallbacks, tous les événements GLUT sont traités dans les bonnes méthodes de la classe. De plus, toutes les variables, qui étaient préalablement dans la section globale du fichier, sont maintenant des attributs privés de la classe.

Télécharger Sélectionnez
25.
26.
27.
28.
29.
struct DirectionalLight
{
    Vector3f Color;
    float AmbientIntensity;
};

C'est le début de la définition de la lumière directionnelle. Pour l'instant, seule la partie ambiante existe et la direction est absente. Nous ajouterons la direction dans le tutoriel suivant, lorsque nous traiterons la lumière diffuse. La structure contient deux champs : une couleur et l'intensité ambiante. La couleur détermine quels canaux de couleur des objets seront reflétés et en quelle intensité. Par exemple, si la couleur vaut (1.0, 0.5, 0.0), alors le canal rouge de l'objet sera complètement reflété, le canal vert sera reflété à moitié et le canal bleu ne sera pas reflété du tout. C'est parce qu'un objet ne peut que refléter la lumière qui l'atteint (les sources lumineuses sont différentes, elles émettent la lumière et doivent être gérées séparément). Dans le cas du soleil, la couleur habituellement choisie est le blanc pur (1.0, 1.0, 1.0).

AmbientIntensity définit à quel point la lumière est sombre ou claire. Nous pouvons avoir une lumière blanche dont l'intensité est 1.0, donc l'objet est complètement éclairé, ou une intensité de 0.1 qui signifie que l'objet sera visible, mais apparaîtra très sombre.

Télécharger Sélectionnez
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
class LightingTechnique : public Technique
{
public:

    LightingTechnique();

    virtual bool Init();

    void SetWVP(const Matrix4f& WVP);
    void SetTextureUnit(unsigned int TextureUnit);
    void SetDirectionalLight(const DirectionalLight& Light);

private:

    GLuint m_WVPLocation;
    GLuint m_samplerLocation;
    GLuint m_dirLightColorLocation;
    GLuint m_dirLightAmbientIntensityLocation;
};

Voici le premier exemple de l'utilisation de la classe Technique. LightingTechnique est une classe dérivée qui implémente la lumière, en utilisant les fonctionnalités communes de compilation et liaison fournies par la classe de base. La fonction Init() doit être appelée après que l'objet a été créé. Il appelle simplement Technique::AddShader() et Technique::Finalize() pour générer le programme GLSL.

Télécharger Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
#version 330

in vec2 TexCoord0;

out vec4 FragColor;

struct DirectionalLight
{
    vec3 Color;
    float AmbientIntensity;
};

uniform DirectionalLight gDirectionalLight;
uniform sampler2D gSampler;

void main()
{
    FragColor = texture2D(gSampler, TexCoord0.xy) *
            vec4(gDirectionalLight.Color, 1.0f) *
            gDirectionalLight.AmbientIntensity;
}

Le vertex shader reste inchangé dans ce tutoriel. Il continue de passer la position (après l'avoir multipliée par la matrice WVP) et les coordonnées de texture. Toute la nouvelle logique se situe dans le fragment shader. L'ajout ici est l'utilisation du mot-clef « struct » afin de définir la lumière directionnelle. Comme nous pouvons le voir, ce mot-clef est utilisé pratiquement de la même manière qu'en C/C++. La structure est identique à celle que nous avons dans le code de l'application et nous devons la garder ainsi afin que l'application et le shader puissent communiquer.

Il y a maintenant une nouvelle variable uniforme de type DirectionalLight, dont l'application a besoin pour la mise à jour. Cette variable est utilisée dans le calcul de la couleur finale du pixel. Comme avant, nous échantillonnons la texture pour récupérer la couleur de base. Nous la multiplions ensuite, par la couleur et l'intensité ambiante, selon la formule ci-dessus. Cela termine le calcul de la lumière ambiante.

Télécharger Sélectionnez
44.
45.
46.
47.
m_WVPLocation = GetUniformLocation("gWVP");
m_samplerLocation = GetUniformLocation("gSampler");
m_dirLightColorLocation = GetUniformLocation("gDirectionalLight.Color");
m_dirLightAmbientIntensityLocation = GetUniformLocation("gDirectionalLight.AmbientIntensity");

Afin d'accéder à la variable uniforme DirectionalLight à partir de l'application, nous devons récupérer la position de ses deux champs, indépendamment. La classe LightingTechnique garde quatre Gluint pour les positions des variables uniformes dans le vertex shader et le fragment shader. La matrice WVP et la position de l'échantillonneur sont récupérées d'une manière familière. La couleur et l'intensité ambiante sont récupérées de la manière que nous voyons : en spécifiant le nom de la variable uniforme, suivi d'un point puis du nom du champ dans la structure elle-même. La définition de la valeur de ces variables est faite de la même manière que pour n'importe quelle autre variable. La classe LightingTechnique fournit deux méthodes pour définir la lumière directionnelle et la matrice WVP. La classe Tutorial17 les appelle avant chaque dessin, pour mettre les valeurs à jour.

Ce tutoriel nous permet de jouer avec l'intensité ambiante, en utilisant les touches « a » et « s », qui l'augmentent et la réduisent, respectivement. Regardez la fonction KeyboardCB() dans la classe Tutorial17 pour voir comment c'est fait.

III. Sources

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources


Tutoriel 18 - Lumière diffuse

  • I. Contexte
  • II. Explication du code
  • III. Point mathématique
  • IV. Sources
  • V. Remerciements
  • Navigation

Ce tutoriel est le second de la série sur l'éclairage.

I. Contexte

La principale différence entre la lumière ambiante et la lumière diffuse réside dans le fait que la lumière diffuse dépend de la direction des rayons de lumière, tandis que la lumière ambiante l'ignore complètement. Quand seule la lumière ambiante est présente, la scène entière est uniformément éclairée. La lumière diffuse fait que les parties des objets qui font face à la lumière sont plus claires que les parties qui lui tournent le dos.

La lumière diffuse ajoute aussi une astuce qui fait que l'angle dans lequel la lumière atteint la surface détermine la clarté de cette surface. Ce concept est illustré dans l'image suivante :

Image non disponible

Supposons que la force des deux rayons est identique et que leur seule différence est leur direction. Le modèle de lumière diffuse dit que la surface à gauche sera plus claire que la surface à droite, car la surface à droite est atteinte avec un angle plus aigu que la surface de gauche. En fait, la surface de gauche sera la plus claire possible, car la lumière l'atteint avec un angle de 90 degrés.

Le modèle de lumière diffuse est actuellement basé sur la « Lambert's cosine law », qui dit que l'intensité de la lumière reflétée sur une surface est directement proportionnelle au cosinus de l'angle entre la direction de l'observateur et la normale à la surface. Notons que nous avons un peu changé cela, en utilisant la direction de la source lumineuse au lieu de la direction de l'observateur (que nous utiliserons dans la lumière spéculaire).

Pour calculer l'intensité de la lumière dans le modèle de lumière diffuse, nous allons simplement utiliser le cosinus de l'angle entre la lumière et la normale à la surface (alors que la loi de Lambert se réfère au concept plus général de « directement proportionnelle »). Considérons l'image suivante :

Image non disponible

Nous voyons quatre rayons lumineux qui atteignent la surface avec des angles différents. La normale à la surface est la flèche verte qui pointe hors de la surface. Le rayon de lumière A est celui avec la plus grande force. L'angle entre A et la normale est zéro et le cosinus de zéro vaut un. Cela signifie qu'après la multiplication de l'intensité de la lumière (les trois canaux de zéro à un) par la couleur de la surface, nous multiplions le résultat par un. Nous ne pouvons pas avoir plus que cela avec la lumière diffuse. Le rayon B atteint la surface avec un angle entre 0 et 90. Cela signifie que l'angle entre B et la normale est lui aussi entre 0 et 90, et le cosinus de cet angle vaut entre zéro et un. Nous allons pondérer le résultat de la multiplication du dessus par le cosinus de cet angle, ce qui signifie que l'intensité de la lumière sera définitivement plus faible que celle du rayon A.

Les choses deviennent différentes avec les rayons C et D. C atteint la surface directement depuis le côté, avec un angle de zéro. L'angle entre C et la normale est exactement de 90 degrés, dont le cosinus vaut zéro. Le résultat est donc que C n'a aucun effet sur l'éclairage de la surface. L'angle entre D et la normale est obtus, ce qui signifie que son cosinus a une valeur négative. Le résultat final est le même que pour C : aucun effet sur l'éclairage de la surface.

À partir de cette discussion, on peut tirer une conclusion importante : afin d'avoir un effet sur la clarté de la surface, la lumière doit atteindre la surface de sorte que l'angle entre elle et la normale à la surface soit plus grand que zéro et strictement inférieur à 90 degrés.

Nous voyons que la normale à la surface joue un rôle important dans le calcul de la lumière diffuse. Les exemples ci-dessus étaient très simples : la surface était une simple ligne et il n'y avait qu'une seule normale à considérer. Dans le monde réel, nous avons des objets qui sont composés de multiples polygones et la normale à chaque polygone est différente de celle du polygone à côté. Voici un exemple :

Image non disponible

Comme la normale est la même tout le long de la surface du polygone, elle est suffisante pour calculer la lumière diffuse dans le vertex shader. Les trois sommets du triangle auraient la même couleur, qui serait celle du triangle complet. Cependant, cela ne serait pas très joli. Nous aurons un ensemble de polygones où chacun aura une couleur particulière, légèrement différente de celle du polygone d'à côté, et nous verrons les jointures des couleurs au niveau des arêtes. Cela peut assurément être amélioré.

L'astuce est d'utiliser un concept connu sous le nom de « normale par sommet ». Une normale par sommet est la moyenne des normales de tous les triangles qui partagent le sommet. Au lieu de demander au vertex shader de calculer la lumière diffuse, nous lui passons juste la normale du sommet comme attribut, qui est ensuite transmise au fragment shader, et c'est tout. Le rasteriser va récupérer trois normales différentes et va devoir effectuer une interpolation entre elles. Le fragment shader va être appelé pour chaque pixel avec la normale spécifique à ce pixel. Nous pouvons dès lors calculer la lumière diffuse au niveau du pixel, en utilisant cette normale spécifique. Le résultat va être un effet de lumière, qui va agréablement changer au travers de la face du triangle et entre les triangles voisins. Cette technique est connue sous le nom de « Ombrage Phong ». Voici à quoi ressembleront les normales des sommets après l'interpolation :

Image non disponible

Vous pouvez trouver que le modèle de pyramide, que nous avons utilisé lors des quelques tutoriels précédents, a un aspect étrange avec ces normales par sommet et décider de rester en normales originales. C'est compréhensible. Cependant, comme les modèles vont devenir de plus en plus complexes (et nous verrons cela dans le futur) et que leurs surfaces deviendront plus douces, je pense que vous trouverez les normales par sommet plus appropriées.

La seule chose restante dont nous devons nous occuper est l'espace de coordonnées dans lequel les calculs de lumière diffuse vont prendre place. Les sommets et leurs normales sont exprimés dans leur système local de coordonnées, puis sont transformés dans le vertex shader vers l'espace de découpe grâce à la matrice WVP que nous donnons au shader. Cependant, définir la direction de la lumière dans l'espace monde est l'approche la plus logique. Après tout, la direction de la lumière est le résultat d'une source lumineuse qui est positionnée quelque part dans le monde (même le soleil est situé dans le « monde », bien qu'à de nombreux kilomètres de distance) et répand sa lumière dans une direction particulière. Par conséquent, nous devons transformer les normales dans l'espace monde avant le calcul.

II. Explication du code

Télécharger Sélectionnez
25.
26.
27.
28.
29.
30.
31.
struct DirectionalLight
{
    Vector3f Color;
    float AmbientIntensity;
    Vector3f Direction;
    float DiffuseIntensity;
};

C'est la nouvelle structure DirectionalLight. Il y a deux nouveaux membres ici : la direction est un vecteur à trois dimensions défini dans l'espace monde et l'intensité est un nombre flottant (qui sera utilisé de la même manière que l'intensité ambiante).

Télécharger Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
#version 330

layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
layout (location = 2) in vec3 Normal;

uniform mat4 gWVP;
uniform mat4 gWorld;

out vec2 TexCoord0;
out vec3 Normal0;

void main()
{
    gl_Position = gWVP * vec4(Position, 1.0);
    TexCoord0 = TexCoord;
    Normal0 = (gWorld * vec4(Normal, 0.0)).xyz;
}

Voici le vertex shader mis à jour. Nous avons un nouvel attribut de sommet, la normale, que l'application va devoir fournir. De plus la transformation vers l'espace monde possède sa propre variable uniforme et l'application devra là aussi la fournir, en plus de la matrice WVP. Le vertex shader transforme la normale dans l'espace monde en utilisant la matrice monde et la passe au fragment shader. Notons comment la normale à trois dimensions est étendue à un vecteur quatre dimensions, multipliée par la matrice monde à quatre dimensions, puis réduite à nouveau à trois dimensions en utilisant la notation (…).xyz. Cette capacité du langage GLSL est appelée « réarrangement » et permet une grande flexibilité dans la manipulation des vecteurs. Par exemple, si nous avons un vecteur trois dimensions v(1,2,3), nous pouvons écrire : vec4 n = v.zzyy, ainsi le vecteur n contiendra (3,3,2,2). Rappelons-nous que lorsque nous étendons la normale de trois à quatre dimensions, nous devons placer zéro comme quatrième composante. Cela neutralise l'effet de la translation de la matrice monde (la quatrième colonne). La raison est que les vecteurs ne peuvent pas être déplacés comme des points. Ils peuvent uniquement être mis à l'échelle ou orientés.

Télécharger Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
#version 330

in vec2 TexCoord0;
in vec3 Normal0;

out vec4 FragColor;

struct DirectionalLight
{
    vec3 Color;
    float AmbientIntensity;
    float DiffuseIntensity;
    vec3 Direction;
};

Voici le début du fragment shader. Il reçoit maintenant la normale interpolée, qui a été transformée dans l'espace monde par le vertex shader. La structure DirectionalLight a été modifiée pour refléter celle dans le code C++ et contient les nouveaux attributs de la lumière.

Télécharger Sélectionnez
19.
20.
21.
22.
void main()
{
    vec4 AmbientColor = vec4(gDirectionalLight.Color, 1.0f) *
            gDirectionalLight.AmbientIntensity;

Il n'y a aucun changement dans le calcul du facteur de couleur ambiante. Nous le calculons et le stockons ici pour pouvoir l'utiliser dans la formule finale plus bas.

Sélectionnez
float DiffuseFactor = dot(normalize(Normal0), -gDirectionalLight.Direction);

Voici le cœur du calcul de lumière diffuse. Nous calculons le cosinus de l'angle entre le vecteur lumière et la normale en faisant un produit scalaire entre eux. Trois choses à noter ici :

  1. La normale venue du vertex shader est normalisée avant utilisation. On le fait parce que l'interpolation, au travers de laquelle le vecteur est passé, peut avoir modifié sa longueur et il n'est donc plus un vecteur unitaire ;
  2. La direction de la lumière a été inversée. Lorsqu'on y réfléchit un instant, nous voyons que la lumière qui atteint la surface à angle droit est en fait à 180 degrés de la normale de la surface (qui pointe simplement vers la source lumineuse). En inversant la direction de la lumière dans ce cas, nous obtenons un vecteur qui est égal à la normale. Par conséquent l'angle entre eux vaut zéro, ce que nous souhaitons ;
  3. le vecteur lumière n'est pas normalisé. Ce serait un gâchis de ressources GPU que de normaliser le même vecteur encore et encore, pour chaque pixel. À la place, nous nous assurons de normaliser le vecteur que l'application passe avant que le dessin ne soit fait.
Sélectionnez
vec4 DiffuseColor;

    if (DiffuseFactor > 0) {
        DiffuseColor = vec4(gDirectionalLight.Color, 1.0f) * gDirectionalLight.DiffuseIntensity * DiffuseFactor;
    }
    else {
        DiffuseColor = vec4(0, 0, 0, 0);
    }

Ici nous calculons le terme de diffusion, qui dépend de la couleur de la lumière, l'intensité de diffusion et la direction de la lumière. Si le facteur de diffusion est inférieur ou égal à zéro, cela signifie que la lumière atteint la surface avec un angle obtus (soit « de côté », soit « de derrière »). Dans ce cas la lumière diffuse n'a pas d'effet et le vecteur DiffuseColor est initialisé à zéro. Si le facteur est supérieur à zéro, nous calculons la couleur diffuse en multipliant la couleur de base de la lumière par l'intensité de diffusion, puis pondérons le résultat par le facteur de diffusion. Si l'angle entre la lumière et la normale est zéro, le facteur de diffusion vaudra un, ce qui fournit la force maximale de lumière.

Sélectionnez
    FragColor = texture2D(gSampler, TexCoord0.xy) * (AmbientColor + DiffuseColor);
}

Voici le calcul final de l'éclairage. Nous ajoutons les termes ambiant et diffusion, puis multiplions le résultat par la couleur échantillonnée à partir de la texture. Maintenant, nous voyons que même si la lumière diffuse n'a pas d'effet sur la surface (à cause de sa direction), la lumière ambiante peut toujours l'éclairer, si elle existe.

Télécharger Sélectionnez
144.
145.
146.
147.
148.
149.
150.
151.
152.
void LightingTechnique::SetDirectionalLight(const DirectionalLight& Light)
{
    glUniform3f(m_dirLightLocation.Color, Light.Color.x, Light.Color.y, Light.Color.z);
    glUniform1f(m_dirLightLocation.AmbientIntensity, Light.AmbientIntensity);
    Vector3f Direction = Light.Direction;
    Direction.Normalize();
    glUniform3f(m_dirLightLocation.Direction, Direction.x, Direction.y, Direction.z);
    glUniform1f(m_dirLightLocation.DiffuseIntensity, Light.DiffuseIntensity);
}

Cette fonction définit les paramètres de la lumière directionnelle dans le shader. Elle a été étendue pour couvrir le vecteur direction et l'intensité de diffusion. Notons que le vecteur direction est normalisé avant d'être défini. La classe LightingTechnique récupère aussi la position des variables uniformes dans le shader, pour la direction et pour l'intensité de diffusion, ainsi que pour la matrice monde. Il y a aussi une fonction pour définir la matrice de transformation monde. Toutes ces choses sont maintenant de jolies fonctions et le code n'est pas retranscrit ici. Visionnez les sources pour plus de détails.

Télécharger Sélectionnez
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
struct Vertex
{
    Vector3f m_pos;
    Vector2f m_tex;
    Vector3f m_normal;

    Vertex() {}

    Vertex(Vector3f pos, Vector2f tex)
    {
        m_pos = pos;
        m_tex = tex;
        m_normal = Vector3f(0.0f, 0.0f, 0.0f);
    }
};

La structure Vertex a été mise à jour et inclut maintenant la normale. Elle est initialisée automatiquement à zéro par le constructeur et nous avons une fonction dédiée qui parcourt tous les sommets et calcule les normales.

Télécharger Sélectionnez
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
void CalcNormals(const unsigned int* pIndices, unsigned int IndexCount, Vertex* pVertices, unsigned int VertexCount)
{
    for (unsigned int i = 0 ; i < IndexCount ; i += 3) {
        unsigned int Index0 = pIndices[i];
        unsigned int Index1 = pIndices[i + 1];
        unsigned int Index2 = pIndices[i + 2];
        Vector3f v1 = pVertices[Index1].m_pos - pVertices[Index0].m_pos;
        Vector3f v2 = pVertices[Index2].m_pos - pVertices[Index0].m_pos;
        Vector3f Normal = v1.Cross(v2);
        Normal.Normalize();

        pVertices[Index0].m_normal += Normal;
        pVertices[Index1].m_normal += Normal;
        pVertices[Index2].m_normal += Normal;
    }

    for (unsigned int i = 0 ; i < VertexCount ; i++) {
        pVertices[i].m_normal.Normalize();
    }
}

Cette fonction prend un tableau de sommets et un tableau d'indices, parcourt chaque triangle en fonction des indices et calcule sa normale. Dans la première boucle, nous accumulons simplement les normales des trois sommets du triangle. Pour chaque triangle, la normale est calculée comme le produit vectoriel entre deux arêtes venant du premier sommet. Avant d'accumuler la normale dans le sommet, nous nous assurons de la normaliser. La raison est que le résultat du produit vectoriel n'est pas forcément un vecteur unitaire. Dans la seconde boucle, nous parcourons le tableau de sommets directement (nous n'avons plus à nous préoccuper des indices) et normalisons la normale de chaque sommet. Cette opération est l'équivalent de faire la moyenne de la somme de toutes les normales accumulées et nous laisse avec une normale par sommet qui est de longueur unitaire. Cette fonction est appelée avant la création du tampon de sommets, de manière à avoir dans ce tampon les normales calculées, avec les autres attributs de sommets.

Télécharger Sélectionnez
131.
132.
133.
134.
135.
136.
137.
138.
    const Matrix4f& WorldTransformation = p.GetWorldTrans();
    m_pEffect->SetWorldMatrix(WorldTransformation);
    ...
    glEnableVertexAttribArray(2);
    ...
    glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)20);
    ...
    glDisableVertexAttribArray(2);

Voici les changements principaux de la boucle de rendu. La classe Pipeline a une nouvelle fonction qui fournit la matrice de transformation monde (en plus de la matrice WVP). La matrice monde est calculée comme la multiplication de la matrice de mise à l'échelle par la matrice de rotation et finalement par la matrice de translation. Nous activons et désactivons le troisième attribut de sommet, et spécifions le décalage de la normale à l'intérieur de chaque sommet du tampon de sommets. Le décalage vaut 20, car la normale est précédée de la position (12 octets) et des coordonnées de texture (8 octets).

Pour compléter la démonstration que nous voyons dans l'image de ce tutoriel, nous devons aussi spécifier l'intensité de diffusion et la direction de la lumière. Cela est fait dans le constructeur de la classe Tutorial18. L'intensité de diffusion est définie à 0.8 et la direction de la lumière va de gauche à droite. L'intensité de l'ambiante a été réduite à zéro pour amplifier l'effet de la lumière diffuse. Nous pouvons jouer avec les touches « z » et « x » pour contrôler l'intensité de diffusion (comme « a » et « s » du tutoriel précédent modifient l'intensité de l'ambiante).

Image non disponible
Résultat

III. Point mathématique

De nombreuses sources sur Internet vous diront que vous devez transposer l'inverse de la matrice monde pour transformer le vecteur normal. C'est correct, cependant nous n'avons généralement pas besoin d'aller si loin. Nos matrices monde sont toujours orthogonales (leurs vecteurs sont toujours orthogonaux). Comme l'inverse d'une matrice orthogonale est égale à sa transposée, la transposée de l'inverse est en fait la transposée de la transposée, et nous arrivons donc avec notre matrice originale. Aussi longtemps que nous évitons de faire des distorsions (pondérer un axe différemment des autres) nous pouvons continuer avec l'approche présentée ci-dessus.

IV. Sources

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources


Tutoriel 19 - Lumière spéculaire

  • I. Contexte
  • II. Explication du code
  • III. Sources
  • IV. Remerciements
  • V. Navigation

Ce tutoriel est le troisième de la série sur l'éclairage. Il termine avec la lumière spéculaire.

I. Contexte

Lorsque nous avons calculé l'éclairage ambiant, le seul facteur était l'intensité de la lumière. Ensuite, nous avons ajouté la lumière diffuse, qui prenait en compte la direction de la lumière dans l'équation. La lumière spéculaire inclut ces facteurs et ajoute un nouvel élément au tout : la position de l'observateur.

L'idée est que lorsque la lumière atteint une surface avec un angle donné, elle est aussi réfléchie avec le même angle (du côté opposé par rapport à la normale). Si l'observateur est situé quelque part le long du rayon reflété, il reçoit une plus grande quantité de lumière qu'un observateur placé ailleurs.

Le résultat final de la lumière spéculaire rendra les objets plus clairs sous certains angles et cette clarté diminuera si l'observateur se déplace. L'exemple parfait de la lumière spéculaire dans le monde réel est un objet métallique. Ce type d'objets peut parfois être tellement clair qu'au lieu de voir la couleur naturelle de l'objet on voit une tache de lumière blanche directement reflétée vers vous. Cependant, ce type de caractéristique, naturelle pour les métaux, est absente pour de nombreux autres matériaux (par exemple : le bois). De nombreux objets ne brillent tout simplement pas, quel que soit l'endroit d'où vient la lumière et quelle que soit la position de l'observateur. La conclusion est que le facteur spéculaire dépend plus de l'objet que de la lumière elle-même.

Voyons comment nous pouvons amener la position de l'observateur dans le calcul de la lumière spéculaire. Regardons l'image suivante :

Image non disponible

Il y a cinq choses auxquelles il faut prêter attention :

  • « I » est la lumière incidente qui atteint la surface (et génère la lumière diffuse) ;
  • « N » est la normale à la surface ;
  • « R » est le rayon de lumière qui est réfléchi par la surface. Il est symétrique à « I » par rapport à N, mais sa direction est inversée (il pointe vers le « haut » et pas vers le « bas »).
  • « V » est le vecteur qui va du point d'intersection entre « I » et la surface jusqu'à l'« œil » (qui représente l'observateur).
  • « α » est l'angle créé par les vecteurs « R » et « V ».

Nous allons modéliser le phénomène de lumière spéculaire en utilisant l'angle « α ». L'idée derrière la lumière spéculaire est que la force du rayon réfléchi est à son maximum le long du vecteur « R ». Dans ce cas, « V » est identique à « R » et l'angle vaut zéro. Quand l'observateur commence à s'éloigner de « R », l'angle grandit. Nous voulons que l'effet de la lumière diminue quand l'angle augmente. À présent, nous pouvons deviner que nous allons utiliser le produit scalaire, une nouvelle fois, afin de calculer le cosinus de « α ». Cela servira de facteur spéculaire dans la formule d'éclairage. Quand « α » vaut zéro, son cosinus vaut un, ce qui est le facteur maximum que nous puissions obtenir. Quand « α » diminue, son cosinus devient plus petit jusqu'à ce que « α » atteigne 90 degrés, où le cosinus vaut zéro, et où il n'y a pas d'effet spéculaire. Quand « α » vaut plus de 90 degrés, le cosinus devient négatif et il n'y a pas non plus d'effet spéculaire. Cela signifie que l'observateur n'est pas du tout dans le chemin du rayon réfléchi.

Pour calculer « α », nous allons avoir besoin de « R » et de « V ». « V » peut être calculé en soustrayant la position du point d'impact de la lumière, dans l'espace monde, à la position de l'observateur (lui aussi dans l'espace monde). Comme notre caméra se situe déjà dans l'espace monde, nous n'avons qu'à passer sa position au shader. Comme l'image ci-dessus est simplifiée, il n'y a qu'un seul point que la lumière atteint. En réalité, le triangle complet est éclairé (si tant est qu'il fait face à la lumière). Donc, nous calculons l'effet spéculaire pour chaque pixel (comme nous le faisons déjà avec la lumière diffuse) et pour cela, nous avons besoin de la position de chaque pixel dans l'espace monde. Là aussi, c'est simple : nous transformons les sommets dans l'espace monde, puis nous laissons le rasterizer interpoler la position monde du pixel et nous fournir le résultat dans le fragment shader. En fait, c'est le même travail que celui que nous avons effectué sur la normale dans le tutoriel précédent.

La seule chose qu'il nous reste à faire est de calculer le rayon réfléchi « R », en utilisant le vecteur « I » (fourni au shader par l'application). Regardons l'image suivante :

Image non disponible

Souvenons-nous qu'un vecteur n'a pas vraiment de point de départ et que tous les vecteurs ayant la même direction et la même norme sont égaux. Par conséquent, le vecteur « I » a été copié « en dessous » de la surface et la copie est identique à l'original. L'objectif est de calculer le vecteur « R ». Selon les règles de l'addition de vecteurs, « R » est égal à « I » + « V ». « I » est déjà connu donc nous n'avons qu'à calculer « V ». Notons que l'opposée à la normale « V » apparaît aussi comme « -N » et l'utilisation d'un produit scalaire entre « I » et « -N » nous permet de trouver la norme du vecteur créé lorsqu'on projette « I » sur « -N ». Cette norme est exactement la moitié de la norme de « V ». Comme « V » a la même direction que « N », nous calculons « V » en multipliant « N » (dont la norme vaut 1.0) par deux fois la valeur de cette norme. En résumé :

R=I+V R=I+V
V=2×N×(−N.I)V=2×N×(−N.I)
⇒R=I+2×N×(−N.I)=I−2×N×(N.I)⇒R=I+2×N×(−N.I)=I−2×N×(N.I)

Maintenant que vous comprenez ces mathématiques, il est temps de vous révéler un petit secret : GLSL fournit une fonction interne appelée « reflect » qui fait exactement ce calcul. Nous verrons plus bas comment l'utiliser dans un shader.

Finalisons la formule de la lumière spéculaire :

⎛⎝⎜⎜RspéculaireGspéculaireBspéculaire⎞⎠⎟⎟=⎛⎝⎜⎜RlumièreGlumièreBlumière⎞⎠⎟⎟×⎛⎝⎜RsurfaceGsurfaceBsurface⎞⎠⎟×M×(R.V)P(R sp é culaireG sp é culaireB sp é culaire)=(R lumi è reG lumi è reB lumi è re)×(R surfaceG surfaceB surface)×M×(R.V)P

Nous commençons en multipliant la couleur de la lumière par la couleur de la surface. C'est la même chose qu'avec les lumières ambiante et diffuse. Le résultat est multiplié par l'intensité spéculaire du matériau (« M »). Un matériau qui n'a pas de propriété spéculaire (par exemple : du bois) aura une intensité spéculaire de zéro, ce qui mettra à zéro le résultat de l'équation. Des objets plus brillants, comme le métal, peuvent avoir des niveaux de plus en plus élevés d'intensité spéculaire. Après cela, nous multiplions ce résultat par le cosinus de l'angle entre le rayon réfléchi et le vecteur vers l'observateur. Notons que cette dernière partie est élevée à la puissance « P ». « P » est appelée « puissance spéculaire » ou « facteur de brillance ». Son travail est d'intensifier et d'affiner les bords dans les zones où la lumière spéculaire est présente. L'image suivante montre l'effet de la puissance spéculaire quand elle est mise à 1 :

Image non disponible

Alors que la suivante montre un exposant spéculaire de 32 :

Image non disponible

La puissance spéculaire est aussi considérée comme un attribut du matériau donc différents objets peuvent avoir différentes valeurs de puissance spéculaire.

II. Explication du code

Télécharger Sélectionnez
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
class LightingTechnique : public Technique
{
public:
...
    void SetEyeWorldPos(const Vector3f& EyeWorldPos);
    void SetMatSpecularIntensity(float Intensity);
    void SetMatSpecularPower(float Power);

private:
...
    GLuint m_eyeWorldPosLocation;
    GLuint m_matSpecularIntensityLocation;
    GLuint m_matSpecularPowerLocation;

Il y a trois nouveaux attributs dans la classe LightingTechnique : la position de l'observateur, l'intensité spéculaire et la puissance spéculaire du matériau. Les trois sont indépendantes de la lumière elle-même. La raison est que lorsque la même lumière atteint deux matériaux différents (par exemple : du métal et du bois), chacun brille d'une manière différente. L'utilisation de ces deux attributs pour le matériau impose certaines limites. Tous les triangles faisant partie du même appel à la fonction de dessin auront la même valeur pour ces attributs. Cela peut être gênant quand les triangles représentent des parties différentes du modèle, avec différentes propriétés de matériau. Quand nous atteindrons les tutoriels de chargement de maillage, nous verrons que nous pouvons générer différentes valeurs de spéculaire dans les applications de modelage et les intégrer au tampon de sommets (au lieu de paramètres de shader). Cela nous permettra de dessiner des triangles avec différents éclairages spéculaires dans le même appel. Pour l'instant, l'approche simple que nous en avons faite suffira (comme exercice, vous pouvez essayer d'ajouter l'intensité spéculaire et la puissance spéculaire au tampon de sommets et y accéder dans le shader).

Télécharger Sélectionnez
12.
13.
14.
15.
16.
17.
18.
19.
20.
out vec3 WorldPos0;

void main()
{
    gl_Position = gWVP * vec4(Position, 1.0);
    TexCoord0 = TexCoord;
    Normal0 = (gWorld * vec4(Normal, 0.0)).xyz;
    WorldPos0 = (gWorld * vec4(Position, 1.0)).xyz;
}

Le vertex shader ci-dessus intègre une seule nouvelle ligne (la dernière). La matrice monde (que nous avons ajoutée dans le tutoriel précédent pour transformer la normale) est maintenant utilisée pour passer la position monde du sommet au fragment shader. Nous voyons une technique intéressante, qui est de transformer la même position du sommet (qui est fournie dans l'espace local) en utilisant deux matrices différentes et de passer les résultats indépendamment au fragment shader. Le résultat de la transformation complète (matrice de transformation monde-vue-projection) va dans la variable du GLSL « gl_Position », le GPU s'occupe de la transformer dans l'espace-écran et l'utilise dans le procédé de rastérisation. Le résultat de la transformation « partielle » (uniquement dans l'espace monde) va dans un attribut défini par l'utilisateur, qui est simplement interpolé durant la rastérisation, afin que chaque pixel pour lequel le fragment shader est invoqué reçoive sa propre position dans l'espace monde. Cette pratique est très utile et est souvent employée.

Télécharger Sélectionnez
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
in vec3 WorldPos0;
.
.
.
uniform vec3 gEyeWorldPos;
uniform float gMatSpecularIntensity;
uniform float gSpecularPower;

void main()
{
    vec4 AmbientColor = vec4(gDirectionalLight.Color, 1.0f) * gDirectionalLight.AmbientIntensity;
    vec3 LightDirection = -gDirectionalLight.Direction;
    vec3 Normal = normalize(Normal0);

    float DiffuseFactor = dot(Normal, LightDirection);

    vec4 DiffuseColor = vec4(0, 0, 0, 0);
    vec4 SpecularColor = vec4(0, 0, 0, 0);

    if (DiffuseFactor > 0) {
        DiffuseColor = vec4(gDirectionalLight.Color, 1.0f) *
            gDirectionalLight.DiffuseIntensity *
            DiffuseFactor;

        vec3 VertexToEye = normalize(gEyeWorldPos - WorldPos0);
        vec3 LightReflect = normalize(reflect(gDirectionalLight.Direction, Normal));
        float SpecularFactor = dot(VertexToEye, LightReflect);
        SpecularFactor = pow(SpecularFactor, gSpecularPower);
        if (SpecularFactor > 0) {
            SpecularColor = vec4(gDirectionalLight.Color, 1.0f) * gMatSpecularIntensity * SpecularFactor;
        }
    }

    FragColor = texture2D(gSampler, TexCoord0.xy) * (AmbientColor + DiffuseColor + SpecularColor);
}

Il y a plusieurs changements dans le vertex shader. On y trouve maintenant trois nouvelles variables uniformes, qui stockent les attributs requis pour le calcul de la lumière spéculaire (position de l'observateur, intensité et puissance spéculaire). La couleur ambiante est calculée de la même manière que dans les deux tutoriels précédents. Puis les couleurs diffuse et spéculaire sont créées et initialisées à zéro. Elles ont toutes les deux une valeur différente de zéro uniquement lorsque l'angle entre la lumière et la surface vaut moins de 90 degrés. La vérification est faite en utilisant le facteur de diffusion (de la même manière que dans le tutoriel sur la lumière diffuse).

L'étape suivante est le calcul du vecteur allant du sommet en position monde à la position de l'observateur (elle aussi dans l'espace monde). Nous faisons ce calcul en soustrayant la position monde du sommet à la position de l'observateur, qui est une variable uniforme et est identique pour tous les pixels. Ce vecteur est normalisé pour le rendre utilisable dans le produit scalaire. Après cela, le vecteur lumière réfléchi est calculé en utilisant la fonction interne « reflect » (nous pourrions aussi le calculer manuellement, en suivant la description plus haut). Cette fonction accepte deux paramètres : le vecteur lumière et la normale à la surface. L'important ici est d'utiliser le vecteur lumière original, celui qui va vers la surface, pas celui qui a été utilisé pour le calcul du facteur de diffusion. C'est évident lorsqu'on regarde le diagramme ci-dessus. Ensuite, nous calculons le facteur spéculaire, à partir du cosinus de l'angle entre le rayon de lumière réfléchi et le vecteur du sommet à l'observateur (là encore avec un produit scalaire).

L'effet spéculaire est visible uniquement si cet angle est inférieur à 90 degrés. Par conséquent, nous vérifions que le résultat du dernier produit scalaire est supérieur à zéro. La couleur spéculaire finale est calculée en multipliant la couleur de la lumière par l'intensité spéculaire du matériau et le facteur spéculaire. Nous ajoutons les couleurs ambiante, diffuse et spéculaire pour calculer la couleur complète de la lumière. Elle est multipliée par la couleur échantillonnée depuis la texture et fournit la couleur finale du pixel.

Télécharger Sélectionnez
134.
135.
136.
m_pEffect->SetEyeWorldPos(m_pGameCamera->GetPos());
m_pEffect->SetMatSpecularIntensity(1.0f);
m_pEffect->SetMatSpecularPower(32);

Utiliser la couleur spéculaire est très simple. Dans la boucle de rendu, nous récupérons la position de la caméra (qui est déjà définie dans l'espace monde) et la passons à la technique de lumière. Nous définissons aussi l'intensité spéculaire et sa puissance. Tout le reste est géré par le shader.

Jouez avec les différentes valeurs spéculaires et la direction de la lumière pour voir leur effet. Vous devrez probablement vous déplacer autour de l'objet pour vous placer dans une position où la lumière spéculaire est visible.

III. Sources

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources


Tutoriel 20 - Lumières ponctuelles

  • I. Contexte
  • II. Explication du code
  • III. Sources
  • IV. Remerciements
  • Navigation

Quatrième chapitre sur l'éclairage, ce tutoriel étudie les sources de lumières ponctuelles.

I. Contexte

Nous avons étudié les trois modèles de l'éclairage basique (ambiant, diffus, spéculaire), sous le couvert de la lumière directionnelle. La lumière directionnelle est un type de lumière caractérisé par un simple vecteur direction et l'absence d'origine. Par conséquent, son éclairage ne devient pas plus faible avec la distance (en fait, on ne peut pas définir la distance entre la source lumineuse et la cible éclairée). Nous allons maintenant voir le type de lumière appelé « lumière ponctuelle », qui a un point d'origine ainsi qu'un effet d'affaiblissement, qui augmente à mesure que les objets s'en éloignent. Un exemple classique de lumière ponctuelle est une ampoule. Nous ne pouvons pas bien nous rendre compte de l'effet d'affaiblissement lorsque l'ampoule est dans une pièce, mais, en la plaçant à l'extérieur, nous voyons bien comme sa puissance est limitée. Notons que la direction de la lumière, qui est une constante de scène pour une lumière directionnelle, devient dynamique pour une lumière ponctuelle. C'est parce qu'une lumière ponctuelle brille de la même manière dans toutes les directions, donc la direction doit être calculée pour chaque objet, en récupérant le vecteur entre l'objet et la source lumineuse. C'est pour cela que nous spécifions l'origine plutôt que la direction des lumières ponctuelles.

L'effet d'affaiblissement des lumières ponctuelles est généralement appelé « atténuation ». L'atténuation pour les lumières réelles est régie par la loi des carrés inverses, qui dit que l'intensité de la lumière est inversement proportionnelle au carré de la distance à la source de la lumière. Ce concept est décrit de manière mathématique par la formule suivante :

Ldistance=L1distance2 (L1−force de la lumière à la distance 1)L distance=L 1 distanc e 2 (L 1−force de la lumi è re à la distance 1)

Cette formule ne donne pas de bons résultats en graphisme 3D. Par exemple, comme la distance devient plus petite, l'intensité de la lumière s'approche de l'infini. En outre, le développeur n'a pas de contrôle sur le résultat, à part en définissant l'intensité initiale de la lumière. C'est trop limité. Par conséquent, nous ajoutons quelques facteurs à la formule ci-dessus pour la rendre plus flexible :

Ldistance=L1Attenuationconstante+Attenuationlinéaire×distance+Attenuationexp×distance2 L distance=L 1 Attenuatio n constante+Attenuatio n lin é aire×distance+Attenuatio n exp×distanc e 2

Nous avons ajouté trois facteurs d'atténuation de la lumière au dénominateur : un facteur constant, un facteur linéaire et un facteur exponentiel. La formule physiquement correcte est retrouvée en mettant les facteurs linéaire et constant à zéro et le facteur exponentiel à un. Nous pourrons trouver utile de mettre le facteur constant à un et les deux autres à des fractions plus petites. En mettant le facteur constant à un, nous garantissons que l'intensité de la lumière atteint son maximum (en fait, la valeur configurée dans le programme) à la distance zéro et décroît avec l'augmentation de la distance, car le dénominateur devient plus grand que un. En réglant correctement les facteurs, linéaire et exponentiel, nous atteindrons l'effet désiré de lumière qui s'affaiblit rapidement ou lentement, suivant la distance.

Résumons les étapes requises pour le calcul d'une lumière ponctuelle :

  1. Calculer le terme ambiant, de la même manière que pour une lumière directionnelle ;
  2. Calculer la direction de la lumière comme étant le vecteur allant du pixel (dans l'espace monde) au point d'origine de la source lumineuse. Nous pouvons maintenant calculer les termes de diffusion et de spéculaire, de la même manière que pour une lumière directionnelle, en utilisant cette fois la direction calculée ;
  3. Calculer la distance entre le pixel et le point d'origine de la source lumineuse et l'utiliser pour calculer la valeur totale d'atténuation ;
  4. Ajouter les trois termes et diviser cette somme par l'atténuation pour obtenir la couleur finale.

II. Explication du code

Télécharger Sélectionnez
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
struct BaseLight
{
    Vector3f Color;
    float AmbientIntensity;
    float DiffuseIntensity;
};
.
.
.
struct PointLight : public BaseLight
{
    Vector3f Position;

    struct
    {
        float Constant;
        float Linear;
        float Exp;
    } Attenuation;
}

En dépit de leurs différences, les sources lumineuses directionnelles et ponctuelles ont beaucoup en commun. Ces données communes ont été déplacées dans la structure BaseLight, dont les deux types de lumière dérivent maintenant. La structure de lumière directionnelle ajoute la direction tandis que la structure de lumière ponctuelle ajoute la position (dans l'espace monde) et les trois facteurs d'atténuation.

Télécharger Sélectionnez
81.
void SetPointLights(unsigned int NumLights, const PointLight* pLights);

En plus de montrer comment implémenter une lumière ponctuelle, ce tutoriel montre aussi comment utiliser plusieurs sources lumineuses. Nous considérons ici qu'il n'y a généralement qu'une seule lumière directionnelle (jouant le rôle de « soleil ») et plusieurs sources lumineuses ponctuelles possibles (des ampoules dans des pièces, des torches dans un donjon, etc.). Cette fonction prend en paramètre un tableau de structures PointLight ainsi que sa taille et met à jour le shader avec leurs valeurs.

Télécharger Sélectionnez
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
struct {
    GLuint Color;
    GLuint AmbientIntensity;
    GLuint DiffuseIntensity;
    GLuint Position;
    struct
    {
        GLuint Constant;
        GLuint Linear;
        GLuint Exp;
    } Atten;
} m_pointLightsLocation[MAX_POINT_LIGHTS];

Afin de supporter plusieurs lumières ponctuelles, le shader contient un tableau de structures identiques à la structure PointLight (en GLSL, cette fois). Il y a deux méthodes pour mettre à jour un tableau de structures dans les shaders :

  1. Nous pouvons récupérer la position de chaque membre de la structure, pour chaque élément du tableau (donc un tableau de cinq structures avec quatre champs amène à vingt positions de variables uniformes) et définir la valeur de chaque champ séparément ;
  2. Nous pouvons récupérer la position des champs du premier élément et utiliser une fonction OpenGL, qui définit les valeurs d'un tableau de variables, pour chaque type d'attribut spécifique à un champ. Par exemple, le premier champ est un flottant et le second est un entier, donc on peut définir toutes les valeurs du premier champ en donnant un tableau de flottants en un appel et définir le second champ avec un tableau d'entiers en un second appel.

La première méthode est la moins rentable en termes de nombre de positions de variables uniformes à stocker, mais est plus flexible à l'utilisation. Elle permet de mettre à jour n'importe quelle variable du tableau en accédant simplement à sa position et ne requiert pas la transformation de données qu'impose la seconde méthode.

La seconde méthode requiert moins de gestion de positions de variables uniformes ; cependant, si nous voulons mettre à jour plusieurs tableaux d'éléments à la fois et que l'utilisateur donne un tableau de structures (comme le fait la méthode SetPointLights()), nous allons devoir les transformer en une structure de tableaux, à cause du fait que les positions de variables uniformes représentent des tableaux de variables de même type. Lors de l'utilisation d'un tableau de structures, il y a un espace en mémoire entre les positions du même champ de deux structures consécutives dans le tableau, ce qui requiert de les rassembler dans un même tableau.

Dans ce tutoriel, nous allons utiliser la première méthode. Vous pouvez vous amuser avec les deux pour décider laquelle fonctionne le mieux dans votre cas.

La constante MAX_POINT_LIGHTS limite le nombre de lumières ponctuelles pouvant être utilisées et doit être synchronisée avec la valeur correspondante dans le shader. La valeur par défaut est 2. En augmentant le nombre de sources lumineuses dans votre application, vous pouvez rencontrer des problèmes de performances, qui empirent avec l'augmentation du nombre de lumières. Ce problème peut être contourné en utilisant une technique appelée « rendu différé » qui sera traitée plus tard.

Télécharger Sélectionnez
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
vec4 CalcLightInternal(BaseLight Light, vec3 LightDirection, vec3 Normal)
{
    vec4 AmbientColor = vec4(Light.Color, 1.0f) * Light.AmbientIntensity;
    float DiffuseFactor = dot(Normal, -LightDirection);

    vec4 DiffuseColor = vec4(0, 0, 0, 0);
    vec4 SpecularColor = vec4(0, 0, 0, 0);

    if (DiffuseFactor > 0) {
        DiffuseColor = vec4(Light.Color, 1.0f) * Light.DiffuseIntensity * DiffuseFactor;
        vec3 VertexToEye = normalize(gEyeWorldPos - WorldPos0);
        vec3 LightReflect = normalize(reflect(LightDirection, Normal));
        float SpecularFactor = dot(VertexToEye, LightReflect);
        SpecularFactor = pow(SpecularFactor, gSpecularPower);
        if (SpecularFactor > 0) {
            SpecularColor = vec4(Light.Color, 1.0f) *
            gMatSpecularIntensity * SpecularFactor;
        }
    }

    return (AmbientColor + DiffuseColor + SpecularColor);
}

Il n'est pas surprenant de remarquer que nous avons beaucoup de code en commun, entre les lumières directionnelles et les lumières ponctuelles. La plupart des algorithmes sont les mêmes. La différence réside dans le fait que nous devons ajouter le facteur d'atténuation uniquement pour la lumière ponctuelle. De plus, la direction de la lumière est fournie par l'application dans le cas de la lumière directionnelle alors qu'elle est calculée par pixel dans le cas de la lumière ponctuelle.

La fonction ci-dessus encapsule le code commun aux deux types de lumières. La structure BaseLight contient les intensités et la couleur. Le membre LightDirection est fourni séparément à cause de la raison décrite précédemment. La normale au sommet est aussi fournie, car nous la normalisons une fois en entrée du fragment shader puis l'utilisons plusieurs fois dans cette fonction.

Télécharger Sélectionnez
70.
71.
72.
73.
vec4 CalcDirectionalLight(vec3 Normal)
{
    return CalcLightInternal(gDirectionalLight.Base, gDirectionalLight.Direction, Normal);
}

Avec la mise en place de la fonction commune, la fonction de calcul de la lumière directionnelle devient une simple enveloppe pour celle-ci, renseignant la plupart des paramètres à partir de variables globales.

lighting.fs
Sélectionnez
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
vec4 CalcPointLight(int Index, vec3 Normal)
{
    vec3 LightDirection = WorldPos0 - gPointLights[Index].Position;
    float Distance = length(LightDirection);
    LightDirection = normalize(LightDirection);

    vec4 Color = CalcLightInternal(gPointLights[Index].Base, LightDirection, Normal);
    float Attenuation = gPointLights[Index].Atten.Constant +
                        gPointLights[Index].Atten.Linear * Distance +
                        gPointLights[Index].Atten.Exp * Distance * Distance;

    return Color / Attenuation;
}

Calculer une lumière ponctuelle est un peu plus complexe que calculer une lumière directionnelle. Cette fonction sera appelée pour chaque source ponctuelle donc elle prend l'indice de la source en paramètre et l'utilise comme index du tableau de lumières ponctuelles. Elle calcule le vecteur partant de la source (fournie dans l'espace monde par l'application) vers la position du pixel dans l'espace monde, fourni par le vertex shader. La distance de la source au pixel est calculée en utilisant la fonction interne length(). Maintenant que nous avons la distance, nous normalisons le vecteur de direction de la lumière. Rappelons-nous que CalcLightInternal() s'attend à ce qu'il soit normalisé et, dans le cas d'une lumière directionnelle, la classe LightingTechnique s'en occupe. Nous récupérons la couleur en sortie de la fonction CalcLightInternal() et utilisons la distance récupérée auparavant pour calculer l'atténuation. La couleur finale de la source lumineuse ponctuelle est calculée en divisant la couleur obtenue par l'atténuation.

Télécharger Sélectionnez
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
void main()
{
    vec3 Normal = normalize(Normal0);
    vec4 TotalLight = CalcDirectionalLight(Normal);

    for (int i = 0 ; i < gNumPointLights ; i++) {
        TotalLight += CalcPointLight(i, Normal);
    }

    FragColor = texture2D(gSampler, TexCoord0.xy) * TotalLight;
}

Maintenant que nous avons toute l'infrastructure en place, le fragment shader devient très simple. Il effectue la normalisation de la normale au sommet et accumule les résultats d'application des sources lumineuses. Le résultat est multiplié par la couleur échantillonnée et est utilisé comme couleur finale du pixel.

Télécharger Sélectionnez
279.
280.
281.
282.
283.
284.
285.
286.
287.
288.
289.
290.
291.
292.
void LightingTechnique::SetPointLights(unsigned int NumLights, const PointLight* pLights)
{
    glUniform1i(m_numPointLightsLocation, NumLights);

    for (unsigned int i = 0 ; i < NumLights ; i++) {
        glUniform3f(m_pointLightsLocation[i].Color, pLights[i].Color.x, pLights[i].Color.y, pLights[i].Color.z);
        glUniform1f(m_pointLightsLocation[i].AmbientIntensity, pLights[i].AmbientIntensity);
        glUniform1f(m_pointLightsLocation[i].DiffuseIntensity, pLights[i].DiffuseIntensity);
        glUniform3f(m_pointLightsLocation[i].Position, pLights[i].Position.x, pLights[i].Position.y, pLights[i].Position.z);
        glUniform1f(m_pointLightsLocation[i].Atten.Constant, pLights[i].Attenuation.Constant);
        glUniform1f(m_pointLightsLocation[i].Atten.Linear, pLights[i].Attenuation.Linear);
        glUniform1f(m_pointLightsLocation[i].Atten.Exp, pLights[i].Attenuation.Exp);
    }
}

Cette fonction met à jour les valeurs des lumières ponctuelles dans le shader, en itérant sur le tableau d'éléments et en passant une à une les valeurs de chaque élément. C'est la « méthode 1 » décrite précédemment.

La démonstration de ce tutoriel montre deux lumières ponctuelles se poursuivant sur le terrain. Les déplacements d'une des sources sont basés sur la fonction cosinus et les déplacements de l'autre le sont sur la fonction sinus. Le terrain est un simple quadrilatère formé par deux triangles. La normale est un vecteur pointant directement vers le haut.

III. Sources

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources


Tutoriel 21 - Projecteurs

  • I. Contexte
  • II. Explication du code
  • III. Sources
  • IV. Remerciements
  • Navigation

Dernier tutoriel sur l'éclairage, nous y étudions les sources de lumière de type projecteur.

I. Contexte

Le projecteur est la troisième et dernière source de lumière que nous allons étudier (pour au moins un petit moment…). Elle est plus complexe que la lumière directionnelle et la lumière ponctuelle et emprunte beaucoup aux deux. Le projecteur a une position d'origine et un effet d'atténuation lié à l'éloignement de la cible (comme les lumières ponctuelles), de plus sa lumière pointe dans une direction spécifique (comme les lumières directionnelles). La source lumineuse projecteur ajoute un attribut unique : elle se déverse uniquement à l'intérieur d'un cône limité, qui s'élargit avec l'éloignement de la lumière par rapport à son origine. Un bon exemple de projecteur est une lampe torche. Les projecteurs sont très utiles lorsque le personnage du jeu que vous développez est en train d'explorer un donjon souterrain, ou qu'il s'échappe d'une prison.

Nous connaissons déjà tous les outils pour implémenter le projecteur. La pièce manquante est l'effet de cône de ce type de lumière. Regardons l'image suivante :

Image non disponible

La direction de la source lumineuse est définie par la flèche qui pointe directement vers le bas. Nous voulons que notre lumière n'ait d'effet que dans l'aire délimitée par les deux lignes rouges. Le produit scalaire vient encore une fois à notre secours. Nous définissons le cône de lumière comme l'angle entre chacune des lignes rouges et la direction de la lumière (c'est-à-dire la moitié de l'angle entre les lignes rouges). Nous pouvons prendre le cosinus « C » de cet angle et effectuer un produit scalaire entre la direction de la lumière « L » et le vecteur « V » entre l'origine de la lumière et le pixel. Si le résultat du produit scalaire est plus grand que « C » (rappel : le cosinus augmente lorsque l'angle diminue), alors l'angle entre « L » et « V » est plus petit que l'angle entre « L » et les deux lignes rouges qui définissent le cône de lumière (l'angle d'ouverture). Dans ce cas, nous voulons que le pixel reçoive de la lumière. Si l'angle est plus large, le pixel ne reçoit plus de lumière de cette source. Dans l'exemple ci-dessus, un produit scalaire entre « L » et « V » va produire un résultat plus petit que le produit scalaire entre « L » et n'importe laquelle des deux lignes rouges (il est assez évident que l'angle entre « L » et « V » est plus grand que l'angle entre « L » et les lignes rouges). Par conséquent, le pixel est hors du cône lumineux et n'est donc pas illuminé par le projecteur.

Si nous continuons avec cette approche « reçoit/ne reçoit pas de lumière », tout ce que nous allons obtenir c'est un projecteur extrêmement artificiel, avec une démarcation très nette entre les zones éclairées et les zones sombres. Elle projettera un cercle parfait dans le noir (s'il n'y a pas d'autre source lumineuse). Un projecteur plus réaliste aura une lumière diminuant progressivement au niveau des bords du cercle. Nous pouvons utiliser le produit scalaire, que nous avons calculé (afin de déterminer si le pixel est éclairé ou pas), comme facteur. Nous savons déjà que le produit scalaire vaut 1 (donc éclairage maximum) lorsque les vecteurs « L » et « V » sont égaux, mais nous rencontrons maintenant l'effet indésirable de la fonction cosinus. L'angle d'ouverture ne doit pas être trop large, sinon la lumière sera diffusée trop largement et perdra l'apparence d'un projecteur. Par exemple, définissons l'angle à 20 degrés. Le cosinus de 20 degrés est 0.939, mais l'intervalle [0.939, 1.0] est trop petit pour servir de facteur. Il n'y a pas assez d'écart et les résultats de l'interpolation ne seront pas perceptibles par l'œil. L'intervalle [0, 1] fournit de bien meilleurs résultats.

L'approche que nous allons utiliser est de faire correspondre l'intervalle plus petit, défini par l'angle d'ouverture, à l'intervalle plus large [0, 1]. Voici comment nous allons procéder :

Image non disponible

Le principe est très simple : on calcule le ratio entre l'intervalle le plus petit et l'intervalle le plus grand, puis on multiplie l'intervalle par ce ratio.

II. Explication du code

lighting_technique.h
Sélectionnez
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
struct SpotLight : public PointLight
{
    Vector3f Direction;
    float Cutoff;

    SpotLight()
    {
        Direction = Vector3f(0.0f, 0.0f, 0.0f);
        Cutoff = 0.0f;
    }
};

La structure définissant le projecteur est dérivée de PointLight et ajoute les deux attributs qui la différencient d'une lumière ponctuelle : un vecteur direction et une valeur d'arrêt. La valeur d'arrêt correspond à l'angle maximum entre la direction de la lumière et le vecteur entre la source et le pixel, pour les pixels sous l'éclairage du projecteur. Le projecteur n'a pas d'effet au-delà de la valeur d'arrêt. Nous avons aussi ajouté un tableau de positions pour le shader, à la classe LightingTechnique (non décrit ici). Ce tableau nous permet d'accéder au tableau de projecteurs dans le shader.

lighting.fs
Sélectionnez
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
struct SpotLight
{
    struct PointLight Base;
    vec3 Direction;
    float Cutoff;
};
...
uniform int gNumSpotLights;
...
uniform SpotLight gSpotLights[MAX_SPOT_LIGHTS];

Une structure similaire est ajoutée en GLSL, pour les lumières de type projecteur. Comme nous ne pouvons pas utiliser l'héritage comme en C++, nous utilisons une structure PointLight en tant que membre et ajoutons les nouveaux attributs. La différence importante ici est que dans le code C++, la valeur d'arrêt est l'angle lui-même, alors que dans le shader c'est le cosinus de cet angle. Le shader n'a besoin que du cosinus, donc il est plus efficace de le calculer une fois et pas pour chaque pixel. Nous définissons aussi un tableau de projecteurs et utilisons un compteur nommé « gNumSpotLights »pour permettre à l'application de définir le nombre de projecteurs réellement utilisés.

lighting.fs
Sélectionnez
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
vec4 CalcPointLight(struct PointLight l, vec3 Normal)
{
    vec3 LightDirection = WorldPos0 - l.Position;
    float Distance = length(LightDirection);
    LightDirection = normalize(LightDirection);

    vec4 Color = CalcLightInternal(l.Base, LightDirection, Normal);
    float Attenuation = l.Atten.Constant +
        l.Atten.Linear * Distance +
        l.Atten.Exp * Distance * Distance;

    return Color / Attenuation;
}

La fonction de lumière ponctuelle a subi une légère modification : elle prend maintenant une structure PointLight en paramètre, plutôt que l'accès direct au tableau global. Cela permet de partager facilement cette fonction avec les projecteurs. À part ça, pas de changement ici.

lighting.fs
Sélectionnez
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
vec4 CalcSpotLight(struct SpotLight l, vec3 Normal)
{
    vec3 LightToPixel = normalize(WorldPos0 - l.Base.Position);
    float SpotFactor = dot(LightToPixel, l.Direction);

    if (SpotFactor > l.Cutoff) {
        vec4 Color = CalcPointLight(l.Base, Normal);
        return Color * (1.0 - (1.0 - SpotFactor) * 1.0/(1.0 - l.Cutoff));
    }
    else {
        return vec4(0,0,0,0);
    }
}

Ici nous calculons l'effet du projecteur. Nous commençons en récupérant le vecteur entre la source lumineuse et le pixel. Comme c'est souvent le cas, nous le normalisons afin de le préparer pour le produit scalaire qui suit. Nous calculons donc le produit scalaire entre ce vecteur et la direction de la lumière (qui a déjà été normalisée par l'application), et récupérons le cosinus de l'angle entre eux deux. Ensuite, nous le comparons à la valeur d'arrêt. Celle-ci représente le cosinus de l'angle entre la direction de la lumière et le vecteur qui définit le cercle d'influence du projecteur. Si le cosinus est plus petit, cela signifie que l'angle entre la direction de la lumière et le vecteur de la source au pixel place le vecteur hors du cercle d'influence. Dans ce cas, la contribution de ce projecteur vaut zéro. Cela limitera le projecteur à un cercle plus ou moins large, dépendant de la valeur d'arrêt. Sinon, nous calculons la couleur de base comme si la lumière était une lumière ponctuelle. Puis, nous récupérons le produit scalaire que nous avons calculé précédemment (« SpotFactor ») et l'introduisons dans la formule calculée plus haut. Cela fournit le facteur qui va interpoler linéairement « SpotFactor » entre 0 et 1. Nous le multiplions par la couleur de la lumière ponctuelle et obtenons la couleur finale du projecteur.

lighting.fs
Sélectionnez
122.
123.
124.
125.
126.
...
for (int i = 0 ; i < gNumSpotLights ; i++) {
    TotalLight += CalcSpotLight(gSpotLights[i], Normal);
}
...

De manière similaire aux lumières ponctuelles, nous avons, dans le programme principal, une boucle qui accumule la contribution de tous les projecteurs à la couleur finale du pixel.

lighting_technique.cpp
Sélectionnez
367.
368.
369.
370.
371.
372.
373.
374.
375.
376.
377.
378.
379.
380.
381.
382.
383.
384.
void LightingTechnique::SetSpotLights(unsigned int NumLights, const SpotLight* pLights)
{
    glUniform1i(m_numSpotLightsLocation, NumLights);

    for (unsigned int i = 0 ; i < NumLights ; i++) {
        glUniform3f(m_spotLightsLocation[i].Color, pLights[i].Color.x, pLights[i].Color.y, pLights[i].Color.z);
        glUniform1f(m_spotLightsLocation[i].AmbientIntensity, pLights[i].AmbientIntensity);
        glUniform1f(m_spotLightsLocation[i].DiffuseIntensity, pLights[i].DiffuseIntensity);
        glUniform3f(m_spotLightsLocation[i].Position, pLights[i].Position.x, pLights[i].Position.y, pLights[i].Position.z);
        Vector3f Direction = pLights[i].Direction;
        Direction.Normalize();
        glUniform3f(m_spotLightsLocation[i].Direction, Direction.x, Direction.y, Direction.z);
        glUniform1f(m_spotLightsLocation[i].Cutoff, cosf(ToRadian(pLights[i].Cutoff)));
        glUniform1f(m_spotLightsLocation[i].Atten.Constant, pLights[i].Attenuation.Constant);
        glUniform1f(m_spotLightsLocation[i].Atten.Linear, pLights[i].Attenuation.Linear);
        glUniform1f(m_spotLightsLocation[i].Atten.Exp, pLights[i].Attenuation.Exp);
    }
}

Cette fonction met à jour le tableau de SpotLight dans le shader. C'est la même fonction que pour les lumières ponctuelles, avec deux choses en plus. Le vecteur direction de la lumière est aussi donné au shader, après normalisation. La valeur d'arrêt est aussi fournie en tant qu'angle par l'appelant, mais son cosinus est donné au shader (ce qui permet au shader de comparer le produit scalaire à cette valeur, directement). Notons que la fonction cosf() prend comme paramètre un angle en radians donc nous utilisons la macro ToRadian pour le convertir.

III. Sources

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources


Tutoriel 22 - Chargement de modèles en utilisant la bibliothèque Open Asset Import Library

  • I. Contexte
  • II. Explication du code
  • III. Sources
  • IV. Remerciements
  • Navigation

Dans ce tutoriel, nous allons découvrir une bibliothèque de chargement de modèles à partir de fichiers.

I. Contexte

Nous sommes arrivés jusqu'ici en utilisant des modèles générés manuellement. Comme vous vous en doutez, la méthode de spécifier manuellement la position et autres attributs pour chaque sommet d'un objet n'est pas très adaptée aux objets complexes. Ça va bien pour une boîte, une pyramide, et une simple surface en tuiles, mais qu'en est-il pour un visage humain ? Dans le monde des jeux vidéo et des applications commerciales, le procédé de création d'un maillage est pris en charge par des artistes, qui utilisent des applications telles que Blender, Maya et 3ds Max. Ces applications fournissent des outils avancés qui aident les artistes à créer des modèles extrêmement sophistiqués. Lorsque le modèle est achevé, il est enregistré dans un fichier, dans un des multiples formats disponibles. Le fichier contient la complète définition de la géométrie du modèle. Il peut maintenant être chargé dans un moteur de jeu (si tant est que le moteur supporte ce format particulier), et son contenu peut être utilisé pour remplir les tampons de sommets et d'indices, pour le rendu. Connaître la manière d'analyser le format de définition de la géométrie, et charger des modèles professionnels est crucial pour porter votre programmation 3D à un autre niveau.

Développer un analyseur par vous-même peut prendre beaucoup de votre temps. Si vous voulez être capable de charger des modèles depuis différentes sources, vous devez étudier chaque format et développer un analyseur spécifique à chaque fois. Certains formats sont simples, mais d'autres sont très complexes et vous pourriez finir en passant beaucoup trop de temps sur quelque chose qui s'éloigne du cœur de la programmation 3D. Par conséquent, l'approche décrite dans ce tutoriel est d'utiliser une bibliothèque externe pour prendre en charge l'analyse et le chargement des modèles à partir de fichiers.

La bibliothèque Open Asset Import, ou Assimp, est une bibliothèque open source, qui peut gérer de nombreux formats 3D, incluant les plus populaires. Elle est portable et disponible à la fois sous Windows et Linux. Elle est très simple à utiliser et s'intègre dans des applications écrites en C/C++.

Il n'y a pas beaucoup de théorie dans ce tutoriel. Voyons directement comment intégrer Assimp dans nos applications 3D. (Avant de débuter, vérifiez que vous avez installé Assimp à partir du lien ci-dessus.)

II. Explication du code

Télécharger Sélectionnez
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
class Mesh
{
public:
    Mesh();

    ~Mesh();

    bool LoadMesh(const std::string& Filename);

    void Render();

private:
    bool InitFromScene(const aiScene* pScene, const std::string& Filename);
    void InitMesh(unsigned int Index, const aiMesh* paiMesh);
    bool InitMaterials(const aiScene* pScene, const std::string& Filename);
    void Clear();

#define INVALID_MATERIAL 0xFFFFFFFF

    struct MeshEntry {
        MeshEntry();

        ~MeshEntry();

        bool Init(const std::vector& Vertices,
        const std::vector& Indices);

        GLuint VB;
        GLuint IB;
        unsigned int NumIndices;
        unsigned int MaterialIndex;
    };

    std::vector m_Entries;
    std::vector m_Textures;
};

La classe Mesh représente l'interface entre Assimp et notre application OpenGL. Un objet de cette classe prend un nom de fichier comme paramètre à la méthode LoadMesh(), utilise Assimp pour charger le modèle, puis crée les tampons de sommets et d'indices et les objets Texture, qui contiennent les données du modèle, dans un format que notre programme comprend. Afin de dessiner le maillage, nous utiliserons la méthode Render(). La structure interne de la classe Mesh correspond à la manière dont Assimp charge les modèles. Assimp utilise un objet aiScene pour représenter le maillage chargé. Cet objet aiScene contient les structures de maillage qui encapsulent des parties du modèle. Il doit y avoir au moins une structure de maillage dans l'objet aiScene. Les modèles complexes peuvent contenir de multiples maillages. L'entrée m_Entries de la classe Mesh est un vecteur de la structure MeshEntry, où chaque structure correspond à un maillage de l'objet aiScene. Cette structure contient le tampon de sommets, le tampon d'indices, et l'indice du matériau. Pour l'instant, un matériau est simplement une texture, et comme les maillages peuvent partager des matériaux, nous avons un vecteur séparé pour ceux-ci (m_Textures). MeshEntry::MaterialIndex pointe sur une des textures de m_Textures.

Télécharger Sélectionnez
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
bool Mesh::LoadMesh(const std::string& Filename)
{
    // Release the previously loaded mesh (if it exists)
    Clear();

    bool Ret = false;
    Assimp::Importer Importer;

    const aiScene* pScene = Importer.ReadFile(Filename.c_str(), aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_FlipUVs);

    if (pScene) {
        Ret = InitFromScene(pScene, Filename);
    }
    else {
        printf("Error parsing '%s': '%s'\n", Filename.c_str(), Importer.GetErrorString());
    }

    return Ret;
}

Cette méthode est le point d'entrée du chargement du maillage. Nous créons une instance de la classe Assimp::Importer sur la pile et appelons sa méthode ReadFile(). Cette méthode prend deux paramètres : le chemin complet d'accès au fichier du modèle, et un masque d'actions en post-traitement. Assimp est capable d'effectuer de nombreuses actions utiles sur les modèles chargés. Par exemple, il peut générer les normales pour les modèles qui n'en ont pas, optimiser la structure du modèle pour améliorer les performances, etc. La liste complète des options est disponible ici. Dans ce tutoriel, nous utilisons trois options :

  • aiProcess_Triangulate, qui convertit les maillages faits de polygones non triangles en maillages constitués uniquement de polygones triangles. Par exemple, un maillage fait de rectangles peut être converti en maillage de triangles en créant deux triangles par rectangle ;
  • la seconde option, aiProcess_GenSmoothNormals, génère des normales aux sommets, dans le cas où le modèle original n'en contiendrait pas encore. Notez que les options de post-traitement sont des masques de bits, vous permettant d'appliquer diverses options en les combinant avec un OU logique ;
  • la dernière option, aiProcess_FlipUVs, inverse les coordonnées de texture le long de l'axe Y. Cette option est requise pour dessiner correctement le modèle de Quake utilisé pour la démonstration. Vous devrez probablement vérifier les options que vous allez utiliser, en fonction des modèles d'entrée. Si le maillage a été chargé correctement, vous obtenez un pointeur sur un objet aiScene. Cet objet contient toutes les données du modèle, divisées en aiMesh. Ensuite nous appelons la méthode InitFromScene(), pour initialiser l'objet Mesh.
Télécharger Sélectionnez
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
bool Mesh::InitFromScene(const aiScene* pScene, const std::string& Filename)
{
    m_Entries.resize(pScene->mNumMeshes);
    m_Textures.resize(pScene->mNumMaterials);

    // Initialise les maillages de la scène, un par un
    for (unsigned int i = 0 ; i < m_Entries.size() ; i++) {
        const aiMesh* paiMesh = pScene->mMeshes[i];
        InitMesh(i, paiMesh);
    }

    return InitMaterials(pScene, Filename);
}

Nous commençons l'initialisation de l'objet Mesh, en définissant l'espace occupé par les vecteurs de maillages et de textures, pour tous les maillages et matériaux dont nous allons avoir besoin. Les nombres sont donnés par les membres mNumMeshes et mNumMaterials de l'objet aiScene, respectivement. Ensuite, nous parcourons le tableau mMeshes de l'objet aiScene, et initialisons les entrées de maillage une à une. Enfin, nous initialisons les matériaux.

Télécharger Sélectionnez
111.
112.
113.
114.
115.
116.
117.
void Mesh::InitMesh(unsigned int Index, const aiMesh* paiMesh)
{
    m_Entries[Index].MaterialIndex = paiMesh->mMaterialIndex;

    std::vector Vertices;
    std::vector Indices;
    ...

Nous commençons l'initialisation du maillage, en stockant l'indice de son matériau. Il sera utilisé lors du rendu, pour utiliser la bonne texture. Ensuite, nous créons deux vecteurs STL, pour stocker le contenu des tampons de sommets et d'indices. Le vecteur STL possède la caractéristique intéressante de stocker son contenu dans un tampon continu en mémoire. Cela facilite le chargement des données dans un tampon OpenGL (en utilisant la fonction glBufferData()).

Télécharger Sélectionnez
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
    const aiVector3D Zero3D(0.0f, 0.0f, 0.0f);

    for (unsigned int i = 0 ; i < paiMesh->mNumVertices ; i++) {
        const aiVector3D* pPos = &(paiMesh->mVertices[i]);
        const aiVector3D* pNormal = &(paiMesh->mNormals[i]) : &Zero3D;
        const aiVector3D* pTexCoord = paiMesh->HasTextureCoords(0) ? &(paiMesh->mTextureCoords[0][i]) : &Zero3D;

        Vertex v(Vector3f(pPos->x, pPos->y, pPos->z),
                Vector2f(pTexCoord->x, pTexCoord->y),
                Vector3f(pNormal->x, pNormal->y, pNormal->z));

        Vertices.push_back(v);
    }
    ...

Ici, nous préparons le contenu du tampon de sommets, en remplissant le vecteur de Vertex. Nous utilisons les attributs suivants de la classe aiMesh :

  1. mNumVertices : le nombre de sommets ;
  2. mVertices : un tableau de mNumVertices aiVector3D contenant les positions ;
  3. mNormals : un tableau de mNumVertices aiVector3D contenant les normales ;
  4. mTextureCoords : un tableau de mNumVertices aiVector3D contenant les coordonnées de texture. C'est en fait un tableau à deux dimensions, car chaque sommet peut avoir plusieurs coordonnées de texture.

Donc, nous avons en fait trois tableaux séparés, contenant tout ce dont nous avons besoin pour les sommets, et nous devons prendre chaque attribut dans le tableau correspondant, afin de remplir une structure Vertex. Cette structure est ensuite mise en fin du vecteur de sommets (en gardant donc le même indice que dans les trois tableaux de aiMesh). Notez que certains modèles n'ont pas de coordonnées de texture, donc, avant d'accéder au tableau mTextureCoords (et par la même occasion, risquer de causer une erreur de segmentation), nous vérifions si les coordonnées de texture existent, en appelant la méthode HasTextureCoords(). En plus de cela, un maillage peut contenir plusieurs coordonnées de texture par sommet. Dans ce tutoriel, nous prenons le chemin facile, en n'utilisant que la première coordonnée de texture. Ainsi, on n'accède qu'à la première ligne du tableau mTextureCoords. Par conséquent, la méthode HasTextureCoords() est toujours appelée pour la première ligne. Si une coordonnée de texture n'existe pas, la structure Vertex sera initialisée avec le vecteur zéro.

Télécharger Sélectionnez
132.
133.
134.
135.
136.
137.
138.
139.
    for (unsigned int i = 0 ; i < paiMesh->mNumFaces ; i++) {
        const aiFace& Face = paiMesh->mFaces[i];
        assert(Face.mNumIndices == 3);
        Indices.push_back(Face.mIndices[0]);
        Indices.push_back(Face.mIndices[1]);
        Indices.push_back(Face.mIndices[2]);
    }
    ...

Ensuite nous créons le tampon d'indices. Le membre mNumFaces de la classe aiMesh nous indique le nombre de polygones existant, et le tableau mFaces contient leurs données (qui sont les indices des sommets). Nous vérifions que le nombre d'indices dans le polygone est bien trois (en chargeant le modèle, nous avons demandé à ce qu'il soit converti en triangles, mais c'est toujours bon de vérifier). Ensuite, nous extrayons les indices du tableau mIndices et les mettons en fin du vecteur d'indices.

Télécharger Sélectionnez
140.
141.
    m_Entries[Index].Init(Vertices, Indices);
}

Enfin, la structure MeshEntry est initialisée, en utilisant les vecteurs de sommets et d'indices. Il n'y a rien de nouveau dans la méthode MeshEntry::Init(), qui n'est donc pas listée ici. Elle utilise glGenBuffers(), glBindBuffer(), et glBufferData(), pour créer et remplir les tampons d'indices et de sommets. Regardez le fichier source pour plus de détails.

Télécharger Sélectionnez
143.
144.
145.
146.
147.
bool Mesh::InitMaterials(const aiScene* pScene, const std::string& Filename)
{
    for (unsigned int i = 0 ; i < pScene->mNumMaterials ; i++) {
        const aiMaterial* pMaterial = pScene->mMaterials[i];
       ...

Cette méthode charge toutes les textures utilisées par le modèle. L'attribut mNumMaterials de l'objet aiScene contient le nombre de matériaux, et mMaterials est un tableau de pointeurs sur des structures aiMaterialaiMaterialaiMaterial (de cette taille). La structure aiMaterial est une bestiole complexe, mais cache sa complexité derrière un nombre réduit de méthodes. En général, un matériau est organisé comme une pile de textures, et entre deux textures consécutives les fonctions de mélange et de puissance doivent être appliquées. Par exemple, la fonction de mélange peut nous dire d'ajouter la couleur de deux textures, et la fonction de puissance peut nous dire de diviser le résultat par deux. Les fonctions de mélange et de force font partie de la structure aiMaterial, et peuvent être récupérées. Pour nous faciliter la vie, et rester en accord avec la manière de fonctionner de notre shader d'éclairage, nous ignorons les fonctions de mélange et de puissance, et utilisons la texture telle quelle.

Télécharger Sélectionnez
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
        m_Textures[i] = NULL;
        if (pMaterial->GetTextureCount(aiTextureType_DIFFUSE) > 0) {
            aiString Path;

            if (pMaterial->GetTexture(aiTextureType_DIFFUSE, 0, &Path, NULL, NULL, NULL, NULL, NULL) == AI_SUCCESS) {
                std::string FullPath = Dir + "/" + Path.data;
                m_Textures[i] = new Texture(GL_TEXTURE_2D, FullPath.c_str());

                if (!m_Textures[i]->Load()) {
                    printf("Error loading texture '%s'\n", FullPath.c_str());
                    delete m_Textures[i];
                    m_Textures[i] = NULL;
                    Ret = false;
                }
            }
        }
        ...

Un matériau peut contenir plusieurs textures, et toutes ne contiennent pas des couleurs. Par exemple, une texture peut définir un relief, contenir des normales ou encore des déplacements, etc. Comme notre shader d'éclairage n'utilise pour l'instant qu'une seule texture pour tous les types de lumières, nous ne sommes intéressés que par la texture diffuse. Par conséquent, nous vérifions combien de textures diffuses existent, en utilisant la méthode aiMaterial::GetTextureCount(). Cette méthode prend le type de texture en paramètre, et retourne le nombre de textures de ce type spécifique. Si au moins une texture diffuse est disponible, nous la récupérons en utilisant la méthode aiMaterial::GetTexture(). Le premier paramètre de cette méthode est le type. Ensuite vient l'indice et nous utilisons toujours 0. Après cela, nous devons spécifier l'adresse d'une chaîne de caractères, où le nom du fichier de cette texture va être récupéré. Enfin, il y a cinq paramètres d'adresses, qui permettent de récupérer diverses configurations de la texture, telles que le facteur de mélange, le mode d'application, l'opération de texture, etc. Ceux-ci sont optionnels, et nous les ignorons pour l'instant, en passant NULL à chacun. Nous ne sommes intéressés que par le nom du fichier de la texture, et nous le concaténons au dossier où le modèle se trouve. Le dossier avait été récupéré au début de la fonction (non listée ici), et nous supposons que la texture et le modèle se trouvent dans le même dossier. Si la structure du dossier est plus compliquée, vous devrez chercher la texture autre part. Nous créons notre objet texture et le chargeons.

Télécharger Sélectionnez
187.
188.
189.
190.
191.
192.
193.
194.
       if (!m_Textures[i]) {
          m_Textures[i] = new Texture(GL_TEXTURE_2D, "../Content/white.png");
          Ret = m_Textures[i]->Load();
       }
    }

    return Ret;
}

Le morceau de code ci-dessus est un petit contournement d'un problème que vous pourrez rencontrer, si vous commencez à charger des modèles trouvés sur Internet. Parfois, le modèle n'inclut pas de texture et dans ce cas, vous ne verrez rien, car la couleur qui est échantillonnée à partir d'une texture inexistante est le noir, par défaut. Une manière de gérer cela est de détecter le cas, et de le traiter comme un cas spécial dans le shader ou dans un shader dédié. Ce tutoriel applique une approche plus simple, en chargeant une texture contenant un simple texel blanc (vous trouverez cette texture avec les sources attachées). Cela fera que la couleur par défaut des pixels sera le blanc. Ce ne sera probablement pas très joli par contre, au moins, vous verrez quelque chose. Cette texture prend vraiment très peu de place, et nous permet d'utiliser le même shader dans tous les cas.

Télécharger Sélectionnez
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
void Mesh::Render()
{
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    glEnableVertexAttribArray(2);

    for (unsigned int i = 0 ; i < m_Entries.size() ; i++) {
        glBindBuffer(GL_ARRAY_BUFFER, m_Entries[i].VB);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)12);
        glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)20);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_Entries[i].IB);

        const unsigned int MaterialIndex = m_Entries[i].MaterialIndex;

        if (MaterialIndex < m_Textures.size() && m_Textures[MaterialIndex]) {
            m_Textures[MaterialIndex]->Bind(GL_TEXTURE0);
        }

        glDrawElements(GL_TRIANGLES, m_Entries[i].NumIndices, GL_UNSIGNED_INT, 0);
    }

    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
    glDisableVertexAttribArray(2);
}

Cette fonction encapsule le dessin d'un maillage, et le détache de l'application principale (dans les précédents tutoriels, il faisait partie du code de l'application elle-même). Le tableau m_Entries est parcouru, et les tampons de sommets et d'indices de chaque nœud sont liés au contexte OpenGL. L'indice du matériau utilisé par le nœud permet de récupérer l'objet texture, à partir du tableau m_Textures, et la texture est liée elle aussi. Enfin la commande de dessin est exécutée. Maintenant vous pouvez avoir plusieurs objets Mesh chargés depuis des fichiers, et les dessiner un par un, en appelant la méthode Mesh::Render.

Télécharger Sélectionnez
112.
glEnable(GL_DEPTH_TEST);

La dernière chose qu'il nous reste à étudier est quelque chose que nous avons laissé de côté lors des tutoriels précédents. Si vous continuez, et chargez des modèles en utilisant le code ci-dessus, vous observerez probablement des anomalies visuelles dans votre scène. La raison est que les triangles qui sont plus éloignés de la caméra sont dessinés au-dessus de ceux qui sont plus proches. Afin de prévenir cela, nous devons activer le fameux test de profondeur (aussi appelé Z-test). Lorsque le test de profondeur est activé, le rasterizer compare, avant son rendu, la profondeur de chaque pixel, par rapport à celle du pixel déjà dessiné à la même position sur l'écran. Le pixel dont la couleur est finalement choisie est celui qui a « gagné » le test de profondeur (c'est-à-dire le plus proche de la caméra). Le test de profondeur n'est pas activé par défaut, et la ligne ci-dessus s'occupe de cela (comme part de l'initialisation d'OpenGL dans la fonction GLUTBackendRun()). C'est une des trois portions de code nécessaires pour le test profondeur (voir plus bas).

Télécharger Sélectionnez
73.
glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH);

La seconde portion est l'initialisation du tampon de profondeur. Afin de pouvoir comparer la profondeur entre deux pixels, la profondeur de « l'ancien » pixel doit être stockée quelque part (le tampon du « nouveau » pixel est disponible, car passé au vertex shader). Dans ce but, nous avons un tampon spécial connu sous le nom de tampon de profondeur (ou Z buffer). Il a les mêmes dimensions que l'écran, afin que chaque pixel du tampon de couleurs ait un emplacement correspondant dans le tampon de profondeur. Cet emplacement stocke toujours la profondeur du pixel le plus proche et il est utilisé dans le test de profondeur pour la comparaison.

Télécharger Sélectionnez
101.
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

La dernière chose que nous devons faire est de vider le tampon de profondeur au début de chaque image rendue. Si nous ne le faisons pas, le tampon contiendra les vieilles valeurs de l'image précédente, et la profondeur des pixels de la nouvelle image sera comparée à la profondeur des pixels de l'image précédente. Comme vous pouvez l'imaginer, cela va causer de sérieuses corruptions (essayez !). La fonction glClear() prend un masque de bits des tampons sur lesquels elle doit opérer. Jusqu'ici, nous n'avions vidé que le tampon de couleurs. Il est maintenant temps de vider aussi le tampon de profondeur.

III. Sources

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources


Tutoriel 23 - Shadow-mapping

Partie 1

  • I. Contexte
  • II. Explication du code
  • III. Sources
  • IV. Remerciements
  • Navigation

Le concept d'ombre est inséparable du concept de lumière : nous avons besoin de lumière afin de pouvoir projeter une ombre. Il y a de nombreuses techniques pour générer des ombres, et dans ce tutoriel en deux parties, nous allons étudier l'une des plus simples et basiques : le Shadow Mapping.

I. Contexte

Lorsqu'arrive le moment de la rastérisation et des ombres, la question qui vous vient à l'esprit est : est-ce que ce pixel est dans l'ombre, ou non ? Posons cette question différemment : est-ce que le chemin de la source lumineuse vers le pixel passe au travers d'un autre objet, ou non ? Si c'est le cas, le pixel est probablement dans l'ombre (en supposant que l'objet en question ne soit pas transparent…), et s'il ne le fait pas, le pixel n'est pas dans l'ombre. En un sens, cette question est proche de la question que nous nous posions dans le précédent tutoriel : comment s'assurer que, lorsque deux objets se chevauchent, nous voyions le plus proche ? Si nous plaçons, pour un instant, la caméra à l'origine de la lumière, les deux questions n'en sont plus qu'une. Nous voulons que les pixels qui échouent le test de profondeur (c'est-à-dire ceux qui sont trop éloignés, ou qui ont d'autres pixels devant eux) soient dans l'ombre. Seuls les pixels qui gagnent le test de profondeur doivent être dans la lumière. Ils sont ceux qui sont en contact direct avec la source lumineuse et rien ne les dissimule. Il s'agit là du cœur de l'idée derrière le shadow mapping.

Il semble donc que le test de profondeur peut nous aider à déterminer si un pixel est dans l'ombre, mais il y a un problème : la caméra et la lumière ne sont pas toujours situées à la même position. Le test de profondeur est habituellement utilisé pour résoudre les problèmes de visibilité, depuis le point de vue de la caméra donc, comment pouvons-nous l'utiliser pour la détection des ombres, lorsque la source lumineuse est placée plus loin ? La solution est de dessiner la scène deux fois. Tout d'abord, du point de vue de la source lumineuse. Les résultats de cette passe de rendu n'atteignent pas le tampon de couleurs. Par contre, les valeurs de profondeur les plus proches sont dessinées dans un tampon de profondeur, créé par l'application (au lieu de celui automatiquement généré par GLUT). Durant la seconde passe, la scène est dessinée, comme d'habitude, selon le point de vue de la caméra. Le tampon de profondeur que nous avons créé est lié au fragment shader, en lecture. Pour chaque pixel, nous récupérons la profondeur correspondante dans ce tampon de profondeur. Nous calculons aussi la profondeur de ce pixel du point de vue de la lumière. Parfois, les deux valeurs de profondeur seront identiques. C'est le cas où ce pixel était plus proche de la lumière, donc sa valeur de profondeur a fini dans le tampon de profondeur. Si ça arrive, nous considérons ce pixel comme éclairé, et calculons sa couleur comme d'habitude. Si les valeurs de profondeur sont différentes, cela signifie qu'un autre pixel couvrait celui-ci, du point de vue de la source lumineuse. Dans ce cas, nous ajoutons le facteur d'ombre au calcul de la lumière pour simuler l'effet d'ombre. Regardons l'image suivante :

Image non disponible

Notre scène est constituée de deux objets : la surface, et le cube. La source lumineuse est située dans le coin en haut à gauche, et est dirigée vers ce cube. Pendant la première passe, nous dessinons dans le tampon de profondeur, du point de vue de la source lumineuse. Attardons-nous sur les trois points A, B, et C. Lorsque B est dessiné, sa valeur de profondeur va dans le tampon de profondeur. La raison est qu'il n'y a rien, entre le point et la lumière. Par défaut, il est le point le plus proche de la lumière sur cette ligne. Par contre, lorsque A et C sont dessinés, ils « bataillent » exactement sur le même point du tampon de profondeur. Les deux points sont sur la même ligne depuis la source lumineuse, donc après que la projection en perspective a été appliquée, le rastériseur trouve que deux points doivent aller sur le même pixel à l'écran. C'est le test de profondeur, C le « gagne ».

Durant la seconde passe, nous dessinons la surface et le cube, du point de vue de la caméra. Dans notre shader d'éclairage par pixel, en plus de tout ce que nous faisons déjà, nous allons aussi calculer la distance entre la source lumineuse et le pixel, et la comparer à la valeur correspondante dans le tampon de profondeur. Lorsque nous calculons le point B, les deux valeurs devraient être globalement les mêmes (des différences sont attendues, dues aux interpolations, ainsi qu'aux problèmes de précision sur les nombres flottants). Ainsi, nous déterminons que B n'est pas dans l'ombre, et agissons en conséquence. Lorsque nous rastérisons A, nous trouvons que la valeur de profondeur stockée est nettement plus petite que la profondeur de A. Par conséquent, nous déterminons que A est dans l'ombre, et appliquons un certain facteur d'ombre pour le dessiner plus sombre que d'habitude.

Les sources de ce tutoriel incluent un simple maillage en rectangle, pouvant être utilisé pour afficher la shadow map. Le rectangle est constitué de deux triangles, et les coordonnées de texture sont définies pour que la texture couvre toute la surface. Lorsque le rectangle est dessiné, les coordonnées de texture sont interpolées par le rastériseur, vous permettant d'échantillonner une texture complète, et de l'afficher sur l'écran.

II. Explication du code

Télécharger Sélectionnez
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
class ShadowMapFBO
{
    public:
        ShadowMapFBO();

        ~ShadowMapFBO();

        bool Init(unsigned int WindowWidth, unsigned int WindowHeight);

        void BindForWriting();

        void BindForReading(GLenum TextureUnit);

    private:
        GLuint m_fbo;
        GLuint m_shadowMap;
};

Les résultats du pipeline 3D d'OpenGL finissent dans quelque chose qui est appelé « Framebuffer Object », tampon d'image en français (et aussi agrégé FBO). Ce concept englobe le tampon de couleurs (qui est affiché sur l'écran), le tampon de profondeur, ainsi que quelques autres tampons pour d'autres utilisations. Lorsque glutInitDisplayMode() est appelée, elle crée le tampon d'image par défaut, en utilisant les paramètres spécifiés. Ce tampon d'image est géré par le système de fenêtrage et ne peut pas être détruit par OpenGL. En plus du tampon d'image par défaut, une application peut créer des FBO elle-même. Ces objets peuvent être manipulés et utilisés dans diverses techniques, sous le contrôle de l'application. La classe ShadowMapFBO fournit une interface simple d'utilisation vers un FBO, qui va être utilisée pour la technique de shadow mapping. En interne, cette classe contient deux identifiants OpenGL. L'identifiant « m_fbo » représente le FBO. Le FBO encapsule l'état complet du tampon d'image. Une fois cet objet créé et configuré correctement, nous pouvons changer de tampon d'image simplement en attachant un objet différent.

Seul le tampon d'image par défaut peut être utilisé pour dessiner quelque chose sur l'écran. Les tampons d'image créés par l'application ne peuvent être utilisés que pour du rendu « hors écran ». Cela peut être une passe de rendu intermédiaire (par exemple : notre tampon de shadow mapping), qui sera utilisée plus tard pour la passe de rendu « réel » sur l'écran.

En lui-même, le tampon d'image n'est qu'un espace réservé. Pour le rendre utilisable, nous devons attacher des textures, à un ou plusieurs points d'attache. Les textures contiennent l'espace de stockage réel du tampon d'image. OpenGL définit les points d'attache suivants :

  1. COLOR_ATTACHMENTi : la texture qui sera attachée ici recevra la couleur provenant du fragment shader. Le suffixe « i » signifie qu'il peut y avoir plusieurs textures attachées aux attaches de couleur simultanément. Il y a un mécanisme dans le fragment shader permettant le rendu dans plusieurs tampons de couleur en même temps ;
  2. DEPTH_ATTACHMENT : la texture qui sera attachée ici recevra les résultats du test de profondeur ;
  3. STENCIL_ATTACHMENT : la texture attachée ici servira de tampon de découpe. Le tampon de découpe permet de limiter la zone de rastérisation, et peut être utilisé dans diverses techniques ;
  4. DEPTH_STENCIL_ATTACHMENT : celui-ci est simplement une combinaison d'un tampon de profondeur et d'un tampon de stencil, car les deux sont souvent utilisés conjointement.

Pour la technique de shadow mapping, nous n'avons besoin que d'un tampon de profondeur. Le membre « m_shadowMap » est l'identifiant de la texture qui sera attachée au point DEPTH_ATTACHMENT. La classe ShadowMapFBO fournit aussi quelques méthodes qui seront utilisées dans la fonction principale de dessin. Nous appellerons BindForWriting() avant le dessin dans la shadow map, et BindForReading() lors du début de la seconde passe de dessin.

Télécharger Sélectionnez
43.
glGenFramebuffers(1, &m_fbo);

Ici, nous créons le FBO. De même que pour les textures et les tampons, nous donnons l'adresse d'un tableau de GLuint et sa taille. Le tableau est rempli avec les identifiants.

Télécharger Sélectionnez
46.
47.
48.
49.
50.
51.
52.
glGenTextures(1, &m_shadowMap);
glBindTexture(GL_TEXTURE_2D, m_shadowMap);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, WindowWidth, WindowHeight, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

Ensuite, nous créons la texture qui va servir de shadow map. De manière générale, c'est une texture 2D standard, avec quelques options de configuration spécifiques pour la rendre utilisable dans ce contexte :

  1. Le format interne est GL_DEPTH_COMPONENT. C'est différent des précédentes utilisations de cette fonction où le format interne est habituellement un des types de couleurs (par exemple : RGB). GL_DEPTH_COMPONENT signifie un seul nombre flottant qui représente la profondeur normalisée ;
  2. Le dernier paramètre de glTexImage2D est nul. Cela signifie que nous ne fournissons pas de données pour initialiser le tampon. C'est logique, sachant que nous voulons que le tampon contienne les valeurs de profondeur de chaque frame, et chaque frame est différente. À chaque fois que nous entamons une nouvelle frame, nous utilisons glClear() pour vider le tampon. C'est toute l'initialisation dont nous avons besoin pour le contenu ;
  3. Nous disons à OpenGL que si les coordonnées de texture dépassent les bornes, nous les remettons dans l'intervalle [0, 1]. Cela arrive lorsque la fenêtre de projection depuis le point de vue de la caméra contient plus que la fenêtre de projection du point de vue de la source lumineuse. Pour éviter des artefacts étranges, tels que l'ombre qui se répète autre part (dû à l'enroulement), nous imposons qu'elles soient remises dans cet intervalle.
Télécharger Sélectionnez
54.
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);

Nous avons généré le FBO, l'objet de texture, et nous avons configuré cet objet pour le shadow mapping. Maintenant, nous devons attacher l'objet de texture au FBO. La première chose à faire pour cela est d'activer le FBO. Cela le définit comme « courant », et toutes les opérations pour FBO à venir s'appliqueront à celui-ci. Cette fonction prend l'identifiant du FBO et la cible voulue. La cible peut être GL_FRAMEBUFFER, GL_DRAW_FRAMEBUFFER ou GL_READ_FRAMEBUFFER. GL_READ_FRAMEBUFFER est utilisé lorsque nous voulons lire depuis le FBO en utilisant glReadPixels() (pas dans ce tutoriel). GL_DRAW_FRAMEBUFFER est utilisé lorsque nous voulons dessiner dans le FBO. Lorsque nous utilisons GL_FRAMEBUFFER, et l'état d'écriture, et l'état de lecture sont mis à jour et c'est la méthode recommandée pour initialiser le FBO. Nous allons utiliser GL_DRAW_FRAMEBUFFER lorsque nous commencerons effectivement à dessiner.

Télécharger Sélectionnez
55.
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_shadowMap, 0);

Ici, nous attachons la texture de shadow map au point d'attache de profondeur du FBO. Le dernier paramètre de cette fonction indique quelle couche de mipmap utiliser. Le mipmapping est une fonctionnalité de placage de texture, où une texture est représentée à différentes résolutions, en commençant par la résolution la plus haute, au mipmap 0, et en allant vers des résolutions inférieures, au mipmaps 1 à N. La combinaison de textures avec des mipmaps et le filtre trilinéaire fournit des résultats plus jolis, lors de la combinaison de texels provenant de niveaux de mipmap voisins (lorsqu'aucun niveau ne convient parfaitement). Ici nous avons un unique niveau de mipmap, nous utilisons donc 0. Nous fournissons l'identifiant de la shadow map en quatrième paramètre. Si nous utilisons 0 ici, nous détachons la texture du point d'attache (profondeur, dans notre cas).

Télécharger Sélectionnez
58.
59.
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);

Comme nous n'allons pas dessiner dans le tampon de couleurs (seulement dans celui de profondeur), nous le spécifions explicitement avec les appels ci-dessus. Par défaut, le tampon de couleurs est attaché au point GL_COLOR_ATTACHMENT0, mais notre FBO ne contient même pas de tampon de couleurs. Par conséquent, il est mieux de dire explicitement notre intention à OpenGL. Les paramètres valides pour ces fonctions sont GL_NONE et GL_COLOR_ATTACHMENT0 à GL_COLOR_ATTACHMENTm, où « m » vaut GL_MAX_COLOR_ATTACHMENTS - 1. Ces paramètres sont valides uniquement pour des FBO. Si le tampon d'image par défaut est utilisé, les paramètres valides sont alors GL_NONE, GL_FRONT_LEFT, GL_FRONT_RIGHT, GL_BACK_LEFT et GL_BACK_RIGHT. Ces paramètres vous permettent de dessiner directement dans les tampons avant et arrière (où chacun à un tampon droite et gauche). Nous définissons aussi le tampon de lecture à GL_NONE (souvenez-vous, nous n'allons pas appeler les fonctions glReadPixel()). C'est principalement pour éviter les problèmes liés aux GPU ne supportant qu'OpenGL 3.x et pas 4.x.

Télécharger Sélectionnez
61.
62.
63.
64.
65.
66.
GLenum Status = glCheckFramebufferStatus(GL_FRAMEBUFFER);

if (Status != GL_FRAMEBUFFER_COMPLETE) {
    printf("FB error, status: 0x%x\n", Status);
    return false;
}

Lorsque la configuration du FBO est terminée, il est très important de vérifier que son état est ce que la spécification d'OpenGL définit comme « complet ». Cela signifie qu'aucune erreur n'a été détectée, et que le tampon d'image peut maintenant être utilisé. Le code ci-dessus vérifie cela.

Télécharger Sélectionnez
72.
73.
74.
75.
void ShadowMapFBO::BindForWriting()
{
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo);
}

Nous allons avoir besoin de passer du rendu dans la shadow map au rendu dans le tampon d'image par défaut. Dans la seconde passe, nous avons aussi besoin d'attacher notre shadow map en entrée. Cette fonction et la suivante vont nous faciliter ces tâches. La fonction ci-dessus lie le FBO en écriture, comme nous l'avons fait auparavant. Nous l'appellerons avant la première passe de rendu…

Télécharger Sélectionnez
78.
79.
80.
81.
82.
void ShadowMapFBO::BindForReading(GLenum TextureUnit)
{
    glActiveTexture(TextureUnit);
    glBindTexture(GL_TEXTURE_2D, m_shadowMap);
}

… Et cette fonction sera appelée avant la seconde passe, pour lier la shadow map en lecture.

Notez que nous lions spécifiquement l'objet de texture, plutôt que le FBO lui-même.

Cette fonction prend en paramètre l'unité de texture à laquelle la shadow map doit être liée. L'indice de l'unité de texture doit être synchronisé avec le shader (car le shader possède une variable uniforme sampler2D pour accéder à la texture). Il est très important de noter qu'alors que glActiveTexture() prend l'indice de la texture en tant qu'énumération (c'est-à-dire GL_TEXTURE0, GL_TEXTURE1, etc.), le shader n'a lui besoin que de l'indice lui-même (0, 1, etc.). Cela peut être la source de nombreux bogues (croyez-moi, je le sais…).

Télécharger Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
#version 330

layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
layout (location = 2) in vec3 Normal;

uniform mat4 gWVP;

out vec2 TexCoordOut;

void main()
{
    gl_Position = gWVP * vec4(Position, 1.0);
    TexCoordOut = TexCoord;
}

Nous allons utiliser le même programme de shader pour les deux passes. Le vertex shader sera utilisé par les deux passes, alors que le fragment shader ne sera utilisé que par la seconde passe. Comme nous désactivons l'écriture dans le tampon de couleurs durant la première passe, le fragment shader sera tout simplement inutilisé. Le vertex shader ci-dessus est très simple. Il génère les coordonnées dans l'espace de découpe, en multipliant la position en coordonnées locales par la matrice WVP (World View Position), et transmet les coordonnées de texture. Pendant la première passe, les coordonnées de texture sont redondantes (du fait de l'absence de fragment shader). Cependant, cela n'a pas d'impact réel, et il est plus simple de partager le vertex shader. Comme vous pouvez le voir, le fait que ce soit la passe de profondeur ou la passe de rendu réel ne fait aucune différence, du point de vue du shader. Ce qui diffère, c'est que l'application donne une matrice WVP correspondant au point de vue de la lumière, pendant la première passe, et une matrice WVP correspondant au point de vue de la caméra pendant la seconde passe. Dans la seconde passe, nous avons aussi besoin des coordonnées de texture dans le fragment shader, car nous échantillonnons depuis la shadow map (qui est alors une entrée du shader).

Télécharger Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
#version 330

in vec2 TexCoordOut;
uniform sampler2D gShadowMap;

out vec4 FragColor;

void main()
{
    float Depth = texture(gShadowMap, TexCoordOut).x;
    Depth = 1.0 - (1.0 - Depth) * 25.0;
    FragColor = vec4(Depth);
}

Ceci est le fragment shader utilisé pour afficher la shadow map durant la passe de rendu. Les coordonnées de texture 2D sont utilisées pour récupérer la valeur de profondeur depuis la shadow map. La texture de shadow map a été créée avec comme format interne le type GL_DEPTH_COMPONENT. Cela signifie que le texel basique est un simple nombre flottant, et non une couleur. C'est pourquoi « .x » est utilisé durant l'échantillonnage. La projection en perspective possède la caractéristique connue de réserver, pour les positions proches de la caméra, plus de valeurs de l'intervalle [0, 1] que pour les positions lointaines, lors de la normalisation de la composante Z du vecteur position. La raison est de permettre une plus grande précision en profondeur lorsque nous nous approchons de la caméra, car les erreurs sont plus facilement visibles. Lorsque nous affichons le contenu du tampon de profondeur, nous pouvons tomber dans le cas où l'image résultante n'est pas assez claire. Par conséquent, après avoir échantillonné la profondeur depuis la shadow map, nous l'accentuons en multipliant la distance entre le point courant et le point le plus loin (pour lequel Z vaut 1.0) et en soustrayant le résultat de 1.0. Cela augmente l'intervalle, et améliore l'image finale. Nous utilisons la nouvelle valeur de profondeur pour créer une couleur, en la propageant dans toutes les composantes de la couleur. Cela signifie que nous allons obtenir des variations de gris (le blanc étant le plan de découpe éloigné, et le noir étant le plan de découpe proche).

Maintenant, voyons comment combiner les morceaux de code ci-dessus, et créons l'application.

Télécharger Sélectionnez
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
virtual void RenderSceneCB()
{
    m_pGameCamera->OnRender();
    m_scale += 0.05f;

    ShadowMapPass();
    RenderPass();

    glutSwapBuffers();
}

La fonction principale de rendu est devenue plus simple, car la plupart des fonctionnalités ont été déplacées dans d'autres fonctions. Tout d'abord, nous nous occupons des configurations « globales », telles que la mise à jour de la position de la caméra, et le membre de classe utilisé pour orienter l'objet. Ensuite nous appelons la fonction pour dessiner dans la shadow map, puis la fonction pour afficher le résultat. Enfin, glutSwapBuffers() est appelée pour l'afficher sur l'écran.

Télécharger Sélectionnez
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
virtual void ShadowMapPass()
{
    m_shadowMapFBO.BindForWriting();

    glClear(GL_DEPTH_BUFFER_BIT);

    Pipeline p;
    p.Scale(0.1f, 0.1f, 0.1f);
    p.Rotate(0.0f, m_scale, 0.0f);
    p.WorldPos(0.0f, 0.0f, 5.0f);
    p.SetCamera(m_spotLight.Position, m_spotLight.Direction, Vector3f(0.0f, 1.0f, 0.0f));
    p.SetPerspectiveProj(20.0f, WINDOW_WIDTH, WINDOW_HEIGHT, 1.0f, 50.0f);
    m_pShadowMapTech->SetWVP(p.GetWVPTrans());

    m_pMesh->Render();

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

Nous débutons la passe de shadow map en liant le FBO de la shadow map. À partir de maintenant, toutes les valeurs de profondeur iront dans notre texture de shadow map, et les écritures dans le tampon de couleurs seront défaussées. Nous nettoyons le tampon de profondeur (et seulement lui), avant de commencer à faire quoi que ce soit. Ensuite nous préparons la classe de pipeline, afin de dessiner le maillage (un char d'assaut provenant de Quake 2 est fourni avec les sources du tutoriel). Le seul point important ici est que la caméra est mise à jour, en se basant sur la position et l'orientation du projecteur de lumière. Nous dessinons le maillage, puis revenons au tampon d'image par défaut en liant le FBO zéro.

Télécharger Sélectionnez
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
virtual void RenderPass()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    m_pShadowMapTech->SetTextureUnit(0);
    m_shadowMapFBO.BindForReading(GL_TEXTURE0);

    Pipeline p;
    p.Scale(5.0f, 5.0f, 5.0f);
    p.WorldPos(0.0f, 0.0f, 10.0f);
    p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());
    p.SetPerspectiveProj(30.0f, WINDOW_WIDTH, WINDOW_HEIGHT, 1.0f, 50.0f);
    m_pShadowMapTech->SetWVP(p.GetWVPTrans());
    m_pQuad->Render();
}

La passe de rendu débute en nettoyant les deux tampons, couleurs et profondeur. Ces tampons appartiennent au tampon d'image par défaut. Nous disons au shader d'utiliser l'unité de texture 0, et lions la texture contenant la shadow map, en lecture à l'unité de texture 0. À partir de là, c'est comme d'habitude. Nous agrandissons le rectangle, nous le plaçons directement devant la caméra, et nous le dessinons. Pendant la rastérisation, la shadow map est échantillonnée, et affichée.

Dans ce tutoriel, nous ne chargeons plus automatiquement une texture blanche lorsque le fichier du maillage n'en spécifie pas. C'est pour être capable de lier la shadow map à la place. Si un maillage ne contient pas de texture, nous pouvons tout simplement n'en attacher aucune, cela permet au code appelant d'attacher sa propre texture.

III. Sources

Vous pouvez télécharger les sources de ce projet en suivant ce lien :

Récupérer les sources


Tutoriaux sur la gestion dynamique de l'éclairage avec OpenGL

Voici une série de tutoriaux que j'ai réalisé sur la gestion de l'éclairage dynamique avec OpenGL. Le but premier de cette série est de montrer pour chaque tutoriel une nouvelle méthode pour gérer des lumières dynamiques sans avoir à se préoccuper des contraintes lié au nombres maximum de lumières que peut gérer une carte 3D. En effet, les cartes 3D ne peuvent gérer qu'un nombre limité de lumières à la fois (on peut connaitre ce nombre en récuperant l'attribut GL_MAX_LIGHTS d'OpenGL.)

class="articleBody" itemprop="articleBody">

Gestion de l'éclairage dynamique

Tutoriel | Description
Partie 1 : l'éclairage par vertex | Tutoriel sur la mise en place d'un système simple de gestion de l'éclairage dynamique : l'éclairage par vertex. Ce tutoriel détail les differents mode de gestion de l'éclairage dynamique que nous verrons dans la serie de tutoriaux et montre comment mettre en oeuvre l'éclairage par vertex.
Partie 2 : Le light mapping dynamique simple | Tutoriel sur la mise en place d'un système de light mapping dynamique simple. Ce tutoriel tourne sur toutes cartes graphiques supportant le multi texturing. Il explique le fonctionnement du light mapping standard et explique comment lui ajouter un système pour gérer des lumières dynamiques.
Partie 3 : Le light mapping à base de texture 3D | Ce tutoriel reprend le précédent mais utilise une texture 3D comme light map dynamique au lieu d'utiliser deux textures 2D. Ce tutoriel est relativement simple, mais c'est une étape obligatoire avant de commencer à étudier l'éclairage par pixel.
Partie 4 : l'éclairage par pixel simple | Ce tutoriel présente l'éclairage par pixel et introduit toutes les notions nécessaires à sa compréhension. C'est sans doute le tutoriel le plus compliqué à appréhender. J'y explique notamment le principe de l'espace local des vertex et l'utilisation de normal maps.
Partie 5 : l'éclairage par pixel complet | Ce tutoriel approfondi la gestion de l'éclairage par pixel. J'y traite de l'utilisation d'une cube map de normalisation pour résoudre les problèmes d'interpolation des vecteurs vu dans le tutoriel 4, et je présente aussi une méthode basé sur un rendu multi passes pour gérer les lumières colorées.
Partie 6 : éclairage par pixel complexe à base de shaders. | Ce dernier tutoriel montre comment mettre en place un éclairage complexe en utilisant un fragment program ARB (l'équivalent OpenGL des pixel shaders en assembleur de DirectX). Dans ce tutoriel, nos lumières supportent les reflets spéculaire mais aussi le parallax mapping.

Remerciements

Je tiens à remercier tous ceux qui m'ont permis de réaliser ces articles, notamment l'équipe 2D/3D/Jeux de developpez.com, et plus particulièrement à buchs, loka, fearyourself et miles pour leurs relectures.

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2006 Michel de VERDELHAN. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.


Gestion dynamique de la lumière avec OpenGL - Partie 1 : l'éclairage par vertex

  • I. But de ce tutoriel.
  • II. Pourquoi mettre en place son propre système de gestion de la lumière.
  • III. Les différents modèles d'éclairage.
    • III-1. L'éclairage par vertex
    • III-2. Le light mapping
    • III-3. L'éclairage par pixel
    • III-4. Autres modèles
  • IV. Un premier système de gestion de la lumière : l'éclairage par vertex.
    • IV-1. Principe de l'éclairage par vertex
    • IV-2. Structure de données.
    • IV-3. Gestion de l'atténuation.
    • IV-4. Gestion de l'ombrage.
    • IV-5. Le rendu final
  • V. Conclusions
  • VI. Liens

Ce tutoriel a pour but la mise en place d'un système de gestion des lumières dynamiques par vertex lighting avec OpenGL. nous présenterons aussi une vue d'ensemble des méthodes de gestion des lumières dynamiques dans les moteurs 3D, qui seront traitées pour la plupart dans les prochains tutoriaux.

I. But de ce tutoriel.

Cette série de tutoriaux a pour but d'étudier la mise en oeuvre des systèmes d'éclairage dans un moteur 3D.

Il existe de très nombreuses façons de gérer son éclairage dans un moteur 3D. Dans cette série de tutoriaux, nous aborderons les techniques d'éclairages les plus connus. Néanmoins, nous ne traiterons que les techniques permettant de gérer un éclairage dynamique de nos scènes, et nous ne parlerons que des lumières ponctuelles omnidirectionnelles à rayon fini. Nous n'aborderons donc pas les sources de lumières infini, ou les spots de lumière.

Cette série de tutoriaux se décomposera en 6 parties où nous verrons :
  1. L'éclairage par vertex.
  2. Le light mapping dynamique simple.
  3. Le light mapping dynamique avec texture 3D.
  4. L'éclairage par pixel simple.
  5. L'éclairage par pixel standard.
  6. La création d'un système d'éclairage complexe basé sur les shaders.

Pour la mise en oeuvre, nous utiliserons les librairies OpenGL et GLUT pour gérer l'affichage. Néanmoins, l'ensemble des principes étudié ici est facilement portable sous Direct3D.

II. Pourquoi mettre en place son propre système de gestion de la lumière.

Une question souvent posée sur les forums est de savoir comment gérer les lumières dans un moteur 3D. En effets, même si OpenGL propose une gestion des lumières standard en interne, celle ci n'est pas réellement utilisable dans un moteur complexe. Bien qu'accélérées par le matériel, les lumières OpenGL posent plusieurs problèmes lorsqu'on souhaite les utiliser dans un moteur complexe :

  • L'éclairage est calculé par vertex.
    Cette façon de gérer l'éclairage oblige à avoir un nombre de polygones élevé pour obtenir une qualité d'éclairage intéressante. En effet, l'éclairage étant interpolé entre les sommets d'un polygone, si les polygones sont trop étendus, l'éclairage ne sera pas bon au centre du polygone. La seule solution pour résoudre ce problème est de découper les grand polygones en plusieurs petits polygones (on parle d'augmentation de la tesselation). Cette solution résout relativement bien le problème de l'interpolation de la lumière, mais elle a aussi comme conséquence d'augmenter fortement la géométrie ce qui a un impact important sur les performances, notamment au niveau de :
    - l'envoi de la géométrie à la carte 3D
    - l'utilisation de certains algorithmes (par exemple : les collisions) qui seront plus coûteux, ou encore pour le calcul des ombres volumétriques qui dépendent fortement de la géométrie.
  • L'éclairage étant calculé par la carte graphique, le nombre de lumières utilisables simultanément est limité par le matériel. C'est principalement cette limitation qui impose la mise en place de systèmes de gestion de la lumière dans les moteurs 3D. En effet, on imagine mal un jeu moderne qui ne permet d'avoir que 8 lumières dans un niveau. Il existe bien entendu des méthodes qui permettent d'activer/désactiver des lumières quand elles ne sont plus visibles. Ceci permet de repousser la limite du nombre de lumières, mais cette limite existe toujours. Imaginez un très grand hangar avec seulement 8 lumières là où on en aurait mis plusieurs dizaines dans la réalité, le résultat risque de ne pas être très crédible.

Tous ces problèmes ont contraint les créateurs de moteurs 3D à mettre en place des systèmes de gestion de la lumière plus ou moins complexes.

III. Les différents modèles d'éclairage.

Il existe de nombreuses manières d'appréhender la lumière. La plupart des systèmes de gestion de la lumière sont basé sur les modèles de Phong et de Blinn. Néanmoins, de nombreux moteurs 3D n'implémentent pas ces modèles dans leurs totalités.

Je ne détail pas ici les modèles de Phong et Blinn, mais le net regorge de sites qui les traitent très bien.

Le principe de base d'un système de gestion de la lumière est de pouvoir noircir plus ou moins les éléments d'une scène en fonction de la position et de l'orientation du pixel par rapport à la lumière.

Dans la plupart des modèles d'éclairage utilisé, la lumière se décompose généralement en 4 composantes :
  1. La composante ambiante : elle correspond à la luminosité minimum de la scène.
    Elle permet en fait de simuler les sources de lumières indirectes. En effet, dans la réalité, les objets sont éclairé par 2 types de sources lumineuses : les sources directes et les sources indirectes. Les sources directes sont les générateurs de lumières (ampoule, soleil et autre), et les sources indirectes sont des objets qui ont reçu de la lumière et en restituent une partie.
    Comme les algorithmes de calcul de restitution de la lumière indirecte ont une complexité temporelle trop importante pour être utilisé en temps réel, le résultat est approché en utilisant une couleur ambiante commune à toute la scène.
  2. elle correspond à la lumière reçu par l'objet. Elle varie en fonction de la distance et de l'orientation de la surface par rapport à la lumière. Le calcule de la lumière diffuse est généralement

    Ou est la couleur diffuse, est le vecteur normal à la surface, le vecteur allant de la surface à la lumière, est le facteur d'atténuation en fonction de la distance, et le . représente le produit scalaire entre les deux vecteurs.
    Image non disponible
    l'éclairage d'une surface est fonction de la normale à la surface et de la direction vers la lumière

    Ce calcul est généralement nommé NdotL (dot représentant le produit scalaire) dans la littérature, nous utiliserons donc ce terme au cours des tutoriaux

  3. La composante spéculaire : elle correspond à la réflexivité de la surface. Par exemple, une surface métallique brillante aura une forte composante spéculaire alors qu'un pneu de voiture n'en aura pas.
  4. La composante émissive : plus anecdotique, la composante émissive correspond à la lumière émise par la surface. Cette composante est toujours affichée même quand la surface est ombrée.

Généralement, les modèles d'éclairage considèrent que la lumière est additive. Ce qui signifie que si une surface est éclairée par deux lumières, le résultat final sera l'addition des deux éclairages. Ainsi une surface éclairée par une lumière rouge {1,0,0} et par une lumière bleu {0,0,1} aura la même couleur que si elle avait été éclairé par une lumière violette {1,0,1}. Ceci est particulièrement important pour déterminer la façon dont seront affichées les lumières par la suite.

Maintenant que nous avons vue cette introduction basique des modèles d'éclairages, nous allons voir des systèmes classiques pour les mettre en oeuvre.

Les types de systèmes de gestion de l'éclairage dynamiques les plus connus sont :

III-1. L'éclairage par vertex

C'est, historiquement, le premier modèle d'éclairage en temps réel utilisé. En effet, l'éclairage en temps réel étant coûteux, lorsque les ordinateurs n'avaient pas de carte accélératrice, il fallait utiliser un système très rapide, même si il présente les désavantages présenté précédemment.

C'est ce modèle dont nous allons étudier la mise en oeuvre dans ce premier tutoriel.

III-2. Le light mapping

Comme son nom l'indique (si vous n'êtes pas anglophobe), le light mapping consiste à stocker l'éclairage dans une texture. Cet algorithme se décompose en 2 étapes :

  1. Le rendu de la scène en utilisant la texture d'éclairage. Les parties dans l?ombre sont affichées en noir alors que le reste est affiché dans la couleur de l'éclairage local.
  2. Le rendu de la scène avec la texture normale, en multipliant la couleur précédente. Ainsi, si la couleur précédente était du noir, le résultat final sera du noir, donc une zone ombrée. Si la couleur précédente était du blanc, la couleur final obtenu est celle de la texture, donc une zone éclairée.

Cette technique a comme gros avantage de permettre de pré calculer la texture d'éclairage pour les lumières statiques, et, étant basé sur l'utilisation de textures, d'utiliser principalement les ressources de la carte graphique.

Le light mapping est à la base un système de gestion des lumières statiques, mais il est facile de lui ajouter une gestion de l'éclairage dynamique.
Cette méthode sera vue dans les tutoriaux 2 et 3.

III-3. L'éclairage par pixel

Basé sur des extensions comme le DOT3 bump mapping ou les shaders, l'éclairage par pixel est possible de nos jours car les cartes graphiques ayant évolué, le développeur peut maintenant intervenir sur la façon dont sont calculé les pixels avant qu'ils ne soient ajouté à la scène. Cette méthode de gestion de l'éclairage permet un très bon rendu graphique, tout en étant très flexible. Le choix peut donc être fait entre la qualité graphique ou la performance, en implémentant seulement une partie d'un modèle d'éclairage par exemple.

Cette méthode permettant de nombreuses implémentations, nous l'étudierons durant 3 tutoriaux.

III-4. Autres modèles

Il existe d'autres méthodes (qui sont généralement calculées par pixel) comme les spherical harmonics par exemple, mais ces techniques étant plus anecdotiques dans l'industrie 3D, nous ne les étudierons pas ici.

IV. Un premier système de gestion de la lumière : l'éclairage par vertex.

Nous allons maintenant voir comment mettre en oeuvre un système d'éclairage par vertex, mais avant de voir sa mise en oeuvre précise, je vais essayer d'en expliquer rapidement le principe.

IV-1. Principe de l'éclairage par vertex

L'éclairage par vertex consiste simplement à effectuer l'ensemble des calculs d'éclairage au niveau des vertex. Ce système est très simple et peut se résumer à l'algorithme en pseudo code suivant :

Sélectionnez

initialiser la luminosite de tout les vertex a la lumiere ambiante
pour chaque lumiere
pour chaque face
pour chaque vertex de la face
vertex.couleur = lumière.couleur * (NdotL) * atténuation
fin pour
fin pour
fin pour
rendre la scène

L'algorithme peut être simplifié en ne prenant pas en compte les vertex des faces, mais directement la géométrie, mais cela a pour conséquence de ne pas pouvoir avoir de vertex à normales multiples.

Un vertex à normales multiples est un point de l'espace qui est partagé entre plusieurs faces ayant des orientations très différentes. Par exemple un cube a 8 vertex, mais ses faces ont des orientations suffisamment différentes pour qu'on ne souhaite pas lisser les normales de ces vertex (on parle de normal smoothing), le vertex aura alors plusieurs normales (dans le cas du cube, une par face qui utilise le vertex). Généralement la gestion des normales lissées est laissée à la discrétion de l'artiste qui crée la scène via un outil de modélisation. Ici, nous utiliserons donc les normales contenues dans le fichier.

Pour commencer l'étude de la mise en oeuvre, nous allons voir la structure de données utilisée par nos calculs, ensuite nous aborderons le problème du calcul de l'atténuation de la luminosité, et pour finir, nous parlerons de l'ombrage.

Nous ne traiterons pas des composantes spéculaire et émissive de la lumière, mais leur implémentation est relativement facile à mettre en place.

IV-2. Structure de données.

Ici, nous allons parler des structures de donnée utilisé pour le calcule de la luminosité de la scène. J'utilise dans ce tutoriel des classes basiques pour gérer mes vecteurs, couleurs et coordonnées de textures. Ces classes ne seront pas détaillées ici mais elles sont disponibles avec le code.

Pour le tutoriel nous utilisons une scène stockée dans un fichier .obj. Le chargement de ce fichier n'entre pas en compte dans ce tutoriel, mais il nous permet de récupérer les informations de géométrie sous la forme de 4 tableaux qui contiennent :

  1. Les positions des vertex.
  2. Les normales.
  3. Les coordonnées de textures.
  4. Les faces.

Les faces sont des structures qui contiennent les indices des vertex/normales/coordonnées de texture de chacun des points de la face. Ici nous ne traitons que des faces triangulaires.
Nous avons donc besoin d'une structure pour stocker ces faces

La structure gerant une face.
Sélectionnez

struct Face
{
    unsigned int vertexIndex[3];
    unsigned int texCoordIndex[3];
    unsigned int normalIndex[3];
    Color color[3];
};

Ici, la face contient aussi une couleur pour chacun de ses vertex, c'est la couleur qui sera utilisée pour déterminer l'éclairage final du vertex. Cette information est propre à ce tutoriel et disparaîtra dans les tutoriaux suivants.

Nous avons besoin d'une classe qui va gérer notre géométrie. Cette classe doit pouvoir stocker les informations sur les vertex, normales, coordonnées de textures et sur les faces. Elle doit aussi permettre de charger un fichier de géométrie, gérer l'éclairage et le rendu de la scène.

La classe gerant un modèle (une scène)
Sélectionnez

class Model
{
private:
    Vecteur * vertex;
    Vecteur * normals;
    TexCoord * texCoords;
    Face * faces;
    int nbVertex;
    int nbNormals;
    int nbTexCoord;
    int nbFaces;
public:
    Model(void);
    virtual ~Model(void);
    bool load(const std::string & filePath);
    void initLighting(const Color& ambiant);
    void addLight(Light& light);
    void render();
};

La méthode load() sert à charger la géométrie depuis un fichier, elle n'est pas particulièrement intéressante ici, la seul information qui nous intéresse est que le chargement prend en compte les normales partagées, nous avons donc un modèle dont certains vertex ont plusieurs normales et donc un rendu cohérent des cubes et autres formes avec des angles important entre les faces.

La méthode initLighting prend en paramètre la couleur ambiante et l'assigne à tout les vertex de la scène. Cette méthode donne donc :

La méthode d'initialisation de la lumière
Sélectionnez

void Model::initLighting(const Color& ambiant)
{
    for (int i = 0; i < nbFaces; i++)
    {
        faces[i].color[0] = ambiant;
        faces[i].color[1] = ambiant;
        faces[i].color[2] = ambiant;
    }
}

La méthode addLight prend en paramètre une lumière, effectue les calculs d'éclairage et les appliquent aux vertex. Le code donne donc :

La méthode permettant d'ajouter une lumière au modèle
Sélectionnez

void Model::addLight(Light& light)
{
    // pour chaque face
    for (int i = 0; i < nbFaces; i++)
    {
        // pour chaque vertex de la face
        for (int j = 0; j < 3; j++)
        {
            // on calcul la lumiere
            Color c = light.computeLighting(vertex[faces[i].vertexIndex[j]],normals[faces[i].normalIndex[j]]);
            // et on l'addition avec la lumiere precedente
            faces[i].color[j].r += c.r;
            faces[i].color[j].g += c.g;
            faces[i].color[j].b += c.b;
        }
    }
}

Avec computeLighting, la fonction de calcul de la luminosité du vertex. Cette méthode sera vue plus loin.

Ici la luminosité calculée est additionnée à celle calculée précédemment, car, comme nous l'avons vue, la lumière est additive.

Il ne nous reste plus que la méthode permettant le rendu de la scène. Elle est relativement simple, et ici, je n'utilise aucune optimisation à base de tableaux de vertex ou autre, j'effectue un simple rendu en mode immédiat.

Méthode de rendu du modèle
Sélectionnez

void Model::render()
{
    glBegin(GL_TRIANGLES);
    // pour chaque face
    for(int i = 0;i < nbFaces; i++)
    {
        // pour chaque vertex de la face
        for (int j = 0; j < 3; j++)
        {
            // on recupere la couleur, les coordonnees de texture et la position
            Color &c = faces[i].color[j];
            TexCoord &tc = texCoords[faces[i].texCoordIndex[j]];
            Vecteur &v = vertex[faces[i].vertexIndex[j]];
            // et on les envoient à openGL
            glColor3f(c.r,c.g,c.b);
            glTexCoord2f(tc.u,tc.v);
            glVertex3f(v.x,v.y,v.z);
        }
    }
    glEnd();
}

Maintenant que nous avons notre classe de gestion de la géométrie, nous avons besoins d'une classe qui gère une lumière.

Pour nous simplifier la gestion des calculs, c'est cette classe qui va calculer l'éclairage des vertex en fonction de leur position et de leur normale.

La classe de gestion des lumières
Sélectionnez

class Light
{
private:
    float radius;
    Color color;
    Vecteur position;
public:
    Light(void);
    virtual ~Light(void);
    void setRadius(float r);
    float getRadius();
    void setColor(float r, float g, float b);
    Color getColor();
    void setPosition(float x, float y, float z);
    Vecteur getPosition();
    Color computeLighting(const Vecteur & position, const Vecteur & normal);
};

Notre lumière contient simplement une position, un rayon et une couleur. Mis à part les accesseur et modificateur, cette classe ne contient qu'une méthode intéressante : computeLighting qui prend en paramètre la position d'un vertex et sa normal et retourne sa couleur d'éclairage. C'est cette méthode qui permet de calculer l'éclairage du vertex comme nous allons le voir.

IV-3. Gestion de l'atténuation.

La première chose à prendre en compte dans notre calcul de la luminosité est l'atténuation de la lumière. En effet plus notre vertex est loin de la lumière, moins il est éclairé, et si il dépasse le rayon d'action de la lumière, il n'est plus du tout éclairé.

Il existe plusieurs équations pour calculer l'atténuation (linéaire, quadratique et autre). Ici, nous utiliserons une équation linéaire. C'est à dire qu'un vertex situé à mi-distance du rayon de la lumière recevra la moitié de sa luminosité.
L'équation donne donc

atténuation = max ( 0, 1 ? (distance / rayon))

ou atténuation est le facteur d'atténuation de la lumière en fonction de la distance, distance est la distance du vertex par rapport à la position de la lumière et rayon est le rayon maximum de la lumière.

Le code du calcul de l'atténuation donne donc :

Code du calcul de l'attenuation
Sélectionnez

// on calcul le verteur allant du vertex à la lumiere.
Vecteur vertexToLight = this->position - position;
float attenuation = std::max<float>(0.0f,1.0f - (vertexToLight.getLength() / radius));

Nous calculons le vecteur allant du vertex à la lumière. Ici, le sens du vecteur n'a pas d'importance étant donné que ce qui nous intéresse est sa longueur, mais il est néanmoins important de le calculer dans le bon sens car il est réutilisé par la suite dans la gestion de l'ombrage.

IV-4. Gestion de l'ombrage.

Ce qui est appelé ombrage en infographie est à différencier des ombres portées. L'ombre portée correspond à l'ombre générée par un objet sur un autre objet. L'ombrage lui est juste le fait qu'une face est moins éclairé si elle ne fait pas face à la lumière et est complètement ombrée si elle tourne le dos à la lumière.

Nous voulons donc obtenir ce comportement pour nos vertex. Pour cela, nous allons justement utiliser le fameux calcul NdotL. Mais d'abord un petit rappel de maths sur le produit scalaire s'impose.

Le produit scalaire entre deux vecteurs normalisé (de longueur 1) a une propriété intéressante :

Si les 2 vecteurs sont identiques, le produit scalaire vaut 1. Si les 2 vecteurs ont la même direction, le produit scalaire est compris dans ]0..1]. Si les 2 vecteurs sont perpendiculaires, le produit scalaire vaut 0, si ils sont de sens opposé, le produit scalaire est compris dans [-1..0[, et si ils sont complètement opposé, le produit scalaire vaut -1.

Appliqué à notre problème, si le produit scalaire entre la normale et le vecteur vertexToLight est supérieur à 0, c'est que la face est éclairé, sinon, elle est dans l'ombre. Et, encore plus intéressant, si la normal est exactement identique au vecteur vertexToLight (c'est a dire si le polygone fait exactement face à la lumière), le produit scalaire vaut 1. Nous avons donc, à moindre coût, le calcul exacte de l'ombrage de nos faces avec un simple produit scalaire (et une normalisation de vecteur car il faut que le vecteur vertexToLight soit normalisé pour que ce calcul fonctionne). Nous avons donc retrouvé notre NdotL, mais nous l'appliquons par vertex et non par faces.

Notre fonction de calcul de la luminosité devient donc :

Calcul de l'attenuation en prenant en compte l'ombrage
Sélectionnez

// on calcul le verteur allant du vertex a la lumiere.
Vecteur vertexToLight = this->position - position;
float attenuation = 1.0f - (vertexToLight.getLength() / radius);
// on normalise le vecteur
vertexToLight.normalise();
// on calcul le produit scalaire NdotL avec N = normal et L = vertexToLight
attenuation *= vertexToLight*normal;
attenuation = std::max<float>(0.0f,attenuation);

Maintenant que nous avons calculé la luminosité de notre vertex, nous devons calculer sa couleur. Ceci se fait simplement en multipliant la couleur de la lumière par la luminosité du vertex. Nous obtenons donc le code final de la méthode computeLighting :

La methode de calcul de la couleur d'un vertex
Sélectionnez

Color Light::computeLighting(const Vecteur & position, const Vecteur & normal)
{
    // on calcul le verteur allant du vertex à la lumiere.
    Vecteur vertexToLight = this->position - position;
    float attenuation = 1.0f - (vertexToLight.getLength() / radius);
    // on normalise le vecteur
    vertexToLight.normalise();
    // on calcul le produit scalaire
    attenuation *= vertexToLight*normal;
    attenuation = std::max<float>(0.0f,attenuation);
    Color ret = this->color;
    ret.r *= attenuation;
    ret.g *= attenuation;
    ret.b *= attenuation;
    return ret;
}

IV-5. Le rendu final

Maintenant que nous avons tout ce qu'il faut pour calculer notre éclairage, il nous faut l'afficher. Ceci se fait très simplement en respectant la séquence suivante :

La séquence de code à suivre pour rendre la scène
Sélectionnez

scene->initLighting(ambiant);
// on ajoute nos lumieres
scene->addLight(light1);
scene->addLight(light2);
// on active la texture
glEnable(GL_TEXTURE_2D);
// on affiche la scene
scene->render();

Ce qui donne le resultat suivant :

Image non disponible
Scène éclairée par 3 lumières : 1 rouge, 1 bleu et 1 blanche

V. Conclusions

La méthode d'éclairage par vertex est simple à mettre en oeuvre et présente comme avantage important par rapport aux techniques que nous étudierons plus tard de dessiner la scène qu'une seule fois. Nous économisons donc du temps sur l'envoi de la géométrie, mais c'est du coup le processeur qui prend en charge la majorité des calculs.

Un autre défaut vu précédemment est le problème des artefacts d'éclairage sur les polygones trop étendu. Cette artefact se produit principalement au niveau du calcul de l'atténuation. En effet, lorsqu'on place une lumière sur un polygone trop étendu, on peut tomber sur la situation ou le rayon de la lumière n'est pas suffisant pour éclairer les vertex du polygone, il n'est donc pas éclairé alors que la lumière le touche.

Image non disponible
Cas ou l'atténuation de l'éclairage par vertex pose problème

Pour résoudre ces problèmes, nous allons devoir changer de méthode d'éclairage et passer au light mapping que nous verrons dans le prochain tutoriel.


Gestion dynamique de la lumière avec OpenGL - Partie 2 : le light mapping simple

  • I. Pourquoi utiliser le light mapping.
  • II. Le light mapping : un système simple et puissant.
    • II-1. Rappels sur le blending.
    • II-2. Principe du light mapping.
  • III. Modifications du programme.
    • III-1. Gérer les extensions.
    • III-2. Modifications de la classe de model.
    • III-3. Modification du calcul de la luminosité.
    • III-4. Le rendu final
  • IV. Conclusions.
  • VI. Liens

Ce tutoriel ainsi que celui qui le suit montre la mise en oeuvre d'un système de gestion de lumières dynamiques par light mapping. Ce tutoriel détaille le principe du light mapping et utilise une mise en oeuvre simple à base de textures 2D.

I. Pourquoi utiliser le light mapping.

Nous avons vu dans le tutoriel précédent qu'utiliser les vertex pour calculer la luminosité est simple à mettre en oeuvre mais pose des problèmes au niveau de l'atténuation de la lumière sur les polygones étendus. Ce problème peut être contourné en augmentant la tesselation de la scène, mais dans un moteur de jeu cette solution n'est généralement pas utilisable. En effet, augmenter la tesselation va augmenter la géométrie, ce qui va accentuer la charge de travail des algorithmes basé sur cette géométrie (collisions, ombres volumétriques et autres.)

Une solution à ce problème a été trouvée par John Carmack pour le jeu Quake 1. Cette solution se base sur l'utilisation de textures pour stocker la luminosité des surfaces. On utilise donc 2 textures par polygone : une texture standard contenant l'image qu'on souhaite afficher sur notre polygone, et une texture de luminosité appelée light map, d'où le nom de la méthode : le light mapping.

Cette méthode étant basée sur des textures et non plus directement sur la géométrie, les problèmes lors du calcul de l'atténuation sont donc supprimés.

Nous allons voir dans les deux tutoriaux à venir comment mettre en place un système de light mapping.

II. Le light mapping : un système simple et puissant.

Le light mapping est basé sur la modulation de textures. Je vais donc faire un bref rappel sur le blending qui va nous permettre d'utiliser cette modulation de textures. Ensuite je détaillerai le light mapping, puis je vais expliquer les modifications apportées au tutoriel précédent pour obtenir l'effet souhaité.

II-1. Rappels sur le blending.

L'API OpenGL propose en standard une gestion du blending. Le blending consiste à effectuer plusieurs rendu de la scène et à mélanger les couleurs de la scène rendue précédemment avec celles qu'on est en train de rendre (to blend : mélanger en anglais).

Il existe plusieurs familles de blending. Parmi elles, deux nous intéresse plus particulièrement :

  • le blending additif : la couleur courante est additionnée à la couleur précédente. Le blending additif a comme avantage d'être indépendant de l'ordre d'affichage, on peut donc rendre la scène dans l'ordre qu'on souhaite, le résultat sera le même. Image non disponible
  • Le blending multiplicatif : la couleur courante est multipliée à la précédente. Comme pour le blending additif, l'ordre d'affichage n'a pas d'importance. Ce blending est souvent utilisé avec une composante en niveau de gris, ce qui permet d'assombrir le résultat précédent. Image non disponible

Ces deux modes de blending peuvent s'obtenir en passant à la fonction glBlendFunc() les paramètres (GL_ONE,GL_ONE) pour le blending additif et (GL_DST_COLOR, GL_ZERO) pour le blending multiplicatif.

On peut aussi passer les paramètres (GL_DST_COLOR,GL_SRC_COLOR) pour obtenir un blending multiplicatif + additif. Ce blending va multiplier la couleur précédente par la couleur courante et va additionner la couleur courante au résultat. Ce blending peut être utilisé par le light mapping pour obtenir un effet de saturation des couleurs.

Nous ne traiterons pas d'autres modes de blending ici car ils ne sont pas utiles pour le light mapping.

II-2. Principe du light mapping.

Maintenant que nous savons comment fonctionne le blending (au moins la partie du blending qui nous intéresse), nous allons reparler du light mapping.

Le light mapping est un algorithme de rendu de la lumière multi- passes. La première passe consiste à afficher le niveau en affichant uniquement les textures de luminosité (light map). Une fois cet affichage effectué, nous avons donc notre scène rendu en niveaux de gris (ou en niveaux de couleur si on souhaite avoir des lumières colorées). Ensuite, il ne reste plus qu'à activer le blending multiplicatif et rendre la seconde passe en affichant le niveau texturé. Le résultat final sera le niveau texturé mais avec des zones d'ombres dans les textures correspondantes aux zones non éclairée.

Image non disponible
Les 2 passes du light mapping et le rendu final.

À la base, le light mapping est un système créé pour gérer des lumières statiques. Le calcul des light maps peut être très coûteux si on utilise des algorithmes complexes (radiosité ou autre) pour calculer la luminosité des texels. Toutefois, on peut facilement utiliser une simplification pour prendre en compte des lumières dynamiques sphériques.

Pour cela, nous allons simuler une texture en 3 dimensions représentant une sphère de lumière à l'aide d'une texture en 2D particulière et du multitexturing. Cette texture, appelée light map dynamique, est en fait un cercle de luminosité dont les bords doivent impérativement être noir.

Image non disponible
La light map dynamique

Utilisée en mode non répétitif (GL_CLAMP_TO_EDGE) et avec une utilisation judicieuse du multitexturing, on peut obtenir une approximation d'une texture 3D efficace. Le concept consiste à utiliser la première unité de texture comme un tube lumineux (on calcul les coordonnées de texture sur le plan XY par exemple), puis à n'utiliser que la ligne centrale de la deuxième unité de texture (donc une bande de luminosité allant du noir au noir en passant par le blanc) comme facteur d'atténuation sur le troisième axe (dans notre exemple : Z).

Le calcul de nos coordonnées de texture pour nos deux unités de textures donne donc :

texture1.texcoord : {u = vertexToLight.x / rayon + 0,5 ; v = vertexToLight.y / rayon + 0,5}

texture2.texcoord : {u = vertexToLight.z / rayon + 0,5 ; 0,5}

Nous ajoutons 0,5 aux coordonnées de texture car on considère que le centre de la lumière est au centre de la texture et non pas à son origine. La dernière coordonnée de texture est positionnée à 0,5 car on souhaite utiliser la ligne centrale de la texture.

Ce calcule n'effectue pas réellement une sphère, mais un cube. Cependant, étant donné que notre texture représente un cercle, le résultat visuel s'approche d'une sphère. (Essayer de changer la light map par une texture blanche avec juste les bords noirs pour voir la différence).

Maintenant que nous savons comment calculer les coordonnées de textures, il faut afficher les lumières. Comme nous l'avons vu plus haut, le light mapping s'effectue en 2 passes, et, étant donné que le blending utilisé est multiplicatif, l'ordre de ces 2 passes n'est pas important. On peut rendre d'abord les light maps puis les textures ou l'inverse car l'équation d'éclairage est :

couleur finale = couleur light map * couleur texture.

Néanmoins pour la mise en place du light mapping dynamique, il faut changer la façon dont est rendu le niveau. Nous allons d'abord rendre le niveau avec les light maps, puis nous afficherons les lumières (avec du blending additif), pour finalement multiplier le résultat par les textures du niveau. Ceci donne donc l'équation suivante :

couleur finale = (couleur light map + couleur lumières dynamiques) * couleur texture.

Qui ne donne pas le même résultat que si nous avions d'abord rendu les textures, puis les light maps et finalement les lumières dynamiques dont l'équation est :

couleur finale = (couleur texture * couleur light map) + couleur lumières dynamiques

Nous avons donc besoin de modifier notre programme pour prendre en compte ces changements de calculs, mais aussi pour gérer le multitexturing.

III. Modifications du programme.

Dans cette section, nous allons voir les modifications apportées au tutoriel précédent pour pouvoir utiliser le light mapping dynamique. Nous verrons d'abord comment charger les fonctions de l'extension de multitexturing, puis nous détaillerons les changements dans les méthodes de gestion de la lumière, pour finalement voir la séquence de commandes OpenGL à utiliser pour obtenir le rendu voulu.

III-1. Gérer les extensions.

Pour gérer le multitexturing dans notre application nous avons besoin de deux fonctions que nous déclarons dans le fichier Main.cpp comme variables globales :

Sélectionnez

PFNGLMULTITEXCOORD2FARBPROC        glMultiTexCoord2fARB = NULL;
PFNGLACTIVETEXTUREARBPROC        glActiveTextureARB = NULL;

La fonction glMultiTexCoord2fARB sert à envoyer les coordonnées de texture 2D pour une unité de texture donnée et la fonction glActiveTextureARB permet de definir quelle unité de texture est en cours d'utilisation.

Pour avoir accès aux fonctions des extension, il ne faut pas oublier d'inclure le fichier glext.h

Comme nous aurons aussi besoins de la fonction d'envoi des coordonnées de textures dans notre classe de Model, nous la déclarons en externe dans le fichier Model.cpp

Sélectionnez

extern PFNGLMULTITEXCOORD2FARBPROC        glMultiTexCoord2fARB;

Maintenant que nous avons déclaré nos fonctions, il faut les charger. Charger une fonction consiste à récupérer son adresse auprès du pilote de la carte graphique. Pour cela, nous ajoutons une fonction dans Main.cpp qui va s'occuper de charger les extensions pour ce tutoriel et ceux à venir. Cette fonction donne donc :

La fonction de chargement des extensions
Sélectionnez

void initExtensions()
{
    if (glutExtensionSupported("GL_ARB_multitexture"))
    {
        glMultiTexCoord2fARB =    (PFNGLMULTITEXCOORD2FARBPROC)wglGetProcAddress("glMultiTexCoord2fARB");
        glActiveTextureARB =    (PFNGLACTIVETEXTUREARBPROC)wglGetProcAddress("glActiveTextureARB");
        if (!glActiveTextureARB || !glMultiTexCoord2fARB )
        {
            fprintf(stderr,"impossible d'initialiser l'extension GL_ARB_multitexture\n");
            exit(0);
        }
    }
    else
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_ARB_multitexture\n");
        exit(0);
    }
}

Ici, nous utilisons la fonction wglGetProcAddress pour récupérer l'adresse des fonctions de multitexturing. Cette fonction étant une fonction wgl, elle est propre à la plateforme Windows et n'est pas portable. Il existe une fonction glxGetProcAddress pour Linux, ou encore une fonction portable dans la SDL.

Maintenant que nous avons chargé les fonctions, nous pouvons les utiliser comme n'importe quelle fonction OpenGL.

III-2. Modifications de la classe de model.

Pas de changement dans l'interface de la classe Model, par contre, les méthodes initLighting, addLight et render ne vont plus effectuer la même chose.

La méthode initLighting sert toujours à paramétrer l'éclairage ambiant. Dans un système avec light maps statiques, cette méthode consisterait à afficher la scène avec les light maps statiques, mais comme nous n'en avons pas, ici, nous nous contenterons d'afficher la lumière ambiante. Le code donne donc :

La méthode d'affichage de la couleur ambiante
Sélectionnez

void Model::initLighting(const Color& ambiant)
{
    glColor3f(ambiant.r,ambiant.g,ambiant.b);
    glBegin(GL_TRIANGLES);
    for(int i = 0;i < nbFaces; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            Vecteur &v = vertex[faces[i].vertexIndex[j]];
            glVertex3f(v.x,v.y,v.z);
        }
    }
    glEnd();
}

La méthode addLight permet d'ajouter une lumière à la scène. Comme nous l'avons vu, elle doit dessiner la scène en calculant l'éclairage des polygones. On suppose que la méthode est toujours appelée au bon endroit, c'est-à-dire que le blending additif est activé et que la couleur ambiante a déjà été rendue. Il faut aussi que les deux premières unités de textures contiennent la light map dynamique. Ici, la méthode computeLighting de la classe Light ne calcule plus une couleur, mais des coordonnées de texture en 3D qui sont retournées sous la forme d'un vecteur 3D. Comme nous utilisons du blending additif, l'ordre dans lequel sont ajouté les lumières n'a pas d'importance. Le code de la méthode donne donc :

La méthode permettant d'ajouter une lumière à la scène
Sélectionnez

void Model::addLight(Light& light)
{
    glColor3f(light.getColor().r,light.getColor().g,light.getColor().b);
    glBegin(GL_TRIANGLES);
    // pour chaque face
    for (int i = 0; i < nbFaces; i++)
    {
        // pour chaque vertex de la face
        for (int j = 0; j < 3; j++){
            // on calcule la lumiere
            Vecteur lightTexCoord = light.computeLighting
                            (vertex[faces[i].vertexIndex[j]]);
            glMultiTexCoord2fARB(GL_TEXTURE0_ARB,
                            lightTexCoord.x,lightTexCoord.y);
            glMultiTexCoord2fARB(GL_TEXTURE1_ARB,lightTexCoord.z,0.5f);
            Vecteur &v = vertex[faces[i].vertexIndex[j]];
            glVertex3f(v.x,v.y,v.z);
        }
    }
    glEnd();
}

La méthode render sert à effectuer la dernière passe de l'algorithme qui consiste à multiplier le résultat de l'éclairage par la texture de la scène. Ici aussi, nous supposons qu'OpenGL est bien paramétré pour que le rendu fonctionne, c'est-à-dire que l'équation de blending est placée en mode multiplicatif. Le code de la méthode donne donc :

La méthode permettant de rendre la dernière passe de l'algorithme
Sélectionnez

void Model::render()
{
    glColor3f(1,1,1);
    glBegin(GL_TRIANGLES);
    for(int i = 0;i < nbFaces; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            TexCoord &tc = texCoords[faces[i].texCoordIndex[j]];
            Vecteur &v = vertex[faces[i].vertexIndex[j]];
            glTexCoord2f(tc.u,tc.v);
            glVertex3f(v.x,v.y,v.z);
        }
    }
    glEnd();
}

Ne pas oublier de positionner la couleur courante sur du blanc, sinon le résultat final sera déformé par la couleur.

III-3. Modification du calcul de la luminosité.

Comme nous l'avons vu dans la méthode addLight, la méthode computeLighting de la classe Light est changée. Elle doit calculer les coordonnées de textures à appliquer à nos deux unités de textures. Le calcul correspond à celui décrit dans la partie d'explication du light mapping. Il est donc très simple et se résume à :

La méthode de calcul de la lumière
Sélectionnez

Vecteur Light::computeLighting(const Vecteur & position)
{
    Vecteur ret;
    ret.x = (position.x - this->position.x) / (radius) + 0.5f;
    ret.y = (position.y - this->position.y) / (radius) + 0.5f;
    ret.z = (position.z - this->position.z) / (radius) + 0.5f;
    return ret;
}

Ne pas oublier que la méthode a changé de type de retour depuis le tutoriel précédent, elle ne retourne plus une couleur mais des coordonnées de textures 3D dans un vecteur.

III-4. Le rendu final

Contrairement au tutoriel précédent, la mise en place du rendu est plus compliquée. Le rendu s'effectue en trois étapes :

  • rendre la scène avec les light maps statiques (ici, la couleur ambiante).
  • ajouter toutes les lumières à la scène.
  • multiplier le résultat par la texture de la scène.

Ce qui nous donne donc :

Rendu de la couleur ambiante
Sélectionnez

model->initLighting(ambiant);
Ajout des lumières à la scène
Sélectionnez

// On configure les unites de texture pour pouvoir effectuer le multitexturing.
// En fait les deux unites de texture utilisent la meme texture.
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, lightmap);

glActiveTextureARB(GL_TEXTURE1_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, lightmap);

// On doit activer le blending additif pour pouvoir utiliser ce mode d'eclairage
glEnable(GL_BLEND);
glBlendFunc(GL_ONE,GL_ONE);
// Inutile d'ecrire dans le depth buffer, la passe d'ambiante l'a deja fait
glDepthMask(GL_FALSE);
// On ajoute nos lumieres
model->addLight(light1);
model->addLight(light2);
// On desactive la seconde unite de texture qui ne nous sert plus par la suite
glActiveTextureARB(GL_TEXTURE1_ARB);
glDisable(GL_TEXTURE_2D);
Multiplier le résultat
Sélectionnez

// Et on bind la premiere sur la texture du model
glActiveTextureARB(GL_TEXTURE0_ARB);
glBindTexture(GL_TEXTURE_2D,textureId);

// On doit changer le mode de blending : on passe en mode multiplicatif
glBlendFunc(GL_DST_COLOR,GL_ZERO);

model->render();

glDisable(GL_TEXTURE_2D);

glDepthMask(GL_TRUE);
glDisable(GL_BLEND);

Ici, au lieu d'utiliser une fonction de blending multiplicatif simple, on peut utiliser du blending multiplicatif + additif (GL_DST_COLOR,GL_SRC_COLOR) qui va donner un résultat avec des couleurs nettement plus saturées.

Image non disponible
Le rendu final en mode multiplicatif normal.
Image non disponible
Le rendu final en mode multiplicatif + additif.

IV. Conclusions.

Contrairement à ce que certains pensent, mettre en place un système de light mapping dynamique demande très peu de code pour un résultat de bonne qualité. Par contre, contrairement à la méthode par vertex, le light mapping demande de nombreux rendus de la scène ( un rendu pour les light maps statiques, un par lumière dynamique et un dernier pour multiplier la lumière par la texture de la scène.) mais ceci permet d'avoir un bon rendu de la lumière quelque soit la géométrie contrairement à l'éclairage par vertex.

La solution présentée ici a quand même des désavantages : elle ne prend pas en compte l'orientation des faces, c'est-à-dire qu'une face qui tourne le dos à la lumière sera éclairée de la même façon que si elle lui faisait face. Nous n'avons donc pas de gestion de l'ombrage.

Un autre problème est que cette solution n'est pas facilement extensible pour prendre en compte des lumières de type spot. Pour cela, il faudrait mettre en place un système de projection de textures nettement plus complexe.

Nous verrons dans le prochain tutoriel comment mettre en oeuvre un système de light mapping utilisant une texture 3D comme light map dynamique à la place de deux textures 2D.

VI. Liens

Vous pouvez télécharger les sources de ce tutoriel ici

Image non disponible Tutoriel 1 : éclairage par vertex. Tutoriel 3 : light mapping avec texture 3D Image non disponible

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2006 Michel de VERDELHAN. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.


Gestion dynamique de la lumière avec OpenGL - Partie 3 : light mapping dynamique avec texture 3D

  • I. Pourquoi utiliser une texture 3D ?
  • II. Modifications apportées au programme
    • II-1. Gestion des extensions
    • II-2. Générer une texture 3D
    • II-3. Modification du rendu des models
    • II-4. Le rendu final
  • III. Conclusions

Ce tutoriel fait suite au tutoriel sur la mise en place d'un système de light mapping basique. Dans celui-ci, nous verrons comment mettre en place un système de light mapping se basant sur l'utilisation de textures 3D.
Contrairement aux tutoriaux précédents, ici, nous ne verrons pas de nouvelle méthode de gestion de la lumière, mais nous allons approfondir la technique précédente. Ce tutoriel est relativement court et simple si on maîtrise bien le concept du light mapping, mais c'est une étape importante pour faciliter la compréhension des tutoriaux suivants.

I. Pourquoi utiliser une texture 3D ?

Le light mapping que nous avons vu dans le tutoriel précédent pose quelques problèmes, aussi bien d'un point de vue qualitatif que d'un point de vue de l'optimisation du rendu. En effet, comme nous l'avons vu, le fait d'utiliser deux unités de textures pour simuler notre sphère pose problème du point de vue de la qualité de la sphère. Ce n'est pas une vraie sphère, et, si le rendu pour une petite lumière semble acceptable, il l'est nettement moins pour le rendu des lumières avec un rayon plus important. C'est donc une première raison d'utiliser une texture 3D comme light map : on obtient une sphère nettement plus sphérique, ce qui améliore la qualité d'affichage des grandes lumières.

Le deuxième problème posé est que notre système utilise deux unités de textures, ce qui est important, surtout quand on souhaite utiliser un modèle d'éclairage complexe très demandeur en unités de texture (comme nous le verrons dans le sixième tutoriel.). De plus, cette forte consommation en unités de textures limite l'utilisation de l'éclairage par pixel sur les cartes anciennes (les GeForce 3 et 4 n'ont que 4 unités de textures, les GeForce 2, seulement 2...). Un des avantages de l'utilisation d'une texture 3D est justement de n'utiliser qu'une seule unité de texture pour la light map.

Ceci nous permettra d'utiliser une optimisation du rendu réduisant le nombre de passes nécessaires. Bien que cette optimisation soit utilisable sans texture 3D, le fait de n'avoir qu'une unité de texture pour la light map permet cette optimisation sur les cartes ne possédant que 2 unités de textures, et surtout, nous nous rapprochons plus d'un model d'éclairage réaliste.

II. Modifications apportées au programme

Les modifications apportées au programme dans ce tutoriel sont relativement légères. Dans un premier temps, nous allons voir le chargement des extensions nécessaires, puis nous verrons comment effectuer le calcul de la texture 3D représentant la sphère d'atténuation, puis, pour terminer, nous aborderons le rendu final de la scène.

II-1. Gestion des extensions

Ici, pas de changement majeur. Nous allons juste ajouter plusieurs fonctions nécessaires au fonctionnement de notre programme, mais le principe reste le même que pour le tutoriel précédent.

Etant donné que nous allons utiliser une texture 3D, nous avons besoin de pouvoir lui passer des coordonnées de textures 3D. Pour cela, nous devons donc charger les fonctions qui prennent en charge cela pour le multitexturing. Nos déclarations de fonctions deviennent donc :

Déclaration des fonctions nécessaires pour le multitexturing : main.cpp
Sélectionnez

// les fonctions utilisees pour gerer le multitexturing
PFNGLMULTITEXCOORD2FARBPROC		glMultiTexCoord2fARB = NULL;
PFNGLMULTITEXCOORD3FARBPROC		glMultiTexCoord3fARB = NULL;
PFNGLMULTITEXCOORD4FARBPROC		glMultiTexCoord4fARB = NULL;
PFNGLACTIVETEXTUREARBPROC		glActiveTextureARB = NULL;
PFNGLCLIENTACTIVETEXTUREARBPROC	glClientActiveTextureARB = NULL;
Déclaration des fonctions nécessaire pour le multitexturing : Model.cpp
Sélectionnez

extern PFNGLMULTITEXCOORD2FARBPROC		glMultiTexCoord2fARB;
extern PFNGLMULTITEXCOORD3FARBPROC		glMultiTexCoord3fARB;
extern PFNGLMULTITEXCOORD4FARBPROC		glMultiTexCoord4fARB;

Maintenant que nous pouvons passer nos coordonnées de textures à nos textures 3D, il nous faut gérer ces textures 3D, nous devons donc charger les fonctions suivantes :

Déclaration des fonctions nécessaire pour les textures 3D
Sélectionnez

// les fonctions utilisees pour gerer les textures 3D
PFNGLTEXIMAGE3DEXTPROC			glTexImage3DEXT = NULL;
PFNGLTEXSUBIMAGE3DEXTPROC		glTexSubImage3DEXT = NULL;

Les textures 3D sont une extension EXT et non pas ARB, c'est-à-dire qu'elle n'est pas forcément implémentée sur toutes les cartes 3D.

Maintenant que nous avons déclaré nos fonctions, il ne faut pas oublier de les charger. Notre fonction de chargement des extensions devient donc :

Fonction de chargement des extensions OpenGL
Sélectionnez

void initExtensions()
{
    if (glutExtensionSupported("GL_ARB_multitexture"))
    {
        glMultiTexCoord2fARB =		(PFNGLMULTITEXCOORD2FARBPROC)wglGetProcAddress("glMultiTexCoord2fARB");
        glMultiTexCoord3fARB =		(PFNGLMULTITEXCOORD3FARBPROC)wglGetProcAddress("glMultiTexCoord3fARB");
        glMultiTexCoord4fARB =		(PFNGLMULTITEXCOORD4FARBPROC)wglGetProcAddress("glMultiTexCoord4fARB");
        glActiveTextureARB =		(PFNGLACTIVETEXTUREARBPROC)wglGetProcAddress("glActiveTextureARB");
        glClientActiveTextureARB =	(PFNGLCLIENTACTIVETEXTUREARBPROC)wglGetProcAddress("glClientActiveTextureARB");
        if (!glActiveTextureARB || !glMultiTexCoord2fARB || !glMultiTexCoord3fARB
            || !glMultiTexCoord4fARB || !glClientActiveTextureARB)
        {
            fprintf(stderr,"impossible d'initialiser l'extension GL_ARB_multitexture\n");
            exit(0);
        }
    }
    else
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_ARB_multitexture\n");
        exit(0);
    }
    if (glutExtensionSupported("GL_EXT_texture3D"))
    {
        glTexImage3DEXT = (PFNGLTEXIMAGE3DEXTPROC) wglGetProcAddress("glTexImage3DEXT");
        glTexSubImage3DEXT = (PFNGLTEXSUBIMAGE3DEXTPROC) wglGetProcAddress("glTexSubImage3DEXT");
        if (!glTexImage3DEXT || !glTexSubImage3DEXT)
        {
            fprintf(stderr,"impossible d'initialiser l'extension GL_EXT_texture3D\n");
            exit(0);
        }
    }
    else
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_EXT_texture3D\n");
        exit(0);
    }
}

Une fois que nous avons chargé les extensions, nous pouvons voir comment calculer la texture 3D qui va nous servir de light map.

II-2. Générer une texture 3D

Avant de voir comment calculer notre light map 3D, je vais commencer par faire une rapide explication sur les textures 3D.
Les textures 3D peuvent être vu comme une superposition de plusieurs textures de même taille. Généralement, elles sont générées de manière procédural (comme c'est le cas ici), mais elles peuvent aussi être crées en chargeant plusieurs textures de même taille dans les différentes couches de la texture, pour créer, par exemple, une texture animée simplement (et en profitant de l'interpolation hardware entre les couches de la texture). Il faut aussi bien différencier les textures 3D des cubes maps, qui, bien qu'elles soient elles aussi des représentations 3D d'images, ne représentent pas un volume comme la texture 3D.
Dans notre cas, nous souhaitons crée une texture dont le volume correspond à une sphère de luminosité, c'est-à-dire que plus on s'éloigne du centre de la texture, plus la luminosité diminue. Pour effectuer cette sphère, nous allons utiliser une fonction qui va nous la générer plutôt que de charger plusieurs textures représentant les couches de la texture.

Le concept du calcul de la texture 3D est très simple, il suffit, pour chaque texel de la texture, de calculer sa distance par rapport au "centre" de la texture. Si le texel est proche du centre, il sera blanc, alors que si la distance est supérieure ou égal au rayon de la sphère (la moitié de la taille de la texture), alors le texel sera noir.

Ce calcul s'effectue sur le même principe que le calcule de l'atténuation de l'éclairage par vertex.

La fonction de calcul de la light map est sans doute la partie la plus compliquée de ce tutoriel, mais si vous avez bien compris le principe du calcul de l'atténuation présenté dans le premier tutoriel, celui-ci de va sans doute pas poser de problèmes. Le code de la fonction donne donc :

Fonction de création d'une light map 3D
Sélectionnez

void generateSphereMap(int size)
{
    glEnable(GL_TEXTURE_3D_EXT);
    if(size<1)
    {
        fprintf(stderr,"la taille de la texture 3D doit etre superieur à 1\n");
        return;
    }

    float *datas = new float[size*size*size*3];
    //on remplie avec la couleur noir
    for(int i=0; i<size*size*size*3; i++)
    {
        datas[i] = 0.0f;
    }
    float length;
    //la demi largeur de la texture 3d en texels (represente le rayon de la sphere)
    float size2= (float)size / 2.0f;

    // pour chaque texel de la texture
    for(float i=0; i<size; i++)
    {
        for(float j=0; j<size; j++)
        {
            for(float k=0; k<size; k++)
            {
                //la demi-largeur moins la distance du centre au texel courant de la texture.
                //size2-1 pour eviter l'elairage sur le bord de la texture.
                length = (size2-1) - sqrt( (i-size2)*(i-size2) +
                            (j-size2)*(j-size2) + (k-size2)*(k-size2) );
                //normalisation dans la plage[0,1] (intensite de la lumiere)
                length /= size2;

                //on stocke la couleur avec son intensite dans le texel
                datas[(int)i*size*size*3 + (int)j*size*3 + (int)k*3   ] = length;
                datas[(int)i*size*size*3 + (int)j*size*3 + (int)k*3 +1] = length;
                datas[(int)i*size*size*3 + (int)j*size*3 + (int)k*3 +2] = length;
            }
        }
    }

    // on generer une texture openGL
    glGenTextures(1,&lightmap);
    glBindTexture(GL_TEXTURE_3D_EXT,lightmap);
    // on parametre la texture (ici on utilise que du filtrage linaire, en cas
    // d'utilisation de mipmapping, il faut calculer les different niveaux
    // de mipmap.)
    glTexParameteri(GL_TEXTURE_3D_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_3D_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    // le mode de repetition : CLAMP_TO_EDGE pour ne pas voir la texture se repeter.
    glTexParameteri(GL_TEXTURE_3D_EXT, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_3D_EXT, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_3D_EXT, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
    // la texture doit moduler la couleur precedente.
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

    // on envoi la texture à openGL
    glTexImage3DEXT(GL_TEXTURE_3D_EXT, 0, GL_RGB, size, size, size, 0, GL_RGB, GL_FLOAT, datas);

    glDisable(GL_TEXTURE_3D_EXT);
    delete [] datas;
}

Ici, nous n'utilisons qu'un filtrage linéaire de la texture. Pour utiliser du filtrage bilinéaire ou trilinéaire (GL_LINEAR_MIPMAP_NEAREST et GL_LINEAR_MIPMAP_LINEAR), il faut générer tous les niveaux de mipmap de la texture 3D.

Maintenant que nous avons une light map, nous pouvons voir les modifications à apporter au rendu de modèles pour qu'ils prennent en compte l'utilisation d'une texture 3D.

II-3. Modification du rendu des models

C'est dans cette partie que nous allons voir l'optimisation présentée au début du tutoriel permettant de réduire le nombre de passes pour rendre la scène.

Dans le tutoriel précédent, le rendu s'effectuait en 3 partie : le rendu des light maps statiques, le rendu des light maps dynamiques en niveau de couleur, puis la multiplication du résultat. La scène était rendue dans cet ordre pour que le programme puisse tourner sur toutes les cartes supportant le multitexturing (donc avec deux unités de textures).

Image non disponible
Le mode de rendu présenté dans le tutoriel précédent.

L'optimisation présentée ici peut être appliquée au tutoriel précédent si la carte supporte plus de deux unités de textures.
Cette optimisation consiste à supprimer la dernière étape de rendu de la scène. En effet, si la scène est rendu à chaque étape avec à la fois la light map et la texture du modèle, on a donc à chaque étape à la fois la luminosité et la couleur de la texture qui sont multipliées par le multitexturing, puis le résultat est ajouté à la scène.
Le dessin suivant explique mieux de concept.

Image non disponible
Rendu de la scène en deux étapes

La première étape de ce dessin représente le résultat de l'éclairage dynamique sur une surface avec multiplication de la texture par la light map dynamique. La deuxième étape représente l'affichage de la light map statique avec la texture de la surface. Le tout est additionné pour donner le résultat final. On a donc ici uniquement deux étapes de rendu : une étape de rendu des light maps, et une étape de rendu des lumières dynamiques. Un autre avantage de cette méthode est qu'elle nous redonne la possibilité d'organiser ses deux étapes dans l'ordre que l'on souhaite.

Etant donné que nous utilisons désormais une seule unité de texture pour notre light map, un tel système est simple à mettre en oeuvre, il nous suffit d'attribuer la light map à une unité de texture, et la texture de la surface à une autre unité de texture.

Nous allons donc modifier le comportement de notre programme en conséquence.
Comme pour le tutoriel précèdent, ici, nous n'avons pas de light maps statiques, nous allons donc juste rendre la scène avec une couleur ambiante pour simuler l'utilisation de light maps. Il faut donc que notre méthode initLighting affiche l'ensemble du niveau avec ses textures multipliées par la couleur ambiante. Le code de la méthode devient donc :

Code de la méthode initLighting
Sélectionnez

void Model::initLighting(const Color& ambiant)
{
    glColor3f(ambiant.r,ambiant.g,ambiant.b);
    glBegin(GL_TRIANGLES);
    for(int i = 0;i < nbFaces; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            TexCoord &tc = texCoords[faces[i].texCoordIndex[j]];
            Vecteur &v = vertex[faces[i].vertexIndex[j]];
            glTexCoord2f(tc.u,tc.v);
            glVertex3f(v.x,v.y,v.z);
        }
    }
    glEnd();
}

De la même façon, le code de la méthode addLight doit simplement afficher le modèle en lui attribuant les coordonnées de texture 3D pour la light map dynamique. Ici, nous supposons qu'OpenGL est dans un état cohérent, c'est-à-dire que l'unité de texture 0 contient bien la texture du modèle, et que l'unité de texture 1 contient la light map dynamique en 3D. Le code de la méthode addLight devient donc :

Code de la méthode addLight
Sélectionnez

void Model::addLight(Light& light)
{
    glColor3f(light.getColor().r,light.getColor().g,light.getColor().b);
    glBegin(GL_TRIANGLES);
    for(int i = 0;i < nbFaces; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            TexCoord &tc = texCoords[faces[i].texCoordIndex[j]];
            Vecteur &v = vertex[faces[i].vertexIndex[j]];
            Vecteur lightTexCoord = light.computeLighting(v);
            glMultiTexCoord2fARB(GL_TEXTURE0_ARB,tc.u,tc.v);
            glMultiTexCoord3fARB(GL_TEXTURE1_ARB,lightTexCoord.x,lightTexCoord.y,lightTexCoord.z);
            glVertex3f(v.x,v.y,v.z);
        }
    }
    glEnd();
}

La méthode computeLighting n'a pas changé depuis le dernier tutoriel.

Maintenant que nous avons tous les outils pour effectuer le rendu de notre scène, nous allons voir comment effectuer ce rendu.

II-4. Le rendu final

Comme nous l'avons vu, le rendu final de la scène ne s'effectue plus en trois étapes mais en deux. La première consiste à rendre les light maps statiques (simplement la lumière ambiante dans notre cas), et s'effectue tout simplement comme ça :

Rendu de la lumière ambiante
Sélectionnez

// on configure la premiere unite de texture : elle contient la texture de la scene
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, textureId);

// on effectue le rendu de la lumiere ambiante
model->initLighting(ambiant);

La deuxième étape consiste à ajouter chaque lumière à la scène. Le code donne donc :

Rendu des lumières dynamiques
Sélectionnez

// on configure la deuxieme unite de texture : elle contient la lightmap
glActiveTextureARB(GL_TEXTURE1_ARB);
glEnable(GL_TEXTURE_3D_EXT);
glBindTexture(GL_TEXTURE_3D_EXT,lightmap );

// on doit activer le blending additif pour pouvoir utiliser ce mode d'eclairage
glEnable(GL_BLEND);
glBlendFunc(GL_ONE,GL_ONE);
glDepthMask(GL_FALSE);

// on ajoute nos lumieres
model->addLight(light1);
model->addLight(light2);

// on desactive la seconde unite de texture
glActiveTextureARB(GL_TEXTURE1_ARB);
glDisable(GL_TEXTURE_3D_EXT);
// on desactive la premiere unite de texture
glActiveTextureARB(GL_TEXTURE0_ARB);
glDisable(GL_TEXTURE_2D);

glDepthMask(GL_TRUE);
glDisable(GL_BLEND);

Voila c'est tout. Nous avons maintenant une gestion dynamique des lumières par light mapping avec texture 3D.
Le rendu final de la scène donne ça :

Image non disponible
Le rendu final de la scène.

III. Conclusions

Ca y est, nous en avons fini avec le light mapping. Nous avons maintenant un système fonctionnel, mais il faut néanmoins faire attention à bien l'utiliser. En effet, dans notre cas, la scène affichée est simple et, surtout, elle n'est composée que d'une seule texture. Dans une vraie scène, on utilise généralement de nombreuses textures, ce qui peut poser problème avec notre réduction du nombre de passes. En effet, si la gestion des textures est faite de façon anarchique, il risque d'y avoir multiplication des bind de textures (étant donné qu'on a toujours besoin d'au moins deux unités de textures par rendu), ce qui risque de faire perdre le bénéfice de l'optimisation proposée. Donc si vous souhaitez implémenter un tel système, prenez bien garde à limiter autant que possible les changements de textures.

Contrairement aux tutoriaux précédents et à ceux qui vont suivre, celui-ci est plutôt simple et ne présente pas de nouveau concepts de gestion de la lumière, mais il est néanmoins indispensable de bien l'appréhender pour pouvoir comprendre la suite.

Autant les tutoriaux entre l'éclairage par vertex et l'éclairage par light mapping sont relativement déconnectés, autant le lien entre ce tutoriel et les suivants sur l'éclairage par pixel est important. En effet, l'éclairage par pixel utilise les principes du light mapping pour gérer l'atténuation de la lumière.

Pour récapituler, nous avons vu à l'heure actuelle comment mettre en oeuvre un système de gestion de l'éclairage dynamique par vertex et par light mapping, et il nous reste à voir comment mettre en oeuvre l'éclairage par pixel. Ceci fera l'objet des trois prochains tutoriaux.

Vous pouvez télécharger les sources de ce tutoriel ici ou ici [http]
Une version PDF de ce tutoriel est disponible ici ou ici [http]


Gestion dynamique de la lumière avec OpenGL - Partie 4 : éclairage par pixel simple

  • . Pourquoi utiliser de l'éclairage par pixel ?
  • II. Explications sur le bump mapping
    • II-1. Rappel sur les calculs d'éclairage
    • II-2. La normal map
    • II-3. L'extension env_dot3
  • III. Gestion de l'espace local
    • III-1. Pourquoi utiliser un espace local ?
    • III-2. Modification du chargement des modèles
    • III-3. Calcul de l'espace local
  • IV. Modifications apportées au rendu des modèles
  • V. Rendu final
  • VI. Conclusions
  • Liens

Ce tutoriel présente une version simplifiée de l'éclairage par pixel. Son but est avant tout de présenter les concepts liés à la gestion de l'éclairage par pixel, c'est-à-dire, le calcul et la gestion de l'espace local des vertex. Nous allons aussi modifier notre façon d'utiliser les modèles pour les adapter aux problèmes liés au format de fichiers utilisé.
Dans ce tutoriel et dans celui qui suit, nous verrons comment utiliser l'extension env_dot3 d'OpenGL qui est l'extension à utiliser pour pouvoir effectuer l'éclairage par pixel sans utiliser de fragment program/shaders.

I. Pourquoi utiliser de l'éclairage par pixel ?

Nous avons vu dans les tutoriaux précédents comment calculer l'éclairage, d'abord par vertex, puis en utilisant une light map dynamique, mais la vie est ainsi faite qu'on en souhaite toujours plus... Vous aurez peut être remarqué que si l'éclairage par vertex présente certains problèmes, il a quand même l'avantage de prendre en compte l'orientation de la face pour calculer sa luminosité (ou plutôt, il prend en compte l'orientation des vertex). Ceci n'est pas pris en compte par le light mapping dynamique qui ne calcule que l'atténuation de la lumière, ce qui pose problème lorsqu'on souhaite avoir un éclairage réaliste. Par contre, l'éclairage par light mapping dynamique ne pose pas de problèmes liés à la tesselation (1) des modèles. L'idéal serai donc de réussir à lier les avantages des deux techniques. C'est ce que nous allons faire avec l'éclairage par pixel.

Bien que le fait de pouvoir prendre en compte l'orientation des faces sans avoir les problèmes liés à la tesselation soit intéressant, ce n'est pas non plus le principal avantage de l'éclairage par pixel. Le gros avantage de cette technique est de pouvoir, comme son nom l'indique en fait, calculer l'éclairage au niveau du pixel et non plus du vertex. On obtient ainsi une meilleure finesse dans l'éclairage. Bien évidemment, cette méthode a un coût en terme de temps de calculs.

Dans ce tutoriel, nous allons d'abord voir les principes de l'éclairage par pixel, ensuite nous étudierons plus en détail un des éléments qui est le plus important de l'éclairage par pixel : l'espace local des vertex, et finalement, nous verrons les modifications apportées au programme pour prendre en compte l'éclairage par pixel.

II. Explications sur le bump mapping

Dans cette partie, nous allons voir les principes de fonctionnement de l'éclairage par pixel. Dans un premier temps, je vais revenir sur les calculs d'éclairage, puis j'expliquerai le fonctionnement des normal maps, et pour finir, je présenterai rapidement l'extension env_dot3 qui va nous permettre d'effectuer le calcul de l'éclairage par pixel.

II-1. Rappel sur les calculs d'éclairage

Pour bien comprendre le fonctionnement de l'éclairage par pixel, je reviens rapidement sur le calcul de l'éclairage déjà vu dans le premier tutoriel sur l'éclairage par vertex.
Comme nous l'avons vu, l'éclairage d'une surface se calcul en fonction de :

  • Son éloignement par rapport à la lumière, c'est ce qu'on appel l'atténuation. Nous avons vu comment calculer l'atténuation grâce au light mapping dynamique, je ne reviendrai donc pas dessus dans ce tutoriel.
  • Son orientation par rapport à la lumière. Ainsi, si la surface tourne le dos à la lumière, elle n'est pas éclairée, alors que si elle est orientée en face de la lumière, elle reçoit l'ensemble de la lumière. C'est cette gestion de l'orientation de la surface qui nous intéresse plus particulièrement dans ce tutoriel.

Pour gérer l'orientation de la face par rapport à la lumière, nous avons vu dans le premier tutoriel que nous avons besoin de deux informations : le vecteur perpendiculaire à la surface aussi appelé la normale à la surface et le vecteur dirigé vers la lumière. Une fois ces deux vecteurs récupérés, il nous suffit d'effectuer le produit scalaire entre les deux vecteurs pour avoir le coefficient de luminosité perçu par la surface. C'est le fameux NdotL, avec N représentant la normale à la surface, L le vecteur vers la lumière, et dot le produit scalaire.

Image non disponible
Les vecteurs utilisés pour calculer l'éclairage

Jusqu'à présent, nous avions utilisé la normale des vertex pour effectuer ce calcul, mais à partir de maintenant, nous allons utiliser une normal map dont je vais présenter le principe.

II-2. La normal map

Pour notre calcul, il nous faut récupérer la normale à la surface. Lorsqu'on utilise une normale par vertex, le résultat des calculs est interpolé entre les sommets du polygone, ce qui donne de mauvais résultats lorsque le polygone est trop étendu. En fait, on se retrouve dans des situations où le résultat correspond à un cas où la normale est interpolée comme sur le schéma suivant.

Image non disponible
Résultat de l'interpolation linéaire de deux normales : la normale interpolée n'est plus normalisée.

On voit clairement que le résultat de l'interpolation n'est pas valide pour le calcul de la luminosité : la normale interpolée n'est plus normalisée, on va donc avoir des problèmes d'échelle dans le résultat final (on parle de scaling problems). En effet, si la normale est de longueur 0.8, la luminosité finale sera réduite de 20 % par rapport à ce qu'elle aurait dû être.

C'est pour résoudre ce problème qu'on utilise une texture qui va stocker les normales de la surface, ainsi, on réduit les problèmes liés à l'interpolation des normales (ils ne sont pas complètement supprimés étant donné qu'une texture de trop faible résolution sera elle-même interpolée quand on s'en approche ).

Une normal map est en faite une texture standard dont les composantes RBG ne sont plus interprétées comme des couleurs, mais comme des vecteurs 3D représentant la normale d'une surface. On peut donc facilement donner l'impression qu'un polygone n'est plus plat en modifiant l'orientation des normales dans la normal map. Les calculs d'éclairage vont s'effectuer sur ces normales perturbées, donnant ainsi une impression de volume virtuel.

Image non disponible
L'utilisation d'une normal map perturbée permet de créer un volume virtuel utilisé pour créer une impression de volume sur la surface.

Contrairement à ce qu'on pourrait croire, une normal map peut parfaitement se regarder à l'oeil et est parfaitement compréhensible une fois qu'on sait ce quelle représente. Voici par exemple l'exemple de la local map utilisé dans ce tutoriel :

Image non disponible
La local map utilisée dans le tutoriel : on voit bien les limites des volumes générés par la perturbation des normales.

J'en vois dans le fond qui se demandent bien pourquoi j'utilise le terme de local map au lieu de parler de normal map comme je l'ai fait jusqu'à présent. Tout cela s'explique par le fait qu'il existe deux catégories de normal maps : les global maps et les local maps.

Les global maps sont des normal maps qui stockent des vecteurs orientés dans toutes les directions. Le gros avantage des global maps est qu'elles peuvent être utilisées directement sans avoir à passer par le calcul de l'espace local des vertex. Ceci permet donc de calculer l'éclairage plus vite étant donné que nous n'avons pas tous les calculs de projections, que nous verrons par la suite, à effectuer. Par contre, le gros problème des global maps est qu'elles ne sont pas réutilisables. C'est-à-dire qu'une global map ne peut être utilisée que pour un objet donné. Imaginez une scène ou les graphistes sont obligés de faire une global map propre à chaque polygone... Ce serait totalement impossible à gérer. En fait les global maps sont très peu utilisées, on en trouve parfois lorsqu'il faut afficher des terrains. Etant donné que le terrain a peu de chance d'être modifié, on calcule une global map au lancement, et ainsi, on gagne en temps d'exécution lors de l'affichage.

Les local maps, au contraire des global maps, ne contiennent que des normales orientées sur l'axe des Z positifs. Ainsi, une local map ne représente pas la surface d'un objet, mais une surface générique pouvant être utilisée sur plusieurs objets différents. Le fait qu'elles ne soit orientées que dans une seule direction oblige à des calculs relativement complexes à comprendre au départ, mais, en contrepartie, on peut réutiliser les local maps à volonté. Le nom de local map vient du fait que pour pouvoir utiliser une local map, il faut effectuer les calculs d'espaces locaux des vertex, que nous verrons par la suite. On peut facilement différencier une local map d'une global map et regardant la couleur générale de la texture. Une global map sera potentiellement de toutes les couleurs alors qu'une local map est généralement bleue étant donné que les vecteurs pointent vers les Z positifs (donc la composante bleu du vecteur est souvent proche de 1).

Etant donné que nous ne nous préoccupons que des local maps, j'utiliserai indifféremment le terme normal map et local map pour designer la local map par la suite.

Maintenant que nous avons vu le principe de la normal map, il nous reste une étape à voir : comment faire pour pouvoir calculer le produit scalaire pour chaque pixel ? C'est ce que nous allons voir dans la partie qui vient.

II-3. L'extension env_dot3

OpenGL ne propose pas de fonctions standard pour modifier le calcul au niveau des pixels : pour ça, il faut encore une fois passer par des extensions. Ici, nous allons utiliser l'extension env_dot3 qui va nous permettre de calculer le produit scalaire entre la normale de la surface et le vecteur vers la lumière.

Contrairement aux extensions utilisées dans les tutoriaux précédents, l'extension env_dot3 n'utilise pas de fonctions particulières à charger, elle apporte juste des paramètres en plus à passer aux fonctions standard OpenGL. Comme ce n'est pas une utilisation commune d'une extension, je vais présenter rapidement son fonctionnement afin d'avoir une meilleure compréhension des calculs effectués par la carte graphique.

Voici donc comment faire pour configurer une unité de texture pour qu'elle réalise le calcul de DOT3 bump mapping :

Code pour configurer une unité de texture pour qu'elle effectue le produit scalaire
Sélectionnez

glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, normalMapId);
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_COMBINE_ARB);
glTexEnvf(GL_TEXTURE_ENV,GL_RGB_SCALE_ARB,1);
glTexEnvf(GL_TEXTURE_ENV,GL_COMBINE_RGB_ARB,GL_DOT3_RGB_ARB);
glTexEnvf(GL_TEXTURE_ENV,GL_SOURCE0_RGB_ARB,GL_PREVIOUS_ARB);
glTexEnvf(GL_TEXTURE_ENV,GL_SOURCE1_RGB_ARB,GL_TEXTURE);
glTexEnvf(GL_TEXTURE_ENV,GL_OPERAND0_RGB_ARB,GL_SRC_COLOR);
glTexEnvf(GL_TEXTURE_ENV,GL_OPERAND1_RGB_ARB,GL_SRC_COLOR);

Voila, c'est tout ce qu'il y a à faire. En fait, on signale à OpenGL qu'on souhaite que la carte graphique effectue le produit scalaire entre l'unité de texture courante et la couleur précédente dans le pipeline. Il nous suffit donc de passer le vecteur vers la lumière dans la couleur précédente (par exemple via un appel à glColor comme nous allons le faire dans ce tutoriel ou via une cube map comme nous le ferons dans le prochain tutoriel), et la carte graphique va se charger pour nous du calcul.

III. Gestion de l'espace local

Nous avons vu plus tôt que nous utilisons une local map, il nous faut donc prendre en charge la gestion de l'espace local des vertex. Dans cette partie, je vais d'abord présenter en quoi consiste l'espace local des vertex, puis j'expliquerai les problèmes que peut engendrer cet espace local au niveau du chargement des modèles, et finalement, je donnerai le code utilisé pour calculer l'espace local pour chaque vertex.

III-1. Pourquoi utiliser un espace local ?

La gestion de l'espace local est très certainement la partie la plus complexe à comprendre (et à expliquer ;-) ) du bump mapping. En effet, elle fait appel à des notions d'algèbre linéaire qui ne sont pas triviales à comprendre dans un premier temps. Je vais essayer de l'expliquer, mais si jamais vous ne comprenez pas, n'hésitez pas à allez chercher d'autres sources de documentation sur internet. En effet, il faut parfois plusieurs explications différentes pour bien appréhender ce concept.

Le problème avec les normal maps, c'est que si on tourne l'objet sur lequel la texture est plaquée, les normales, elles, ne sont pas tournées, on se retrouve donc avec un objet qui n'est plus sur le plan XY (plan sur lequel sont générées les normal maps qui pointent donc vers les Z positifs), mais avec des normales qui sont restées sur le plan XY. Pour mieux montrer les problèmes, quelques schémas sont sûrement plus efficaces que des lignes de textes :

Image non disponible
Cas idéal : la surface est sur le bon plan pour les calculs.

Ici, on a le cas idéal : la surface est bien orientée sur le plan XY, les normales (en rouge) de la normal map pointent donc dans la bonne direction, le calcul va bien s'effectuer.

Image non disponible
Cas souhaité : la normale est orientée en même temps que la surface.

Ici, le schéma montre ce que l'on souhaiterais avoir : la surface n'est pas alignée sur le plan XY, mais les normales de la normal map ont bien été tournées, le calcul va donc bien s'effectuer. Mais malheureusement, comme je l'expliquais au dessus, ce n'est pas ce qu'il se passe réellement. On se trouve en fait avec le cas suivant :

Image non disponible
Mauvais cas : les calculs ne prennent pas en compte le changement d'orientation de la surface.

Ici, la surface a bien été tournée, mais les normales de la normal map ne subissent pas de transformations (ce qui se passe effectivement au niveau de la carte graphique qui n'a pas de notions d'orientation des surfaces), on se retrouve donc avec un calcul faux (ici, le calcul va donner un produit scalaire supérieur à 0, donc éclairé, alors que la surface ne devrais pas l'être car elle tourne le dos à la lumière. Etant donné qu'on ne peut pas modifier les normales de la normal map, le seul moyen d'obtenir un calcul juste est de modifier le seul vecteur modifiable : le vecteur vers la lumière, ce qui nous donne le résultat suivant :

Image non disponible
Cas effectivement pratiqué pour le bump mapping dot3 : on ne peut pas modifier la normale, alors on transforme le vecteur vers la lumière.

Ici, on a modifié le vecteur vers la lumière pour obtenir un calcul valide. En fait, sur ce schéma, l'angle entre le vecteur vers la lumière et la normale de la surface est le même que sur le deuxième schéma (aux erreurs de dessin près ;-) ). On a donc un calcul valide qui donne le même résultat que si nous avions tournée la normale de la surface comme dans le cas du deuxième schéma.

Il nous faut donc calculer ce vecteur vers la lumière (qui n'est du coup plus vraiment orienté vers la lumière ;-) ) pour prendre en compte les surface transformées. Si toutes nos surfaces étaient générées sur le plan XY puis tournées pour atteindre leur places finales, le calcul ne poserait pas de problème, il suffirait de faire subir la rotation inverse à notre vecteur vers la lumière pour obtenir ce que l'on souhaite. Mais malheureusement, ce n'est pas le cas. Nous n'avons généralement que trois vertex pour définir un triangle et pas de rotation ou autre à notre disposition. Il nous faut donc calculer quelque chose qui permet d'obtenir le résultat souhaité : l'espace local.

L'espace local (aussi appelé espace tangent) est un espace tridimensionnel formé de trois vecteurs et d'une position dans l'espace. Les trois vecteurs permettent d'orienter l'espace local dans l'espace global, et la position correspond à l'origine de l'espace local dans l'espace global. Oui, dit comme ça, c'est incompréhensible ;-). En fait il faut voir cet espace de la même manière que les axes standards X, Y et Z qui forment eux aussi un espace local positionné en {0,0,0}.

Bon j'ai dis que notre espace local était formé de trois vecteurs, il est temps d'en parler plus en détails. Les trois vecteurs qui forment notre espace local sont appelés Tangente, Binormale et Normale (on voit souvent le diminutif TBN utilisé pour parler de l'espace local). On connaît déjà la normale qui n'est autre que la normale du vertex. Les deux autres vecteurs sont perpendiculaires à la normale, mais surtout, ils sont proportionnels aux coordonnées de texture du polygone. En effet, il faut que notre espace prenne en compte le fait que les coordonnées de textures puissent être tournées/étirées/inversées.

Encore une fois, un petit schéma permet d'avoir les idées plus claires.

Image non disponible
Un polygone avec l'espace local de chacun de ses vertex : quand le polygone change d'orientation, les espaces locaux suivent.

Nous avons vu ce qu'est un espace local, mais il faut encore savoir à quoi il peut bien nous servir. En fait c'est lui qui va nous permettre de calculer le vecteur vers la lumière modifiée pour qu'il soit valide. Pour cela, il suffit de projeter le vecteur vers la lumière dans l'espace local. C'est ici qu'intervient l'algèbre linéaire : pour projeter un vecteur dans un espace 3D formé de trois vecteurs, il suffit, pour chaque composante du vecteur cible, de calculer le produit scalaire entre le vecteur à transformer et le bon vecteur de l'espace local (en fait il faut les prendre dans l'ordre Tangente pour la composante X, Binormale pour la composante Y et Normale pour la composante Z). Le calcul de la projection d'un vecteur correspond donc à ça :
VecteurDestination.x = vecteurSource . Tangente
VecteurDestination.y = vecteurSource . Binormale
VecteurDestination.z = vecteurSource . Normale

Maintenant que nous savons ce qu'est un espace local et à quoi il sert, nous allons pouvoir voir comment le calculer. Mais avant, je vais parler des problèmes que peut entraîner tout ceci au niveau du chargement des modèles.

III-2. Modification du chargement des modèles

Le fait de prendre les tangentes et binormales en compte nous oblige à modifier notre classe de modèle. En effet, il nous faut ajouter un tableau de vecteurs pour les tangentes et un autre tableau pour les binormales. Tout ceci se fait facilement en ajoutant au fichier Model.h ceci :

Ajout des tableaux de binormales et tangentes
Sélectionnez

/**
 * le tableau de binormales (il y a autant de binormales que de normales)
 */
Vecteur * binormals;
/**
 * le tableau de tangentes (il y a autant de tangentes que de normales);
 */
Vecteur * tangents;

Que l'on oubliera pas de supprimer dans le destructeur bien entendu.

Mais le principal problème vient du remplissage de ces tableaux. Idéalement, pour connaître la taille des tableaux, il suffit de prendre la taille du tableau de normales. En effet, si le format de modèles gère bien les normales partagées, nous aurons donc un espace local par normale partagée. Malheureusement, dans notre cas, les normales ne sont pas bien partagées. Lors de l'export en .obj, si deux normales ont les mêmes valeurs, alors l'exporteur n'en conserve qu'une seule et change les indices des faces en conséquences. Ceci pose problème car on peut très bien avoir des normales orientées dans le même sens mais liées à des faces ayant des coordonnées de textures différentes. Ceci entraîne de graves incohérences dans les calcules par la suite.

Dorénavant, nous n'utiliserons plus les normales contenues dans le fichier, mais nous les calculerons à la main au chargement. Le problème que pose cette méthode est que les normales seront toutes adoucies, y compris celles qui sont sur des faces avec une orientation très différente (comme sur un cube par exemple). Ceci aura des conséquences sur la qualité de l'éclairage, certaines faces pouvant tourner le dos à la lumière tout en étant toujours éclairées. J'ai choisis de ne pas régler ce problème car il entraîne une trop forte complication du code pour un débutant, et surtout, il peut être réglé en choisissant un format de modèle qui nous assure d'une bonne gestion des normales partagées.

Maintenant nous pouvons voir le nouveau code de chargement des modèles :

Méthode de chargement des modèles modifiés
Sélectionnez

bool Model::load(const std::string & filePath)
{
    // on cree un lecteur de .obj
    OBJReader reader(filePath);
    // on charge le model
    if (reader.load())
    {
        // le model est bien charge
        // on alloue le tableau de vertex
        vertex = new Vecteur[reader.getVertex().size()];
        nbVertex = 0;
        // on copie les informations des vertex
        for (std::vector<OBJReader::OBJVector>::iterator it =
        reader.getVertex().begin();	it != reader.getVertex().end(); ++it)
        {
            vertex[nbVertex].x = (*it).x;
            vertex[nbVertex].y = (*it).y;
            vertex[nbVertex].z = (*it).z;
            nbVertex++;
        }
        // on alloue le tableau de normales
        // les tableaux de binormales et tangentes font la
        // meme taille que le tableau de normales
        normals = new Vecteur[nbVertex];
        binormals = new Vecteur[nbVertex];
        tangents = new Vecteur[nbVertex];
        // on les initialise au vecteur null
        for (int i = 0; i < nbVertex; i++)
        {
            normals[i].x = normals[i].y = normals[i].z =
            binormals[i].x = binormals[i].y = binormals[i].z =
            tangents[i].x = tangents[i].y = tangents[i].z = 0.0f;
        }
        // on alloue le tableau de coordonnees de textures
        texCoords = new TexCoord[reader.getTexture().size()];
        nbTexCoord = 0;
        // on copie les informations de coordonnees de texture
        for (std::vector<OBJReader::OBJtexCoord>::iterator it =
        reader.getTexture().begin();it != reader.getTexture().end(); ++it)
        {
            texCoords[nbTexCoord].u = (*it).u;
            texCoords[nbTexCoord].v = (*it).v;
            nbTexCoord++;
        }
        // on alloue le tableau de faces
        faces = new Face[reader.getFace().size()];
        nbFaces = 0;
        // on copie les informations des faces
        for (std::vector<OBJReader::OBJface>::iterator it = reader.getFace().begin();
        it != reader.getFace().end(); ++it)
        {
            faces[nbFaces].vertexIndex[0] = (*it).vertex[0];
            faces[nbFaces].vertexIndex[1] = (*it).vertex[1];
            faces[nbFaces].vertexIndex[2] = (*it).vertex[2];
            faces[nbFaces].normalIndex[0] = (*it).normal[0];
            faces[nbFaces].normalIndex[1] = (*it).normal[1];
            faces[nbFaces].normalIndex[2] = (*it).normal[2];
            faces[nbFaces].texCoordIndex[0] = (*it).texture[0];
            faces[nbFaces].texCoordIndex[1] = (*it).texture[1];
            faces[nbFaces].texCoordIndex[2] = (*it).texture[2];
            nbFaces++;
        }

        // maintenant que nous avons toutes les informations sur les faces,
        // on peut calculer l'espace local
        for (int i = 0; i < nbFaces; i++)
        {
            computeLocalSpace(faces[i]);
        }
        // il ne faut pas oublier de renormaliser les tangentes et binormales
        for (int i = 0; i < nbVertex; i++)
        {
            binormals[i].normalise();
            tangents[i].normalise();
            normals[i].normalise();
        }
        return true;
    }
    else
    {
        return false;
    }
}

La méthode computeLocalSpace() correspond à la méthode de calcul de l'espace local que nous allons voir maintenant.

III-3. Calcul de l'espace local

Le calcul de l'espace local pour une face est finalement assez simple une fois qu'on a compris ce qu'est réellement l'espace local. Il suffit de calculer deux vecteurs qui suivent les bords du polygone et sont proportionnels aux coordonnées de textures. Une fois qu'on a effectué ce calcul pour la face, il ne nous reste plus qu'à additionner le résultat aux tangentes, binormales et normales des vertex du polygone. Je tiens à signaler qu'il existe plusieurs algorithmes pour calculer cet espace local, il est donc possible que vous en trouviez des différents sur le net.

Le calcul de l'espace local pour une face donne le code suivant :

Méthode de calcul de l'espace local pour une face
Sélectionnez

void Model::computeLocalSpace(Face &face)
{
    // on calcule 2 vecteurs formant les bords du triangle
    // le vecteur side0 est celui allant du vertex 0 au vertex 1
    Vecteur side0 = vertex[face.vertexIndex[1]] - vertex[face.vertexIndex[0]];
    // le vecteur side1 est celui allant du vertex 0 au vertex 2
    Vecteur side1 = vertex[face.vertexIndex[2]] - vertex[face.vertexIndex[0]];

    // ici on calcule la normale à la face
    Vecteur normal = side0.cross(side1);
    normal.normalise();

    // maintenant on calcule les coefficients des tangentes
    // ces coefficients sont calcules en fonction des
    // coordonnées de textures du polygone
    float deltaT0 = texCoords[face.texCoordIndex[1]].v -
                    texCoords[face.texCoordIndex[0]].v;
    float deltaT1 = texCoords[face.texCoordIndex[2]].v -
                    texCoords[face.texCoordIndex[0]].v;
    // on effectue la meme chose pour la binormale
    float deltaB0 = texCoords[face.texCoordIndex[1]].u -
                    texCoords[face.texCoordIndex[0]].u;
    float deltaB1 = texCoords[face.texCoordIndex[2]].u -
                    texCoords[face.texCoordIndex[0]].u;
    // le facteur permettant de rendre les tangentes et binormales
    // proportionnelles aux coordonnees de textures.
    // nous aurons donc des vecteurs dont la norme depend des coordonnees
    // de textures.
    float scale = 1/ ((deltaB0 * deltaT1) - (deltaB1 * deltaT0));

    // on calcule la tangente temporaire
    Vecteur tmpTangente = ((side0*deltaT1) - (side1*deltaT0))*scale;
    tmpTangente.normalise();

    // on calcule la binormale temporaire
    Vecteur tmpBinormal = ((side0*(-deltaB1)) + (side1*deltaB0))*scale;
    tmpBinormal.normalise();

    // nous avons donc maintenant les tangentes, binormales et normales de la face.
    // on les additionne a celles deja presentes dans les tableaux
    tangents[face.vertexIndex[0]] += tmpTangente;
    tangents[face.vertexIndex[1]] += tmpTangente;
    tangents[face.vertexIndex[2]] += tmpTangente;

    binormals[face.vertexIndex[0]] += tmpBinormal;
    binormals[face.vertexIndex[1]] += tmpBinormal;
    binormals[face.vertexIndex[2]] += tmpBinormal;

    normals[face.vertexIndex[0]] += normal;
    normals[face.vertexIndex[1]] += normal;
    normals[face.vertexIndex[2]] += normal;

    // il ne faudra pas oublier de renormaliser les vecteurs par la suite.
}

Attention à bien renormaliser les vecteurs après avoir calculé l'espace local de toutes les faces.

IV. Modifications apportées au rendu des modèles

Maintenant que nous avons toutes les informations nécessaires pour pouvoir effectuer notre éclairage par pixels, nous allons voir comment le faire en pratique. Pour cela, nous allons voir les modifications apportées au programme pour rendre les modèles. Les modifications apportées ici ne sont pas importantes au niveau taille du code, mais elles sont néanmoins primordiales pour le bon fonctionnement de l'éclairage.

Je vais commencer par une petite méthode utilitaire qui va nous servir à debugger nos calculs d'espaces locaux : la méthode drawLocalSpace qui affiche les tangentes, binormales et normales de chaque vertex. Cette méthode est très simple à comprendre, je ne rentrerais donc pas dans les détails. Tout ce qu'elle fait est d'afficher trois lignes de couleurs différentes qui suivent les trois vecteurs de notre espace local.
Le code de la méthode donne donc :

Méthode servant à dessiner l'espace local de chaque vertex
Sélectionnez

void Model::drawLocalSpace()
{
    glBegin(GL_LINES);
    for(int i = 0;i < nbFaces; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            int vertexIndex = faces[i].vertexIndex[j];
            Vecteur &v = vertex[vertexIndex];
            // tangente en rouge
            Vecteur tangent = tangents[vertexIndex] + v;
            glColor3f(1,0,0);
            glVertex3f(v.x,v.y,v.z);
            glVertex3f(tangent.x,tangent.y,tangent.z);
            // binormale en vert
            Vecteur binormal = binormals[vertexIndex] + v;
            glColor3f(0,1,0);
            glVertex3f(v.x,v.y,v.z);
            glVertex3f(binormal.x,binormal.y,binormal.z);
            // normale en bleu
            Vecteur normal = normals[vertexIndex] + v;
            glColor3f(0,0,1);
            glVertex3f(v.x,v.y,v.z);
            glVertex3f(normal.x,normal.y,normal.z);
        }
    }
    glEnd();
}

Maintenant, nous allons passer à la modification la plus importante de l'affichage : la méthode addLight, que j'ai renommé en addLightPerVertex pour pouvoir la différencier de la méthode que nous verrons dans le prochain tutoriel.

Cette méthode doit effectuer deux choses :

  1. Calculer le vecteur transformé vers la lumière.
  2. Afficher le modèle en prenant en compte le vecteur vers la lumière.

Pour calculer notre vecteur transformé vers la lumière, nous allons utiliser le calcul de la projection d'un vecteur dans un espace 3D vu plus haut. Pour cela, nous calculons d'abord le vecteur vers la lumière standard comme ceci :

Calcul du vecteur du vertex vers la lumière
Sélectionnez

Vecteur tempVect = lightPos - vertex[vertexIndex];

Puis nous calculons sa projection dans l'espace local du vertex comme cela :

Projection du vecteur vers la lumière dans l'espace local du vertex
Sélectionnez

Vecteur vertexToLight;
vertexToLight.x = tangents[vertexIndex]*tempVect;
vertexToLight.y = binormals[vertexIndex]*tempVect;
vertexToLight.z = normals[vertexIndex]*tempVect;

Comme nous l'avons vu dans la partie sur l'extension env_dot3, il nous faut envoyer le vecteur vers la lumière dans la couleur précédant l'unité de texture configuré pour calculer le bump mapping. Pour cela, nous nous contenterons d'utiliser un simple glColor ici. Ensuite, il ne nous reste plus qu'à afficher le modèle. Le code complet de la méthode donne ça :

Méthode d'ajout d'une lumière à la scène
Sélectionnez

void Model::addLightPerVertex(Light& light)
{
    Vecteur lightPos = light.getPosition();
    glBegin(GL_TRIANGLES);
    for(int i = 0;i < nbFaces; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            int vertexIndex = faces[i].vertexIndex[j];
            // on calcule le vecteur vertex->lumiere normalisee
            Vecteur tempVect = lightPos - vertex[vertexIndex];
            Vecteur vertexToLight;
            vertexToLight.x = tangents[vertexIndex]*tempVect;
            vertexToLight.y = binormals[vertexIndex]*tempVect;
            vertexToLight.z = normals[vertexIndex]*tempVect;
            vertexToLight.normalise();
            // nous avons donc maintenant le vecteur du vertex vers la lumiere
            // transforme dans l'espace local du vertex.
            // on l'envoie sous forme de couleur a openGL
            glColor3f(vertexToLight.x,vertexToLight.y,vertexToLight.z);
            // maintenant, on envoie les coordonnees de textures
            // la premiere unite de texture contient la normal map, ce sont donc
            // les meme coordonnees de textures que la scene
            TexCoord &tc = texCoords[faces[i].texCoordIndex[j]];
            glMultiTexCoord2fARB(GL_TEXTURE0_ARB,tc.u,tc.v);
            // la deuxieme unite de texture contient la texture 3D d'attenuation,
            // on calcule donc l'eclairage
            Vecteur &v = vertex[vertexIndex];
            Vecteur lightTexCoord = light.computeLighting(v);
            glMultiTexCoord3fARB(GL_TEXTURE1_ARB,lightTexCoord.x,
                                lightTexCoord.y,lightTexCoord.z);
            // la troisieme unite de texture contient la texture diffuse,
            // donc les coordonnees de texture de la scene
            glMultiTexCoord2fARB(GL_TEXTURE2_ARB,tc.u,tc.v);
            glVertex3f(v.x,v.y,v.z);
        }
    }
    glEnd();
}

Comme d'habitude, ici, nous supposons qu'OpenGL est configuré de la manière suivante : l'unité de texture 0 contient la normal map configurée pour effectuer le calcul de DOT3 bump mapping, l'unité de texture 1 contient la light map 3D d'atténuation et l'unité de texture 2 contient la texture standard de la scène (aussi appelée texture diffuse).

V. Rendu final

Maintenant que nous pouvons afficher un modèle en prenant en compte l'éclairage par pixel, nous allons voir la séquence d'appels à effectuer pour rendre la scène complète. Encore une fois, le code est relativement simple et ne devrait pas poser de problèmes. On doit, dans un premier temps, rendre la composante ambiante de la scène comme ceci :

Rendu de la composante ambiante de la scène
Sélectionnez

// on configure la premiere unité de texture : elle contient la texture de la scene
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, textureId);
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);

// on effectue le rendu de la lumiere ambiante
model->initLighting(ambiant);

Ensuite, il faut configurer les unités de textures pour qu'elles correspondent à ce dont on a besoin dans notre méthode addLight. Ça se fait comme ceci :

Code de configuration de unités de textures
Sélectionnez

// on doit reconfigurer les unites de textures
// la premiere unite contient la normal map
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, normalMapId);
// on configure la normal map pour qu'elle effectue la passe de DOT3
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_COMBINE_ARB);
glTexEnvf(GL_TEXTURE_ENV,GL_RGB_SCALE_ARB,1);
glTexEnvf(GL_TEXTURE_ENV,GL_COMBINE_RGB_ARB,GL_DOT3_RGB_ARB);
glTexEnvf(GL_TEXTURE_ENV,GL_SOURCE0_RGB_ARB,GL_PREVIOUS_ARB);
glTexEnvf(GL_TEXTURE_ENV,GL_SOURCE1_RGB_ARB,GL_TEXTURE);
glTexEnvf(GL_TEXTURE_ENV,GL_OPERAND0_RGB_ARB,GL_SRC_COLOR);
glTexEnvf(GL_TEXTURE_ENV,GL_OPERAND1_RGB_ARB,GL_SRC_COLOR);

// la deuxieme contient la texture d'attenuation
glActiveTextureARB(GL_TEXTURE1_ARB);
glEnable(GL_TEXTURE_3D_EXT);
glBindTexture(GL_TEXTURE_3D_EXT,lightmap );
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
// la troisieme contient la texture de diffuse
glActiveTextureARB(GL_TEXTURE2_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,textureId );
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);

Et maintenant, il ne nous reste plus qu'à ajouter les lumières en activant le blending additif, puis à désactiver les unités de textures.

Sélectionnez

// on doit activer le blending additif pour pouvoir utiliser ce mode d'éclairage
glEnable(GL_BLEND);
glBlendFunc(GL_ONE,GL_ONE);
glDepthMask(GL_FALSE);

// on ajoute nos lumieres
model->addLightPerVertex(light1);
model->addLightPerVertex(light2);

// on desactive la troisieme unite de texture
glActiveTextureARB(GL_TEXTURE2_ARB);
glDisable(GL_TEXTURE_2D);
// on desactive la seconde unite de texture
glActiveTextureARB(GL_TEXTURE1_ARB);
glDisable(GL_TEXTURE_3D_EXT);
// on desactive la premiere unite de texture
glActiveTextureARB(GL_TEXTURE0_ARB);
glDisable(GL_TEXTURE_2D);

glDepthMask(GL_TRUE);
glDisable(GL_BLEND);

Ce qui nous permet d'avoir le rendu final suivant :

Image non disponible
Le rendu final de la scène.

VI. Conclusions

Voila, nous avons un système de gestion de l'éclairage par pixel simplifié qui fonctionne. Pourquoi simplifié ? Tout simplement car il manque quelques éléments pour pouvoir effectivement prétendre gérer correctement les lumières.

Premièrement, nous calculons le vecteur vers la lumière par vertex. Nous retombons donc sur les mêmes problèmes qu'avec l'éclairage par vertex, à savoir : l'interpolation des vecteurs va nous donner des vecteurs non normalisés aux centres des polygones, et si les polygones sont trop grands, on peut tomber sur des cas ou l'éclairage semble venir d'une direction alors que la lumière n'est pas située dans cette direction. C'est dommage d'avoir un éclairage par pixel et de retomber sur les mêmes problèmes que l'éclairage par vertex. Nous verrons donc dans le prochain tutoriel comment résoudre ce problème en utilisant une cube map de normalisation pour définir le vecteur vers la lumière au lieu d'utiliser la couleur du vertex.

Le second problème posé par le système actuel est qu'on ne peut plus avoir de lumières colorées. Elles sont forcéments blanches. Nous verrons donc dans le prochain tutoriel une méthode basée sur un rendu multi passes pour obtenir des lumières colorées.

Liens▲

Vous pouvez télécharger les sources de ce tutoriel ici ou ici [http]
Une version PDF de ce tutoriel est disponible ici ou ici [http]


Gestion dynamique de la lumière avec OpenGL - Partie 5 : éclairage par pixel standard

  • I. Introduction.
  • II. La « cube map » de normalisation.
    • II-1. Pourquoi avons nous besoin d'une cube map de normalisation.
    • II-2. Qu'est ce qu'une cube map de normalisation.
    • II-3. Comment calculer la cube map de normalisation.
  • III. Modification du rendu.
    • III-1. Utiliser la cube map de normalisation.
    • III-2. Rendu des lumières colorées.
    • III-3. Le rendu final.
  • IV. Conclusions.
  • Liens

Dans ce tutoriel, nous allons approfondir la gestion de l'éclairage par pixel vu dans le tutoriel précédent. Nous allons notamment voir comment utiliser une cube map de normalisation pour résoudre les problèmes liés à l'interpolation des vecteurs sur les polygones larges. Je présenterais aussi une technique basée sur un rendu multi-passes pour pouvoir afficher des lumières colorées.

I. Introduction.

Nous avons vu dans le tutoriel précédent comment calculer l'éclairage par pixel. Pour cela, nous avons besoin d'une normal map qui représente les normales de la surface. Nous avons aussi besoin de calculer un espace local par vertex qui nous permet de calculer le vecteur vers la lumière qui nous sert pour le calcul du DOT3 bump mapping. Comme nous l'avons vu, bien qu'il soit assez peu visible dans le tutoriel précédent, le fait d'utiliser les vertex pour envoyer le vecteur vers la lumière pose problème. En effet, l'interpolation des vecteurs donne un résultat non normalisé, et on risque aussi d'avoir des artefacts plutôt gênant qui éclairent le polygone d'une façon incohérente par rapport à la position de la lumière. Pour résoudre ce problème, nous avons besoin d'un mécanisme qui nous permet d'avoir des vecteurs qui pointent tous effectivement dans la direction de la lumière, pas seulement au niveau des vertex mais partout sur le polygone. En plus, il serait intéressant de pouvoir améliorer par la même occasion le problème de normalisation des vecteurs pour avoir un éclairage plus cohérent. Pour cela, je vais présenter une solution basé sur l'utilisation d'une cube map particulière appelée cube map de normalisation.

Un autre problème du tutoriel précédent est que nous ne pouvons plus afficher de lumières colorées. En effet, l'extension env_dot3 utilise la couleur des vertex pour effectuer le calcul de DOT3 bump mapping. Comme nous n'avons pas de moyen standard pour pouvoir modifier la couleur de sortie du multi texturing sans utiliser de fragment program/shader, je vais présenter une solution basée sur un rendu multi passes.

II. La « cube map » de normalisation.

Nous allons voir dans cette partie ce qu'est une cube map de normalisation, pourquoi et comment l'utiliser.

II-1. Pourquoi avons nous besoin d'une cube map de normalisation.

Comme je l'ai dit dans l'introduction, l'utilisation de la couleur des vertex pour envoyer un vecteur n'est pas satisfaisante. Il nous faut un mécanisme permettant d'avoir des vecteurs qui pointent bien tous vers la lumière, et, si possible, qui soient normalisés. Comme pour le light mapping, la solution aux problèmes d'interpolation se base sur l'utilisation de textures. Néanmoins, contrairement au light mapping, ici, nous n'utiliserons pas une texture 2D ou 3D, mais une cube map. L'avantage de la cube map est qu'elle permet de calculer automatiquement une projection multidirectionnelle de textures. Si vous ne voyez pas ce qu'est une projection multidirectionnelle de texture, imaginez simplement une projection de texture dans une direction, puis effectuez cette même projection dans les six directions d'un cube. Voila, vous y êtes, la cube map permet de projeter six textures comme si elles etaient sur un cube projeté à l'infini. A l'origine, les cube maps étaient prévus pour calculer des pseudo réflexions sur des objets, mais en les utilisant d'une certaine manière, elles peuvent résoudre nos problèmes de vecteurs vers la lumière. C'est ce que nous allons voir tout de suite.

II-2. Qu'est ce qu'une cube map de normalisation.

Comme nous l'avons vu au dessus, on peut appréhender les cube maps comme des cubes infiniment lointains autour d'une position. Ceci permet d'avoir à moindre coût une projection dans les six directions d'un cube. Tout ceci est bien beau, mais en quoi cela va-t-il résoudre notre problème de vecteurs? Tout simplement en remplissant notre cube map de vecteurs.

En fait, ce qu'on appel une cube map de normalisation n'est rien d'autre qu'une cube map dont chaque texel est un vecteur pointant vers le centre de la cube map. Ainsi, lorsqu'on projette notre cube map sur un polygone, on obtient un polygone dont tous les pixels sont des vecteurs dirigés vers le centre de la cube map. Il ne nous reste donc plus qu'à placer notre cube map à l'emplacement de la lumière pour que notre polygone soit couvert de vecteurs vers la lumière. En plus, comme chaque texel de la cube map est un vecteur normalisé, le problème des vecteurs non normalisés à cause de l'interpolation entre les vertex est réduit. Par contre, il n'est pas non plus totalement éliminé, étant donné qu'on peut toujours avoir de l'interpolation entre les texels de la cube map. Néanmoins, le résultat est très nettement supérieur à celui de la méthode par vertex.

Comme souvent, un schéma explique mieux qu'un long discours.

Image non disponible
Cube map de normalisation

Voici le contenu d'une cube map de normalisation : chaque texel représente un vecteur pointant vers le centre de la cube map.

Image non disponible
Projection d'une cube map de normalisation sur des polygones.

Et voila le résultat de l'utilisation d'une cube map sur des polygones : les cotés de la cube map sont projetés sur les polygones, les remplissant de vecteurs dirigés vers le centre de la cube map.

Maintenant que nous savons ce qu'est une cube map de normalisation, nous allons voir comment la calculer.

II-3. Comment calculer la cube map de normalisation.

Tout d'abord, nous allons nous occuper de gérer l'extension. Comme pour l'extension env_dot3, les cube maps n'ont pas besoin d'utiliser de nouvelles fonctions. Le chargement de l'extension est donc simple, il suffit de vérifier si la carte supporte les cube maps. La fonction de chargement des extensions devient donc ça :

Fonction de chargement des extensions
Sélectionnez

void initExtensions()
{
    if (glutExtensionSupported("GL_ARB_multitexture"))
    {
        glMultiTexCoord2fARB =		(PFNGLMULTITEXCOORD2FARBPROC)wglGetProcAddress("glMultiTexCoord2fARB");
        glMultiTexCoord3fARB =		(PFNGLMULTITEXCOORD3FARBPROC)wglGetProcAddress("glMultiTexCoord3fARB");
        glMultiTexCoord4fARB =		(PFNGLMULTITEXCOORD4FARBPROC)wglGetProcAddress("glMultiTexCoord4fARB");
        glActiveTextureARB =		(PFNGLACTIVETEXTUREARBPROC)wglGetProcAddress("glActiveTextureARB");
        glClientActiveTextureARB =	(PFNGLCLIENTACTIVETEXTUREARBPROC)wglGetProcAddress("glClientActiveTextureARB");
        if (!glActiveTextureARB || !glMultiTexCoord2fARB || !glMultiTexCoord3fARB
            || !glMultiTexCoord4fARB || !glClientActiveTextureARB)
        {
            fprintf(stderr,"impossible d'initialiser l'extension GL_ARB_multitexture\n");
            exit(0);
        }
    }
    else
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_ARB_multitexture\n");
        exit(0);
    }
    if (glutExtensionSupported("GL_EXT_texture3D"))
    {
        glTexImage3DEXT = (PFNGLTEXIMAGE3DEXTPROC) wglGetProcAddress("glTexImage3DEXT");
        glTexSubImage3DEXT = (PFNGLTEXSUBIMAGE3DEXTPROC) wglGetProcAddress("glTexSubImage3DEXT");
        if (!glTexImage3DEXT || !glTexSubImage3DEXT)
        {
            fprintf(stderr,"impossible d'initialiser l'extension GL_EXT_texture3D\n");
            exit(0);
        }
    }
    else
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_ARB_multitexture\n");
        exit(0);
    }
    if (!glutExtensionSupported("GL_ARB_texture_env_dot3"))
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_ARB_texture_env_dot3\n");
        exit(0);
    }
    if (!glutExtensionSupported("GL_ARB_texture_cube_map"))
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_ARB_texture_cube_map\n");
        exit(0);
    }
}

Maintenant, nous pouvons voir comment calculer la cube map. Pour cela, j'utilise deux fonctions. La première, getCubeVector, permet de récupérer un vecteur pointant vers le centre de la cube map en fonctions de la face du cube et de la position du texel dans la face. Cette fonction est assez simple à comprendre et donne le code suivant :

Fonction permettant de calculer un vecteur vers le centre de la cube map en fonction de sa face et de sa position dans la face.
Sélectionnez

void getCubeVector(int i, int cubesize, int x, int y, float *vector)
{
    float s, t, sc, tc, mag;

    s = ((float)x + 0.5) / (float)cubesize;
    t = ((float)y + 0.5) / (float)cubesize;
    sc = s*2.0 - 1.0;
    tc = t*2.0 - 1.0;

    // selon la face passee en parametre, on calcul le vecteur vers le centre de la cube map.
    switch (i)
    {
        case 0:
            vector[0] = 1.0;
            vector[1] = -tc;
            vector[2] = -sc;
            break;
        case 1:
            vector[0] = -1.0;
            vector[1] = -tc;
            vector[2] = sc;
            break;
        case 2:
            vector[0] = sc;
            vector[1] = 1.0;
            vector[2] = tc;
            break;
        case 3:
            vector[0] = sc;
            vector[1] = -1.0;
            vector[2] = -tc;
            break;
        case 4:
            vector[0] = sc;
            vector[1] = -tc;
            vector[2] = 1.0;
            break;
        case 5:
            vector[0] = -sc;
            vector[1] = -tc;
            vector[2] = -1.0;
            break;
    }
    // on normalise le vecteur.
    mag = 1.0/sqrt(vector[0]*vector[0] + vector[1]*vector[1] + vector[2]*vector[2]);
    vector[0] *= mag;
    vector[1] *= mag;
    vector[2] *= mag;
}

La deuxième fonction utilisée est celle qui calcule effectivement la cube map et l'envoi à OpenGL. Cette fonction prend en paramètre la taille en texel d'un coté de la cube map et génère donc une cube map de normalisation. Pour cela, elle utilise un buffer de la taille d'un coté de la cube map et s'en sert pour générer les six cotés différents en utilisant la fonction getCubeVector pour récupérer chaque texel. Le code de la fonction donne donc ça :

Fonction de chargement d'une cube map de normalisation.
Sélectionnez

void generateNormalisationCubeMap(int size)
{
    glEnable(GL_TEXTURE_CUBE_MAP_ARB);
    //genere l'objet de texture OpenGL en memoire vidéo
    glGenTextures(1,&normalCubeMapId);
    glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, normalCubeMapId);

    float vector[3];
    int i, x, y;
    GLubyte *pixels = new GLubyte[size*size*3];

    // on configure la texture. On utilise un filtrage lineaire pour ne pas avoir à
    // generer les mipmaps.
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

    // Pour chaque face
    for (i = 0; i < 6; i++)
    {
        // on remplit le buffer de texture avec des vecteurs
        for (y = 0; y < size; y++)
        {
            for (x = 0; x < size; x++)
            {
                getCubeVector(i, size, x, y, vector);
                pixels[3*(y*size+x) + 0] = 128 - 127*vector[0];
                pixels[3*(y*size+x) + 1] = 128 - 127*vector[1];
                pixels[3*(y*size+x) + 2] = 128 - 127*vector[2];
            }
        }
        // on envoi le buffer de texture à OpenGL
        gluBuild2DMipmaps(GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB+i,GL_RGB8,size,size,GL_RGB,GL_UNSIGNED_BYTE,pixels);
    }

    delete [] pixels;

    glDisable(GL_TEXTURE_CUBE_MAP_ARB);
}

Ici, je n'utilise qu'un filtrage linéaire, je n'ai donc pas généré les différents niveaux de mipmap, mais il faudrait les générer en cas d'utilisation de filtrage autre.

Ces fonctions sont des versions légèrement modifiées des fonctions utilisées dans les démo de nVidia sur le bump mapping qu'on peut trouver dans la section développeurs du site nvidia.com.

III. Modification du rendu.

Maintenant que nous savons comment calculer notre cube map de normalisation, nous allons voir comment l'utiliser. Ensuite, nous verrons comment faire pour avoir des lumières colorées, puis nous verrons le rendu final comme sur tous les tutoriaux précédents.

III-1. Utiliser la cube map de normalisation.

Maintenant que nous avons notre cube map de normalisation, il faut encore savoir comment l'utiliser. En fait, c'est très simple. Pour cela, il suffit de calculer pour chaque vertex les bonnes coordonnées de texture pour la cube map, et la projection automatique de la cube map va se charger pour nous de recouvrir le polygone comme il faut.

Ces coordonnées de textures sont en faite très simples à calculer, il s'agit tout simplement... d'un vecteur encore une fois. En effet, notre cube map peut être vu comme une sorte particulière de texture en 3D, donc pour récupérer ses informations, il faut lui passer des coordonnées de texture 3D. Voici un petit schéma qui montre comment se calculent les coordonnées de texture de la cube map.

Image non disponible
Le calcul valide des coordonnées de texture de la cube map.

Ici, le vecteur noir représente des coordonnées de texture qui vont adresser une face de la cube map, ensuite, la carte graphique va se charger de tous les calculs de projection. On peut noter ici que contrairement au tutoriel précédent, le vecteur calculé est le vecteur de la lumière vers le vertex. Dans le tutoriel précédent, nous avions fait l'inverse. Le problème est que, pour calculer notre bump mapping, nous avons besoin d'un vecteur allant du vertex vers la lumière, mais notre cube map contient des vecteurs dans le mauvais sens comme le montre le schéma suivant.

Image non disponible
Le calcul non valide des coordonnées de texture de la cube map : le vecteur final au niveau du vertex est dans le mauvais sens.

Nous voulons donc un vecteur dirigé dans l'autre sens comme sur le premier schéma, c'est pour ça que nous calculons le vecteur de la lumière vers le vertex.

A part ce point, et le fait que les unités de textures soient configurées de manières différentes du tutoriel précédent, le code de la méthode pour ajouter une lumière est relativement proche de celui du tutoriel précédent. J'ai néanmoins changé le nom de la méthode en addLightCubeMap pour bien faire la différence entre les deux tutoriaux. Le code de la méthode donne donc :

Méthode d'ajout d'une lumière à la scène avec éclairage par pixel
Sélectionnez

void Model::addLightCubeMap(Light& light)
{
    Vecteur lightPos = light.getPosition();
    glBegin(GL_TRIANGLES);
    for(int i = 0;i < nbFaces; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            int vertexIndex = faces[i].vertexIndex[j];
            // on calcule le vecteur lumiere->vertex projete dans l'espace local du vertex
            Vecteur tempVect = vertex[vertexIndex] - lightPos;
            Vecteur lightToVertex;
            lightToVertex.x = tangents[vertexIndex]*tempVect;
            lightToVertex.y = binormals[vertexIndex]*tempVect;
            lightToVertex.z = normals[vertexIndex]*tempVect;
            // nous avons donc maintenant le vecteur de la lumiere vers le vertex
            // transforme dans l'espace local du vertex.
            // on l'envoie en temps que coordonnees de texture pour la cube map
            glMultiTexCoord3fARB(GL_TEXTURE0_ARB,lightToVertex.x,lightToVertex.y,lightToVertex.z);
            // maintenant, on envoie les coordonnees de textures
            // la seconde unite de texture contient la normal map, ce sont donc
            // les eme coordonnees de textures que la scene
            TexCoord &tc = texCoords[faces[i].texCoordIndex[j]];
            glMultiTexCoord2fARB(GL_TEXTURE1_ARB,tc.u,tc.v);
            // la troisieme unite de texture contient la texture 3D d'attenuation,
            // on calcule donc l'eclairage
            Vecteur &v = vertex[vertexIndex];
            Vecteur lightTexCoord = light.computeLighting(v);
            glMultiTexCoord3fARB(GL_TEXTURE2_ARB,lightTexCoord.x,lightTexCoord.y,lightTexCoord.z);
            // la quatrieme unite de texture contient la texture de diffuse,
            // donc les coordonnees de textures de la scene
            glMultiTexCoord2fARB(GL_TEXTURE3_ARB,tc.u,tc.v);
            glVertex3f(v.x,v.y,v.z);
        }
    }
    glEnd();
}

Comme d'habitude, la méthode suppose qu'OpenGL est bien configuré, à savoir : la première unité de texture contient la cube map de normalisation, la deuxième contient la normal map configurée pour effectuer la passe de DOT3, la troisième contient la texture 3D d'atténuation et la dernière contient la texture diffuse de la scène. De plus, le blending doit être positionné en mode additif pour que les lumières puissent bien être ajoutées à la scène.

III-2. Rendu des lumières colorées.

Ca y est nous avons un rendu de l'éclairage par pixel correct, mais il nous manque quand même un élément important que nous avions avec l'éclairage par vertex et les light maps : nos lumières ne sont plus colorées. Le problème, c'est que nous ne pouvons plus modifier la couleur de notre lumière via glColor car le fait d'utiliser l'extension env_dot3 nous génère forcément un résultat en niveaux de gris, quelque soit la couleur précédente du pixel. Comme il n'existe pas d'extensions standard pour modifier la couleur après la passe de DOT3 (on peut le faire avec des extension propriétaires comme les register combiner ou encore les textures shaders sur GeForce 4, mais je tiens à n'utiliser que des extensions validées par l'ARB.), et comme on n'utilise pas de fragment program/shaders ici, il va falloir passer à un rendu multi passes pour obtenir nos lumières colorées.

Pour cela, il nous suffit simplement d'effectuer une passe supplémentaire par lumières en multipliant le résultat précédent par la couleur souhaitée. Pour éviter au maximum les changements d'état OpenGL, on peut organiser ça en rendant d'abord toutes les lumières avec le bump mapping, puis on effectue une seconde passe en multipliant toutes les lumières par leur couleur. Le problème, c'est que si on utilise le blending multiplicatif simple, on ne pourra effectuer cette méthode qu'avec une seule lumière. En effet, le fait de multiplier la scène va rendre noires toutes les parties qui ne sont pas dans la lumière courante, on obtient donc seulement l'intersection de toutes les lumières de la scène. Il nous faut donc trouver une autre solution.
Cette solution est toute simple, elle consiste à utiliser le blending multiplicatif+additif présenté dans le premier tutoriel sur le light mapping. Ainsi, on peut multiplier la lumière par sa couleur sans pour autant noircir le reste de la scène. Cela donne un rendu de la scène plus clair et les lumières peuvent saturer vers le blanc, mais le resultat est acceptable.
L'algorithme de rendu donne donc le pseudo code suivant :

Pseudo code de l'algorithme de rendu des lumières colorées.
Sélectionnez

Rendu de la couleur ambiante
Blending additif
Pour chaque lumière :
    Ajouter la passe de bump mapping à la scène
Fin pour
Blending multiplicatif+additif
Pour chaque lumière :
    Multiplier la scène précédente par la couleur de la lumière.
Fin pour

Comme vous pouvez le voir, il nous faut pouvoir afficher juste la couleur de nos lumières pour la deuxième passe. Ceci correspond en fait à une passe d'affichage de la texture d'atténuation, autrement dit : une simple passe de light mapping comme nous l'avons fait dans les tutoriaux 2 et 3, mais sans multiplier le tout par la texture diffuse de la scène. Ici, nous ne rendons que la light map dynamique avec la couleur de la lumière. Ceci nous donne donc la méthode suivante :

Méthode de rendu de la couleur de la lumière.
Sélectionnez

void Model::renderColor(Light& light)
{
    glColor3f(light.getColor().r,light.getColor().g,light.getColor().b);
    Vecteur lightPos = light.getPosition();
    glBegin(GL_TRIANGLES);
    for(int i = 0;i < nbFaces; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            Vecteur &v = vertex[faces[i].vertexIndex[j]];
            Vecteur lightTexCoord = light.computeLighting(v);
            // la premiere unité de texture contient la texture d'attenuation
            glMultiTexCoord3fARB(GL_TEXTURE0_ARB,lightTexCoord.x,lightTexCoord.y,lightTexCoord.y);
            glVertex3f(v.x,v.y,v.z);
        }
    }
    glEnd();
}

III-3. Le rendu final.

Maintenant que nous avons tous les éléments en main pour rendre nos lumières, nous pouvons voir comment effectuer le rendu final. Voici d'abord le code nécessaire pour rendre les lumières par pixel correctement sans prendre en compte la couleur des lumières :

Code de rendu des lumières par pixel sans couleur
Sélectionnez

// on configure la premiere unite de texture : elle contient la texture de la scene
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, textureId);
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);

// on effectue le rendu de la lumière ambiante
model->initLighting(ambiant);
if (drawLocalSpace)
{
    glDisable(GL_TEXTURE_2D);
    model->drawLocalSpace();
}

// on doit reconfigurer les unites de textures
// la premiere unite contient la cube map de normalisation
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_CUBE_MAP_ARB);
glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, normalCubeMapId);
// on souhaite que la cube map remplace la couleur des vertex
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_REPLACE);

// la deuxieme unité de texture contient la normal map
glActiveTextureARB(GL_TEXTURE1_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, normalMapId);
// on configure la normal map pour qu'elle effectue la passe de DOT3
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_COMBINE_ARB);
glTexEnvf(GL_TEXTURE_ENV,GL_RGB_SCALE_ARB,1);
glTexEnvf(GL_TEXTURE_ENV,GL_COMBINE_RGB_ARB,GL_DOT3_RGB_ARB);
glTexEnvf(GL_TEXTURE_ENV,GL_SOURCE0_RGB_ARB,GL_PREVIOUS_ARB);
glTexEnvf(GL_TEXTURE_ENV,GL_SOURCE1_RGB_ARB,GL_TEXTURE);
glTexEnvf(GL_TEXTURE_ENV,GL_OPERAND0_RGB_ARB,GL_SRC_COLOR);
glTexEnvf(GL_TEXTURE_ENV,GL_OPERAND1_RGB_ARB,GL_SRC_COLOR);

// la troisieme contient la texture d'attenuation
glActiveTextureARB(GL_TEXTURE2_ARB);
glEnable(GL_TEXTURE_3D_EXT);
glBindTexture(GL_TEXTURE_3D_EXT,lightmap );
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
// la quatrieme contient la texture de diffuse
glActiveTextureARB(GL_TEXTURE3_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,textureId );
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);

// on doit activer le blending additif pour pouvoir utiliser ce mode d'eclairage
glEnable(GL_BLEND);
glBlendFunc(GL_ONE,GL_ONE);
glDepthMask(GL_FALSE);

// on ajoute nos lumières
model->addLightCubeMap(light1);
model->addLightCubeMap(light2);

// on desactive la quatrieme unite de texture
glActiveTextureARB(GL_TEXTURE3_ARB);
glDisable(GL_TEXTURE_2D);
// on desactive la troisieme unite de texture
glActiveTextureARB(GL_TEXTURE2_ARB);
glDisable(GL_TEXTURE_3D_EXT);
// on desactive la seconde unite de texture
glActiveTextureARB(GL_TEXTURE1_ARB);
glDisable(GL_TEXTURE_2D);
// on desactive la premiere unite de texture
glActiveTextureARB(GL_TEXTURE0_ARB);
glDisable(GL_TEXTURE_CUBE_MAP_ARB);

// on desactive le blending
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);

Et maintenant, le code pour avoir des lumières colorées. Ce code s'ajoute simplement à la suite du précédent (mais avant la désactivation du blending).

Code de rendu des lumières colorées
Sélectionnez

glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_3D_EXT);
glBindTexture(GL_TEXTURE_3D_EXT,lightmap );
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);

glBlendFunc(GL_DST_COLOR,GL_ONE);
model->renderColor(light1);
model->renderColor(light2);
glActiveTextureARB(GL_TEXTURE0_ARB);
glDisable(GL_TEXTURE_3D_EXT);

Et voila le résultat final de notre gestion de l'éclairage par pixel avec comme d'habitude 3 lumières : 1 rouge, une bleue et une blanche.

Image non disponible
Le rendu final de la scène.

IV. Conclusions.

Ca y est, nous avons une gestion de l'éclairage par pixel fonctionnelle et complète. Enfin, complète, c'est très relatif. Comme je le disais au début de ce tutoriel, on en veut toujours plus, et, si vous vous souvenez du premier tutoriel, j'avais parlé de la composante spéculaire de la lumière, or, elle n'est pas gérée ici. C'est pourquoi nous allons voir dans le prochain tutoriel comment mettre en place un système de gestion des lumières complexes basé sur l'utilisation de shaders. Nous y verrons donc comment gérer la composante spéculaire de l'éclairage et en plus, nous y ajouterons la gestion du parallax mapping (aussi appelé offset bump mapping), qui permet de déformer les textures pour donner un effet de relief impressionnant pour un coût très faible.
Néanmoins, la technique présentée ici peut être très suffisante pour la plus part des moteurs graphiques amateurs. En effet, il ne faut pas oublier que si l'éclairage par pixel a un très bon rendu visuel, il impose au graphistes de créer des normal maps pour les modèles et pour le niveau, ce qui est un surcoût important en temps de production, et le modèle d'éclairage présenté dans le prochain tutoriel sera encore plus coûteux en terme de création de textures.

Il existe une technique permettant de se passer de cube map de normalisation, mais qui n'est pas non plus totalement satisfaisante. Elle consiste à stocker des vecteurs directement dans la texture 3D. Chaque vecteur pointe vers le centre de la texture, et surtout, sa longueur est proportionnelle à l'éloignement du texel par rapport au centre de la texture. On a donc des vecteurs dont la norme est proche de 1 vers le centre de la texture et nulle sur les bords de la texture. Ainsi, la texture 3D permet de stocker en même temps les vecteurs vers la lumière et l'atténuation de l'éclairage. Le problème de cette méthode est que les texels au centre de la texture vont eux aussi être nul (car pointant sur eux même étant donné qu'ils sont au centre). On a donc un trou noir au centre de notre lumière. Si la lumière ne s'approche pas trop des polygones, cet artefact ne se verra pas, mais ça n'en reste pas moins une limitation importante. Je ne présenterais donc pas plus en détail cette technique.

Liens

Vous pouvez télécharger les sources de ce tutoriel ici ou ici [http]
Une version PDF de ce tutoriel est disponible ici ou ici [http]


Gestion dynamique de la lumière avec OpenGL - Partie 6 : éclairage complexe à base de shaders

  • I. Les concepts lié à la lumière.
    • I-1. L'éclairage spéculaire.
    • I-2. Les materials.
    • I-3. Le parallax mapping.
  • II. Modifications apportée au programme.
    • II-1. Gestion des extensions.
    • II-2. Modification du rendu des modèles.
    • II-3. Les vertex et fragment programs.
    • II-4. Le rendu final.
  • III. Réflexions sur la gestion de la lumière.
  • IV. Conclusions.
  • VI. Liens

Ce tutoriel représente le dernier de cette série de tutoriaux sur la gestion de l'éclairage dynamique en temps réel avec OpenGL. Dans ce tutoriel, je présente un système d'éclairage complexe à base de shaders. Nous y verrons notamment comment gérer la composante spéculaire de la lumière, mais aussi une méthode permettant une meilleure impression de relief sur nos textures : le parallax mapping. Attention : ce tutoriel requiert d'avoir bien compris les tutoriaux précédents ou d'être familier avec les concepts de la lumière pour être bien compris. De même, des notions sur le fonctionnement des vertex/fragments programs sont utiles pour une bonne compréhension. Ce tutoriel n'a pas pour but d'expliquer le fonctionnement de ces extensions.

I. Les concepts lié à la lumière.

Dans cette partie, je vais (re)présenter les concepts fondamentaux du fonctionnement de la lumière, puis je parlerai d'une notion très liée à la lumière : la gestion des materials, et finalement, je parlerai du parallax mapping.

I-1. L'éclairage spéculaire.

Nous avons déjà vu dans les tutoriaux précédents une grande partie du fonctionnement de la lumière. Nous savons donc comment calculer l'atténuation de la lumière via une texture 3D d'atténuation. Nous avons aussi vu comment prendre en compte l'orientation de la face par pixel grâce au calcul du produit scalaire via l'extension env_dot3. Il ne nous reste plus qu'à voir comment fonctionne la composante spéculaire de la lumière.

La composante spéculaire de la lumière est en fait la capacité qu'a une surface à refléter la lumière. On parle souvent de reflets spéculaires. Pour bien voir ce que représente cette composante spéculaire, il suffit d'imaginer la différence entre une surface chromée et un pneu. Les deux surfaces seront noires en l'absence de lumière, mais si il y a une lumière, la surface chromée sera plus brillante que le pneu. Ceci est dû au fait que la surface chromée renvoi plus de lumière que le pneu qui, au contraire, en absorbe la majeur partie.

Pour calculer la composante spéculaire, il suffit de calculer le produit scalaire entre la normal à la surface et le vecteur de demi angle... qu'est ce que c'est que le vecteur de demi angle ? C'est tout simplement le vecteur représentant la moitié de l'angle entre le vecteur vers la lumière et le vecteur vers la camera. Comme ce vecteur dépend de la position de la camera, nous avons donc bien un calcul qui va changer en fonction de la camera et donc donner l'impression de reflet.

Image non disponible
Le vecteur de demi angle représente le vecteur median au vecteurs vers la lumière et vers oeil.

Pour calculer le vecteur de demi angle, rien de plus simple, il suffit d'additionner le vecteur vers la lumière et le vecteur vers la camera, et on obtient notre vecteur. Il ne nous reste plus qu'a le normaliser et voila, on peut calculer notre produit scalaire pour obtenir la composante spéculaire. Malheureusement, si on effectue simplement le calcul comme cela, on obtient une surface bien trop réfléchissante. Pour réduire cet effet de surbrillance, on utilise une fonction puissance qui va réduire la réflexivité des pixels peu éclairés tout en préservant la luminosité des pixels fortement éclairés.

Voici le résultat de la composante spéculaire seule sans utiliser de fonction puissance :

Image non disponible
Composante spéculaire sans utiliser de fonction puissance (et sans atténuation)

Et le résultat avec fonction puissance :

Image non disponible
Composante spéculaire avec fonction puissance.

On perçoit nettement mieux l'impression que la lumière se reflète sur les surfaces avec une fonction puissance. Dans ce tutoriel, le reflet spéculaire est mis à la puissance 8.

Le but de la composante spéculaire étant de faire saturer la couleur des pixels aux endroits de forte réflexion, elle est donc additionnée aux autres composantes de la lumière. Le calcul de la luminosité finale devient donc :

Luminosité = ambiante + ( (diffuse + spéculaire) * atténuation)

Le problème avec cette équation, c'est qu'elle impose d'avoir le même taux de reflets spéculaires sur toute la surface, or il est intéressant de pouvoir spécifier cela par pixel pour pouvoir, par exemple, simuler des surface brillantes par endroit et mat à d'autres endroits. Un bon exemple pour comprendre l'intérêt de pouvoir moduler la composante spéculaire est une plaque de métal rouillée. La ou le métal n'est pas rouillé, on a un reflet spéculaire maximum, mais la ou il est rouillé, il n'y a pas de reflets. Pour mieux illustrer l'intérêt de la composante spéculaire, voici deux images de notre scène finale : la première sans reflets spéculaires.

Image non disponible
Scène sans reflets spéculaires.

Et le deuxième avec des reflets spéculaires.

Image non disponible
Scène avec reflets spéculaires.

Pour pouvoir effectuer cela, nous allons utiliser une gloss map, qui est une des composantes des materials que nous allons voir maintenant.

I-2. Les materials.

Dans le model d'éclairage d'OpenGL, on peut spécifier à chaque vertex sa couleur ambiante, sa couleur diffuse, et sa couleur spéculaire, or maintenant, nous n'utilisons plus de l'éclairage par vertex mais par pixel. Il est dommage de devoir spécifier ces composantes par vertex alors que le reste des calculs d'éclairage va se faire par pixel. C'est pour cela que nous avons besoin de la même notion de materials qu'OpenGL, mais cette fois ci par pixel (pour pouvoir simuler l'effet de la plaque de métal rouillée vu plus haut par exemple.). Pour cela, nous allons utiliser plusieurs textures par materials.

  • La texture de diffuse : Elle représente la couleur du material à son éclairage maximum. Contrairement au textures qu'on aperçoit généralement dans les jeux, notre texture de diffuse ne doit par contenir d'information d'éclairage, c'est-à-dire, qu'elle ne doit pas être pré ombrée, sinon, ces informations d'éclairage vont entrer en conflit avec notre éclairage en temps réel (en étant mal orienté par exemple). Voici la texture de diffuse utilisée dans ce tutoriel.
    Image non disponible
  • La normal map : Bien qu'elle ne soit pas une des composante direct de l'éclairage, la normal map nous sert à calculer la composante diffuse et spéculaire. Elle représente le volume virtuel de notre surface comme nous l'avons vu dans le quatrième tutoriel. Pour réduire la place occupée sur le disque par les textures, on peut retirer la normal map de la height map, que nous verrons plus loin, mais je ne le fais pas dans ce tutoriel. Voici la normal map utilisée dans ce tutoriel. Image non disponible
  • La gloss map : Comme nous l'avons vu plus haut, la gloss map permet de spécifier par pixel l'intensité, mais aussi la couleur des reflets spéculaires. On peut par exemple faire une gloss map jaune pour l'utiliser sur un material représentant de l'or, ainsi, on s'assure que, quelque soit la couleur de la lumière qui éclaire notre surface, le reflet spéculaire sera toujours jaune. Voici la gloss map utilisée dans ce tutoriel. Image non disponible
  • La height map : Ici, la height map n'entre pas dans le calcul de la lumière (bien qu'on puisse l'utiliser pour calculer la normal map), mais elle va nous servir au calcul du parallax mapping. La height map est une texture représentant, comme son nom l'indique, la hauteur de chaque pixel. Il peut être préférable d'utiliser des height maps servant à générer des normal maps au lieu d'utiliser directement des normal maps pour plusieurs raisons. En effet, une height map en niveau de gris prend moins de place sur le disque, permet de générer la normal map et de parallax mapping, mais il y a aussi des considérations qualitatives que je ne présenterai pas ici. Voici la height map utilisée dans ce tutoriel. Image non disponible

Vous aurez peut être remarqué qu'il n'y a pas d'ambiante map... Cela est dû au fait que la lumière ambiante peut être obtenu à partir de la texture de diffuse.

I-3. Le parallax mapping.

Bien qu'il ne soit pas directement lié à la gestion de la lumière, le parallax mapping n'est néanmoins utilisable qu'avec un système d'éclairage par pixel. En effet, le parallax mapping consiste à déformer les textures en fonction d'une height map pour obtenir un effet de volume plus prononcé qu'avec simplement du bump mapping. Il n'y a donc aucun intérêt à utiliser du parallax mapping avec du simple light mapping par exemple.

Un gros avantage du parallax mapping est qu'il est très peu consommateur en ressources et qu'il est facile à mettre en oeuvre. En effet, il suffit d'avoir une height map (on peut la stocker dans la composante alpha de la normal map) et un vecteur du pixel vers la camera pour calculer les nouvelles coordonnées de textures déformées.

Le parallax mapping consiste simplement à calculer un décalage dans les coordonnées de textures en fonction de la direction du pixel par rapport à la camera et de la hauteur du pixel dans la height map. Ceci se résume à trois lignes supplémentaires dans le fragment program.Encore une fois, pour bien voir la différence, rien ne vaut des images comparatives avec/sans.

Image non disponible
Scène sans parallax mapping.
Image non disponible
Scène avec parallax mapping.

On peut voir que les pierres on été déformées pour donner une meilleur impression de volume, mais surtout, cette déformation se fait par rapport à la position de la camera, donc si la camera bouge, la déformation va se modifier pour prendre en compte la nouvelle direction de la camera.

II. Modifications apportée au programme.

Dans cette partie, nous allons voir les modifications apportées au programme. Nous verrons d'abord la nouvelle gestion des extensions mise en place pour ce tutoriel, puis nous verrons les modifications apportées au rendu des modèles. Je détaillerais ensuite les vertex et fragment program utilisé dans le tutoriel, puis, finalement, nous verrons comment mettre en place le rendu final de nos lumières.

II-1. Gestion des extensions.

Depuis le deuxième tutoriel, nous avons ajouté à chaque nouveau tutoriel de nouvelles extensions. Bien que nous n'ayons pas chargé toutes les fonctions liées à ces extensions, notre fonction de chargement des extensions devient de plus en plus grosse. Il nous faut aussi passer les fonctions aux classes qui vont les utiliser (la classe de modèles utilise les fonctions du multitextuing par exemple), ce qui implique l'utilisation de nombreuses variables externes. Pour faciliter la gestion des extensions, nous utiliserons désormais GLEW. GLEW est une bibliothèque qui permet de gérer facilement et de façon totalement transparente les extensions OpenGL. En utilisant GLEW, notre fonction de chargement des extensions devient simplement ça :

Fonction de chargement des extensions.
Sélectionnez

void initExtensions()
{
    GLenum err = glewInit();
    if (GLEW_OK != err)
    {
        // impossible d'initialiser glew
        fprintf(stderr, "Erreur a l'initialisation de GLEW : %s\n", glewGetErrorString(err));
        exit(0);
    }
    if (!glewIsSupported("GL_ARB_multitexture"))
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_ARB_multitexture\n");
        exit(0);
    }
    if (!glewIsSupported("GL_EXT_texture3D"))
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_ARB_multitexture\n");
        exit(0);
    }
    if (!glewIsSupported("GL_ARB_texture_env_dot3"))
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_ARB_texture_env_dot3\n");
        exit(0);
    }
    if (!glewIsSupported("GL_ARB_texture_cube_map"))
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_ARB_texture_cube_map\n");
        exit(0);
    }
    if (!glewIsSupported("GL_ARB_vertex_program"))
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_ARB_vertex_program\n");
        exit(0);
    }
    if (!glewIsSupported("GL_ARB_fragment_program"))
    {
        fprintf(stderr,"votre carte ne supporte pas l'extension GL_ARB_fragment_program\n");
        exit(0);
    }
}

Ici, on vérifie simplement que la carte supporte bien les extensions, mais on ne les chargent pas, c'est la fonction glewInit qui s'en est chargé pour nous. Nous pouvons maintenant utiliser toutes nos fonctions supportées par la carte comme si elles étaient des fonctions standard OpenGL.

Lors de l'inclusion du header glew.h, bien vérifier que l'inclusion est placée avant celle du fichier gl.h.

II-2. Modification du rendu des modèles.

Ici, pas de gros changements. Nous allons juste modifier la méthode d'ajout d'une lumière. Comme nous allons utiliser un vertex program, l'ensemble des calculs effectués par vertex se feront dans le vertex program, ce qui va réduire d'autant notre fonction d'ajout des lumières. La seule chose à bien comprendre ici, c'est que nous devons envoyer certaines informations au vertex/fragment program comme les trois vecteurs de l'espace local du vertex ou encore la position de la lumière et son rayon. Pour cela, j'utilise les coordonnées de textures, mais on aurai très bien pu passer par des tableaux d'attributs... A part cela, la méthode est assez simple à comprendre puisqu'il ne s'agit que d'envoyer la géométrie à la carte graphique, plus les informations nécessaires aux calculs. Le code de la méthode donne donc ça :

Méthode d'ajout des lumières à la scène.
Sélectionnez

void Model::addLightCubeMap(Light& light)
{
    Vecteur lightPos = light.getPosition();
    // on passe la couleur de la lumiere
    glColor3f(light.getColor().r,light.getColor().g,light.getColor().b);
    // on passe la position de la lumiere et son rayon dans la coordonnée de texture 1
    glMultiTexCoord4fARB(GL_TEXTURE1_ARB,lightPos.x,lightPos.y,lightPos.z,
                            1/light.getRadius());
    glBegin(GL_TRIANGLES);
    for(int i = 0;i < nbFaces; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            int vertexIndex = faces[i].vertexIndex[j];
            // on passe les tangentes, binormales et normales dans
            // les coordonnees de textures 2,3 et 4
            glMultiTexCoord3fARB(GL_TEXTURE2_ARB,tangents[vertexIndex].x,
                            tangents[vertexIndex].y,tangents[vertexIndex].z);
            glMultiTexCoord3fARB(GL_TEXTURE3_ARB,binormals[vertexIndex].x,
                            binormals[vertexIndex].y,binormals[vertexIndex].z);
            glMultiTexCoord3fARB(GL_TEXTURE4_ARB,normals[vertexIndex].x,
                            normals[vertexIndex].y,normals[vertexIndex].z);
            // on passe les coordonnée de la scene dans la coordonnée de texture 0
            TexCoord &tc = texCoords[faces[i].texCoordIndex[j]];
            glMultiTexCoord2fARB(GL_TEXTURE0_ARB,tc.u,tc.v);
            Vecteur &v = vertex[vertexIndex];
            glVertex3f(v.x,v.y,v.z);
        }
    }
    glEnd();
}

Ici, je n'envoie pas le rayon de la lumière, mais l'inverse de son rayon. Cela permet de réduire les calculs car pour calculer l'atténuation j'ai besoin de faire une division par le rayon, or, il est plus rapide de multiplier par l'inverse du rayon que de faire cette division directement dans le vertex/fragment program.

Comme d'habitude, je suppose ici qu'OpenGL est bien configuré, c'est-à-dire que les vertex/fragment programs sont bien activé, le blending est en mode additif, et les unités de textures sont bien positionnées.

II-3. Les vertex et fragment programs.

Dans cette partie, je vais présenter le vertex program et le fragment program utilisés dans le tutoriel pour calculer l'éclairage. J'ai préféré utiliser des programs assembleurs ARB au lieu de vertex/fragment shaders ARB car ceux-ci sont supporté par moins de cartes, mais aussi car les programs sont en assembleur, ce qui est plus compliqué à mettre en oeuvre, mais permet de mieux se rendre compte de l'étendu des calculs effectués par la carte graphique.

Dans un premier temps, il nous faut envoyer nos programs à OpenGL pour les compiler et les faire valider par la carte graphique. C'est le travail effectué par la méthode initShaders suivante.

Fonction d'envoi des shaders à la carte graphique
Sélectionnez

void initShaders()
{
    glGenProgramsARB(1, &fragmentProgramId);
    glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, fragmentProgramId);
    glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB,
                strlen((char*) fragmentProgram), fragmentProgram);

    if(GL_INVALID_OPERATION == glGetError() )
    {
        //une erreur est detectee
        //recupere les informations sur l'erreur survenue
        const unsigned char* errorString;
        GLint errorPos = 0;
        errorString=glGetString(GL_PROGRAM_ERROR_STRING_ARB);
        glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB,&errorPos);

        fprintf(stderr,"une erreur est survenue a la position : %d\n%s\n",
                 errorPos, errorString);
        // on affiche le program jusqu'a l'erreur
        for (int i = 0; i < errorPos; i++)
        {
            printf("%c", fragmentProgram[i]);
        }
        printf("\n");
    }

    // chargement du vertex program
    glGenProgramsARB(1, &vertexProgramId);
    glBindProgramARB(GL_VERTEX_PROGRAM_ARB, vertexProgramId);
    glProgramStringARB(GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB,
                strlen((char*) vertexProgram), vertexProgram);

    if(GL_INVALID_OPERATION == glGetError())
    {
        //une erreur est detectee
        //recupere les informations sur l'erreur survenue
        const unsigned char* errorString;
        GLint errorPos = 0;
        errorString=glGetString(GL_PROGRAM_ERROR_STRING_ARB);
        glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB,&errorPos);

        fprintf(stderr,"une erreur est survenue a la position : %d\n%s\n",
                 errorPos, errorString);
        // on affiche le program jusqu'a l'erreur
        for (int i = 0; i < errorPos; i++)
        {
            printf("%c", vertexProgram[i]);
        }
        printf("\n");
    }
}

Maintenant que nous avons chargé nos programs, nous allons voir ce qu'ils contiennent. Les explications des programs sont contenues directement en tant que commentaires pour éviter d'avoir à les découper en petites parties. D'abord, voilà le vertex program utilisé. Il effectue tous les calculs par vertex que nous faisions avant sur le CPU.

Vertex program utilisé dans le tutoriel
Sélectionnez

# vertex program utilisé pour gérer l'éclairage
# il effectue la projection du vecteur vers la lumière et
# du vecteur vers la camera qui sont utilisé dans le fragment
# program par la suite.
# @param vertex.texcoord[0] : coordonnées de texture du polygone
# @param vertex.texcoord[1] : position de la lumiere + rayon dans w
# @param vertex.texcoord[2] : tangente
# @param vertex.texcoord[3] : binormal
# @param vertex.texcoord[4] : normale
# @param vertex.texcoord[5] : position de la camera
# @param vertex.color : couleur de la lumiere
#
# @out result.texcoord[0] : coordonnée de texture du polygone
# @out result.texcoord[1] : vecteur du vertex vers la camera
# @out result.texcoord[2] : vecteur du vertex vers la lumiere
!!ARBvp1.0 OPTION ARB_position_invariant;

# mvit repesente l'inverse transposé de la matrice
# de modelview
PARAM    mvit[4] = {state.matrix.modelview.invtrans};
# modelview est la matrice de modelview
PARAM    modelView[4]    = { state.matrix.modelview };
# camPos est la position de la camera
# note : pour récupérer la position de la camera,
# il suffit de prendre la quatrième ligne de l'inverse
# transposé de la matrice de modelview
PARAM    camPos = state.matrix.modelview.invtrans.row[3];
# les texcoord 2 contienne la tangente
ATTRIB    tangent    = vertex.texcoord[2];
# les texcoord 3 contiennent la binormale
ATTRIB    binormal    = vertex.texcoord[3];
# les texcoord 4 contiennent la normale
ATTRIB    normal     = vertex.texcoord[4];
# les texcoord 1 contiennent les informations sur
# la lumière, c-a-d : sa position et l'inverse de
# son rayon dans la composante w
ATTRIB    lightInfo    = vertex.texcoord[1];
TEMP    vertexToLight;
TEMP    vertexToCam;
TEMP    temp;

# on projete le vecteur vers la lumière dans l'espace local
# du vertex
SUB    temp, lightInfo, vertex.position ;
DP3    vertexToLight.x, temp, tangent;
DP3    vertexToLight.y, temp, binormal;
DP3    vertexToLight.z, temp, normal;
MOV    vertexToLight.w, lightInfo.w;

# on projete le vecteur vers la camera dans l'espace local
# du vertex
SUB    temp, camPos, vertex.position;
DP3    vertexToCam.x, temp, tangent;
DP3    vertexToCam.y, temp, binormal;
DP3    vertexToCam.z, temp, normal;

# ici, je ne normalise pas les vecteurs calculé, ce sera
# fait dans le fragment program pour améliorer la qualité
# de l'affichage (plus de problèmes de normalisation dû à
# l'interpolation)

# on copie les informations calculées sur les sortie du
# vertex program.
MOV    result.texcoord[0], vertex.texcoord[0];
MOV    result.texcoord[1], vertexToCam;
MOV    result.texcoord[2], vertexToLight;
MOV    result.color, vertex.color;
END

Et maintenant, voilà le fragment program qui calcul le résultat final de notre ajout de lumière.

Fragment program de calcul de la lumière.
Sélectionnez

# fragment program gérant l'affichage de la lumière
# ce fragment program calcul l'éclairage avec reflets
# spéculaires et avec du parallax mapping.
# @param texture[0] : normal map
# @param texture[1] : diffuse map
# @param texture[2] : height map
# @param texture[3] : gloss map
# @param texture[4] : attenuation map
# @param fragment.texcoord[0] : coordonnées de texture du polygone
# @param fragment.texcoord[1] : vecteur du fragment vers la camera
# @param fragment.texcoord[2] : vecteur de fragment vers la lumière
# @param fragment.color : la couleur de la lumiere
!!ARBfp1.0 OPTION ARB_precision_hint_fastest;
PARAM    power = {8,0,0,0};
# la texture de normal map
TEMP    normal;
# la texture d'attenuation
TEMP    attenuation;
# la texture de diffuse
TEMP    diffuse;
# la texture de gloss
TEMP    gloss;
# la texture de height map
TEMP    height;
# les coordonnées de textures modifiées par
# le parallax mapping
TEMP    newTexcoord;
# le vecteur allant du fragment vers la lumière
TEMP    toLight;
# la couleur final du fragment
TEMP    final;
# le résultat du produit scalaire entre la normale
# et le vecteur vers la lumière
TEMP    NdotL;
# vecteur temporaire
TEMP    temp;
# le vecteur de demi angle pour le spéculaire
TEMP    halfAngle;
# le vecteur du fragment vers la camera
TEMP    toCamera;
# la composante spéculaire de la lumière
TEMP    specular;

# on normalise le vecteur fragment->lumiere
# note : en l'absence d'extension permettant d'effectuer
# la normalisation d'un vecteur en une instruction, il faut
# utiliser la séquence suivante pour normaliser un vecteur
# (ici on normalise fragment.texcoord[2])
DP3    temp, fragment.texcoord[2], fragment.texcoord[2];
RSQ    temp, temp.x;
MUL    toLight, fragment.texcoord[2], temp;

# on normalise le vecteur fragment->camera
DP3    temp, fragment.texcoord[1], fragment.texcoord[1];
RSQ    temp, temp.x;
MUL    toCamera, fragment.texcoord[1], temp;

# on calcul les nouvelles coordonnées de textures pour le parallax mapping
TEX    height, fragment.texcoord[0], texture[2], 2D;
MAD    height, height, 0.04, -0.02;
MAD    newTexcoord, height, toCamera, fragment.texcoord[0];

# on calcule l'attenuation
# on a texcoord[2] qui contient le vecteur vertex->lumiere
# on divise le vecteur par le rayon de la lumiere (w contient l'inverse du rayon)
# et on ajoute 0.5 pour partir du centre de la texture
# cf : calcul d'attenuation dans les tutoriaux sur le light mapping.
MAD    attenuation, fragment.texcoord[2], fragment.texcoord[2].w, {0.5,0.5,0.5,0};
TEX    attenuation, attenuation, texture[4], 3D;

# on charge la normal map
TEX    normal, newTexcoord, texture[0], 2D;

# on remap la normal dans [0..1] au lieu de [0.5..1]
MAD    normal, normal, 2, -1;

# on renormalise la normal pour maximiser la qualité
# d'affichage (plus d'interpolation, tout les vecteurs
# sont bien normalisé)
DP3    temp, normal, normal;
RSQ    temp, temp.x;
MUL    normal, normal, temp;

# on effectue le produit scalaire entre la
# normale à la surface et le vecteur transformé
# vers la lumière.
DP3    NdotL, normal, toLight;
MAX    NdotL, NdotL, 0;
MUL_SAT    final, NdotL, fragment.color;

# calcul de la composante spéculaire
# on calcul le vecteur de demi angle normalisé
ADD    halfAngle, toCamera, toLight;
DP3    temp, halfAngle, halfAngle;
RSQ    temp, temp.x;
MUL    halfAngle, halfAngle, temp;

# on effectue le calcul du produit scalaire entre la
# normale à la surface et le vecteur de demi angle.
# on s'assure que la composante speculaire n'est pas
# inferieure à 0, ce qui entrainerai des problèmes lors
# de l'addition avec le rest du calcul. En effet, la
# composante spéculaire peut ajouter de la luminosité à un
# fragment mais ne doit pas en retirer.
DP3    specular, normal, halfAngle;
MAX    specular, specular, 0;

# on met le resultat a la puissante en parametre
POW    specular, specular.x, power.x;

# on multiplie par la couleur de la lumiere
MUL    specular, specular, fragment.color;

# on recupere les textures restantes
TEX    diffuse, newTexcoord, texture[1], 2D;
TEX    gloss, newTexcoord, texture[3], 2D;

# on termine l'equation d'eclairage
# on multiplie la composante speculaire par le gloss
MUL    specular, specular, gloss;

# on multiplie le bump par la texture de diffuse et
# on ajoute la composante spéculaire
MAD    final, final, diffuse, specular;

# on attenue le tout
MUL    final, final, attenuation;

# et on envoi le resultat du calcul en temps que couleur
# finale.
MOV    result.color, final;
END

Si vous n'êtes pas familier avec les vertex/fragment programs ARB, ceux-ci peuvent vous sembler incompréhensibles, et pourtant ils ne font que des calculs assez simples à comprendre. Si vraiment vous ne comprenez pas, vous pouvez toujours aller regarder une doc sur les instructions des programs ARB.

Il faut bien se rendre compte que l'ensemble des calculs du fragment program sont effectué pour chaque pixel, même si le pixel est finalement noir. On obtient donc un fragment program relativement coûteux en terme de fill rate (1) . On peut réduire ce coût en ne renormalisant pas la normal map et en effectuant les calculs de normalisation des vecteurs vers la lumière et la camera au niveau du vertex program, mais c'est au dépend de la qualité, et on retombe sur les problèmes d'interpolation si on utilise pas de cube map de normalisation comme c'est le cas ici.

II-4. Le rendu final.

Maintenant que nous avons toutes les informations nécessaires, nous pouvons voir comment effectuer le rendu final de la scène. Le code nécessaire est assez semblable à celui du tutoriel précédent, mis a part qu'on a plus à activer l'extension env_dot3 pour l'unité de texture de la normal map, qu'on doit activer par contre les vertex/fragment programs et qu'on n'a pas à effectuer de deuxième passe pour avoir des lumières colorées. On ajoute aussi l'utilisation de la height map et de la gloss map, ce qui fait que nous utilisons maintenant 5 unités de textures pour notre rendu (et nous n'utilisons plus de cube map de normalisation). Le code de rendu de la scène donne donc :

Code de rendu de notre scène.
Sélectionnez

// on configure la premiere unite de texture :
// elle contient la texture de la scene
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, textureId);
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);

// on effectue le rendu de la lumiere ambiante
model->initLighting(ambiant);

// on doit reconfigurer les unites de textures
// la première unite de texture contient la normal map
glActiveTextureARB(GL_TEXTURE0_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, normalMapId);

// la deuxieme contient la texture de diffuse
glActiveTextureARB(GL_TEXTURE1_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,textureId );

// la troisieme contient la height map
glActiveTextureARB(GL_TEXTURE2_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,heightMapId );

// la quatrieme contient la gloss map
glActiveTextureARB(GL_TEXTURE3_ARB);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D,glossMapId );

// la cinquieme contient la texture d'attenuation
glActiveTextureARB(GL_TEXTURE4_ARB);
glEnable(GL_TEXTURE_3D_EXT);
glBindTexture(GL_TEXTURE_3D_EXT,lightmap );

// on doit activer le blending additif pour pouvoir utiliser
// ce mode d'eclairage
glEnable(GL_BLEND);
glBlendFunc(GL_ONE,GL_ONE);
glDepthMask(GL_FALSE);

// on active les vertex/fragment program
glEnable(GL_VERTEX_PROGRAM_ARB);
glBindProgramARB(GL_VERTEX_PROGRAM_ARB,vertexProgramId);
glEnable(GL_FRAGMENT_PROGRAM_ARB);
glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB,fragmentProgramId);

// on ajoute nos lumieres
model->addLightCubeMap(light1);
model->addLightCubeMap(light2);

// on desactive les vertex/fragment program
glDisable(GL_VERTEX_PROGRAM_ARB);
glDisable(GL_FRAGMENT_PROGRAM_ARB);

// on desactive la cinquieme unite de texture
glActiveTextureARB(GL_TEXTURE4_ARB);
glDisable(GL_TEXTURE_3D_EXT);
// on desactive la quatrieme unite de texture
glActiveTextureARB(GL_TEXTURE3_ARB);
glDisable(GL_TEXTURE_2D);
// on desactive la troisieme unite de texture
glActiveTextureARB(GL_TEXTURE2_ARB);
glDisable(GL_TEXTURE_2D);
// on desactive la seconde unite de texture
glActiveTextureARB(GL_TEXTURE1_ARB);
glDisable(GL_TEXTURE_2D);
// on desactive la premiere unite de texture
glActiveTextureARB(GL_TEXTURE0_ARB);
glDisable(GL_TEXTURE_CUBE_MAP_ARB);

glDepthMask(GL_TRUE);
glDisable(GL_BLEND);

Et voila le résultat final en image.

Image non disponible
Le rendu final de la scène.

III. Réflexions sur la gestion de la lumière.

Nous avons vu au cours de cette série de tutoriaux comment gérer la lumière de plusieurs façons différentes. Cela va de la plus simple, avec l'éclairage par vertex, à des plus compliquée comme nous venons de le voir. Comme je le disais dans la conclusion du tutoriel précédent, l'utilisation d'une méthode d'éclairage ou d'une autre influe non seulement sur les performances du moteur 3D, mais aussi sur la quantité de ressources à fournir pour les artistes. En effet, avec un système complexe, le créateur de textures aura à crée :

  • Une texture de diffuse standard. Elle correspond à la texture généralement crée pour un système d'éclairage simple. Ce n'est donc pas un surcoût ici.
  • Une height map utilisée pour gérer l'effet de parallax mapping.
  • Une normal map. Bien que généralement crée à partir de la height map, on peut parfois souhaiter avoir une normal map différente. C'est sans doute la texture la plus importante d'un material. Une normal map de mauvaise qualité, et l'ensemble du système de gestion de la lumière en subit les conséquences.
  • Une gloss map qui permet de nuancer l'effet de l'éclairage spéculaire. C'est une des textures les plus difficiles à régler. Une gloss map qui atténue trop l'éclairage spéculaire, l'effet perd de son intérêt, au contraire, si la gloss map ne l'atténue pas assez, on se retrouve avec des surface qui ressembles à du plastique ou du métal trop brillant.
  • Une texture émissive. Bien que je ne l'aie pas traitée dans ce tutoriel, on peut souhaiter avoir certaines parties de notre texture toujours éclairée. Par exemple un bouton lumineux ou un écran d'ordinateur émettent leur propre lumière. Pour cela on utilise une texture émissive qui est affichée durant la première passe de l'algorithme de rendu de la lumière (le rendu de la lumière ambiante).

Nous avons donc ici de nombreuses textures à créer pour nos artistes, ce qui peut considérablement alourdir le processus de création d'un jeu vidéo pour une équipe d'amateurs.

Un autre point important que je n'est pas traité ici est l'optimisation du rendu des lumières. Nos lumières étant des lumières sphériques ponctuelles, on peut effectuer de nombreuses optimisations avant de les afficher. En dehors des algorithmes de partitionnement (BSP, octree, portals), la première optimisation qui vient à l'esprit est de vérifier si la face est bien orientée vers la lumière avant de la rendre. En effet, il est inutile de dessiner une face si on sait que l'ensemble de ses vertex tournent le dos à la lumière. Si ceci peut être coûteux pour l'éclairage par vertex (le coût de vérification par face sera supérieur au gain de vitesse), cette optimisation peut être très intéressant pour les systèmes plus coûteux en terme de fill rate, c'est-à-dire, quand le dessin d'un pixel est lent (ce qui est notre cas dans ce tutoriel). On peut aussi exclure de l'affichage toutes les faces qui sont trop éloignées de la lumière pour être éclairées (ceci se fait simplement à l'aide d'une équation de plan par polygone).

Une autre optimisation simple à mettre en oeuvre consiste à utiliser un rectangle de clipping autour de la lumière. Cette optimisation n'est utilisable que pour les rendus de lumières en multi passe, elle n'est donc pas applicable à l'éclairage par vertex. Elle consiste tout simplement à utiliser la possibilité des cartes graphiques à utiliser un rectangle de clipping pour réduire la zone de dessin autorisée. Si, en calculant les extremum de notre lumière, on en déduit un rectangle de clipping qui englobe entièrement notre lumière, on peut ainsi réduire l'affichage des grand polygones touchés par la lumières, et ainsi sauvegarder du fill rate encore une fois.
Un avantage de cette optimisation est qu'elle est aussi très utile pour optimiser le rendu des ombres utilisant l'algorithme des shadow volumes.

Comme je l'ai dit dans le premier tutoriel, je n'ai traité que des lumières sphériques ponctuelles directes. On peut imaginer des systèmes d'éclairage plus complexes gérant les spots de lumières via des projections de textures, et des lumières directionnelles infinies. De même, on peut espérer voir arriver dans l'industrie d'ici quelques temps l'utilisation de l'illumination globale qui permet de gérer les sources de lumières indirectes. Ceci est encore à l'heure actuelle un sujet de recherche important, mais les premiers résultats sont déjà impressionnants.

Un autre point important qui n'est pas traité dans cette série est la gestion de l'occlusion de la lumière, via des algorithmes de génération d'ombres portées. En effet, dans ces tutoriaux, la lumière traverse les objets pour aller éclairer les autres objets. Ceci est embêtant pour le réalisme de notre scène. Il peut donc être intéressant d'avoir des ombres portées sur les autres objets, et pour cela utiliser des techniques comme les shadow volumes ou les shadow buffers (aussi appelée shadow maps).

IV. Conclusions.

Ca y est, c'est fini. Si nous avons vu au cours de cette série de tutoriaux comment gérer la lumière de plusieurs manières, il ne faut pas oublier que le principal argument pour le faire dans un moteur 3D amateur est bien de pouvoir dépasser la limite du nombre maximum de lumières hardware supportées par les cartes graphiques. Il est donc conseillé de choisir la méthode la plus adaptée au moteurs que vous souhaitez réaliser. Il n'est pas forcement utile de mettre en place un système d'éclairage par pixel pour un moteur simple ou pour un jeu de stratégie par exemple, alors qu'une bonne gestion de l'éclairage pourra être très importante dans un FPS. Le principal est que maintenant vous avez le choix de la technique à utiliser dans votre moteur.


OpenGL democoding in MASM

The MASM Forum

Simple Machines Forum
Welcome, Guest. Please login or register.
Did you miss your activation email?

Login with username, password and session length

News:

MASM32 Downloads

  • Home
  • Help
  • Search
  • Login
  • Register

  • The MASM Forum »
  • General »
  • The Campus »
  • OpenGL democoding in MASM
« previous next »
  • Print
Pages: [1]

Author Topic: OpenGL democoding in MASM (Read 3876 times)

kroz

  • Guest
OpenGL democoding in MASM
« on: August 21, 2012, 10:00:05 AM »
I'm considering learning OGL in MASM. Before I start, i'd like to know a few things :
+ How hard is it?
+ How long does it take to learn?
+ Will I be able to do anything useful by the time i've learnt it?

and where are NeHe's MASM ports? The link is broken. Does anyone have them ?

thanks :)
Logged

hfheatherfox07

  • Member
  • Posts: 464
Re: OpenGL democoding in MASM
« Reply #1 on: August 21, 2012, 11:13:55 AM »
I have Posted this stuff on the Old MASM Forum ::)

http://nehe.gamedev5.net/data/lessons/masm/lesson01.zip
http://nehe.gamedev5.net/data/lessons/masm/lesson02.zip
http://nehe.gamedev5.net/data/lessons/masm/lesson03.zip
http://nehe.gamedev5.net/data/lessons/masm/lesson04.zip
http://nehe.gamedev5.net/data/lessons/masm/lesson05.zip
http://nehe.gamedev5.net/data/lessons/masm/lesson06.zip
http://nehe.gamedev5.net/data/lessons/masm/lesson07.zip
http://nehe.gamedev5.net/data/lessons/masm/lesson08.zip
http://nehe.gamedev5.net/data/lessons/masm/lesson09.zip
http://nehe.gamedev5.net/data/lessons/masm/lesson10.zip
http://nehe.gamedev5.net/data/lessons/masm/lesson11.zip
http://nehe.gamedev5.net/data/lessons/masm/lesson12.zip
http://nehe.gamedev5.net/data/lessons/masm/lesson13.zip
http://nehe.gamedev5.net/data/lessons/masm/lesson14.zip
http://nehe.gamedev5.net/data/lessons/masm/lesson15.zip
http://nehe.gamedev5.net/data/lessons/masm/lesson16.zip
http://nehe.gamedev5.net/data/lessons/masm/lesson17.zip
http://nehe.gamedev5.net/data/lessons/masm/lesson18.zip
http://nehe.gamedev5.net/data/lessons/masm/lesson19.zip
http://nehe.gamedev5.net/data/lessons/masm/lesson20.zip
http://nehe.gamedev5.net/data/lessons/masm/lesson21.zip

:biggrin:
Logged
Your code and your skills will be assimilated. Your programming language is irrelevant.
We are the ASM Borg and you will become part of us. Compile and be assembled.

ufo

  • Guest
Re: OpenGL democoding in MASM
« Reply #2 on: August 21, 2012, 06:34:46 PM »
I've converted 2 of them to masm in the past.
They uploaded number 36, but forgot number 24.
http://nehe.gamedev.net/data/lessons/masm/lesson36.zip
I will try to find number 24 again later today and attach it here.

EDIT/
It was 25, not 24.. attached.
* lesson25.zip (51.45 kB - downloaded 179 times.)
« Last Edit: August 22, 2012, 06:05:27 AM by ufo »
Logged

kroz

  • Guest
Re: OpenGL democoding in MASM
« Reply #3 on: August 22, 2012, 03:18:38 AM »
thanks alot :)
Logged

ufo

  • Guest
Re: OpenGL democoding in MASM
« Reply #4 on: August 22, 2012, 06:06:20 AM »
My bad.. I meant lesson 25.
Attached above.
Logged

kroz

  • Guest
Re: OpenGL democoding in MASM
« Reply #5 on: August 24, 2012, 08:25:51 AM »
oh, and this may be a very long shot, but would it be too hard to do somethingl like this in MASM ? www.pouet.net/prod.php?which=16327
Logged

dedndave

  • Member
  • Posts: 8695
  • Still using Abacus 2.0
Re: OpenGL democoding in MASM
« Reply #6 on: August 24, 2012, 11:39:56 AM »
i am guessing that program was written in assembler
20 kb in size is why :P
Logged

Tedd

  • Member
  • Posts: 353
  • Procrastinor Extraordinaire
Re: OpenGL democoding in MASM
« Reply #7 on: August 24, 2012, 11:49:34 PM »
The source is C++

But to answer the question, yes, it's entirely possible - if a little more work. There are a few competitions that focus just on asm demos. As for being "too hard" - it's pretty hard whatever language you choose, asm just adds to it :P

Logged
Potato2

hfheatherfox07

  • Member
  • Posts: 464
Re: OpenGL democoding in MASM
« Reply #8 on: August 25, 2012, 04:59:09 AM »
Quote from: dedndave on August 24, 2012, 11:39:56 AM
i am guessing that program was written in assembler
20 kb in size is why :P

NOP
The program is compressed with kkrunchy v1.0.....
@ kroz ufo did a dancing square that is similar...just similar to this in masm ...It is Possible :P

Any ways here is that glRez.exe unpaked and I got the resources out for you ....
.bmp's and the .xm

I am sure if ufo still has that source that would have been posted ...... long time ago
* glrez_unpacked.zip (34.43 kB - downloaded 169 times.)
Logged
Your code and your skills will be assimilated. Your programming language is irrelevant.
We are the ASM Borg and you will become part of us. Compile and be assembled.

ufo

  • Guest
Re: OpenGL democoding in MASM
« Reply #9 on: August 25, 2012, 07:57:15 AM »
https://github.com/chiptune/glrez
In my view it's all C, even when he's using extensions like .cpp.
On tuts4you.com I've got similar sources like that, but in masm -
although I wouldn't be able to compete with serious stuff like the one of rez.
PS: I will certainly not attach stuff like that over here..
Logged

Farabi

  • Member
  • Posts: 970
  • Neuroscience Fans
Re: OpenGL democoding in MASM
« Reply #10 on: August 25, 2012, 11:05:18 PM »
Quote from: kroz on August 24, 2012, 08:25:51 AM
oh, and this may be a very long shot, but would it be too hard to do somethingl like this in MASM ? www.pouet.net/prod.php?which=16327

Hard would be depend on your knowledge, I bet you'll need at least understanding on sin and cos function, I think it only that, the rest, OpenGL can handle it.
Logged
http://farabidatacenter.url.ph/MySoftware/
My 3D Game Engine Demo.

Contact me at Whatsapp: 6283818314165
  • Print
Pages: [1]
« previous next »
  • The MASM Forum »
  • General »
  • The Campus »
  • OpenGL democoding in MASM

  • SMF 2.0.2 | SMF © 2011, Simple Machines
  • XHTML
  • WAP2


                       ; ------------------------------------------
           ; Title: OpenGL test                                                   Vu dans  Masm32 v8  ...  Example8\OpenGL\OpenGL.asm
           ;
           ; Author: Franck Charlet.
           ;
           ; Notes: Just a little example on how to use OpenGL.
           ;	 Needs OpenGl32.dll and Glu32.dll (should be no problem).
           ;	 Object have 2112 faces / 3 (weird) moving light sources
           ;	 and it's f***ing slow on my cyrix 133.
           ;
           ; ------------------------------------------

           ; --------------- Assembler directives
                       .386
                             .model	flat,stdcall
                       option	casemap:none

           ; --------------- External includes
                       include	\masm32\include\windows.inc
                       include \masm32\include\user32.inc
                       include \masm32\include\kernel32.inc
                       include \masm32\include\gdi32.inc
                       include \masm32\include\opengl32.inc
                       include \masm32\include\glu32.inc

                       includelib \masm32\lib\user32.lib
                       includelib \masm32\lib\kernel32.lib
                       includelib \masm32\lib\gdi32.lib
                       includelib \masm32\lib\gdi32.lib
                       includelib \masm32\lib\opengl32.lib
                       includelib \masm32\lib\glu32.lib

           ; --------------- Macros Section
           szText			MACRO	Name,Text:VARARG
                       LOCAL	lbl
                       jmp	lbl
           Name			db	Text,0
           lbl:
                       ENDM

           m2m			MACRO	M1, M2
                       push	M2
                       pop	M1
                       ENDM

           return 			MACRO	arg
                       mov	eax,arg
                       ret
                       ENDM

           ; --------------- These constants are not defined in windows.inc
           PFD_MAIN_PLANE		equ	0
           PFD_TYPE_COLORINDEX	equ	1
           PFD_TYPE_RGBA		equ	0
           PFD_DOUBLEBUFFER	equ	1
           PFD_DRAW_TO_WINDOW	equ	4
           PFD_SUPPORT_OPENGL	equ	020h

           ; --------------- Data Section
                       .data
           szDisplayName		db	"TestOpenGL - Written by Franck 'hitchhikr' Charlet.",0
                       even
           PixFrm			PIXELFORMATDESCRIPTOR <>

           CommandLine		dd	0
           hWnd			dd	0
           MainHDC			dd	0
           OpenDC			dd	0
           hInstance		dd	0

           ; Some values
           Value0Flt		dd	0.0
           Value1Flt		dd	1.0
           Value1Dbl		dq	1.0
           Value45Dbl		dq	45.0
           Value3Dbl		dq	3.0
           Value7Dbl		dq	7.0

           ; Light position
           LightSourcePosition	dd	-2.0,-2.0,-4.0,0.0
           LightSource2Position	dd	2.0,2.0,4.0,0.0
           LightSource3Position	dd	-2.0,2.0,4.0,0.0
           LightAmbient		dd	0.2,0.0,0.0,1.0
           Light2Ambient		dd	0.0,0.2,0.0,1.0
           Light3Ambient		dd	0.0,0.0,0.2,1.0
           LightDiffuse		dd	1.0,1.0,1.0,1.0
           LightSpecular		dd	1.0,1.0,1.0,1.0
           SpotCut			dd	-1.0
           SpotExp			dd	0.0
           SpotDir			dd	-1.0,-1.0,-1.0
           LightConstAtt		dd	1.0
           LightLinAtt		dd	1.0
           LightQuadAtt		dd	1.0

           ; --- Sphere 1
           ; Angles
           Sphere1AnglesFlt	dd	0.0,0.0,0.0
           ; Rotations speed
           Sphere1AnglesSpeedFlt	dd	-1.0,-1.2,-1.4
           ; Objects datas
           Sphere1Color		dd	0.6,0.4,0.2,0.0
           Sphere1Radius		dq	1.0
           sphere1Parts		dd	24
           Sphere1Position		dd	0.0,0.0,-5.0
           GlSphere1		dd	0

           ; --- Sphere 2
           ; Angles
           Sphere2AnglesFlt	dd	0.0,0.0,0.0
           ; Rotations speed
           Sphere2AnglesSpeedFlt	dd	-1.0,0.8,-0.4
           ; Objects datas
           Sphere2Color		dd	0.4,0.6,0.2,0.0
           Sphere2Radius		dq	0.3
           sphere2Parts		dd	16
           Sphere2Position		dd	0.0,0.0,-1.5
           GlSphere2		dd	0

           ; --- Sphere 3
           ; Angles
           Sphere3AnglesFlt	dd	0.0,0.0,0.0
           ; Rotations speed
           Sphere3AnglesSpeedFlt	dd	-1.0,-0.8,1.4
           ; Objects datas
           Sphere3Color		dd	0.2,0.4,0.6,0.0
           Sphere3Radius		dq	0.3
           sphere3Parts		dd	16
           Sphere3Position		dd	0.0,0.0,1.5
           GlSphere3		dd	0

           ; --- Sphere 4
           ; Angles
           Sphere4AnglesFlt	dd	0.0,0.0,0.0
           ; Rotations speed
           Sphere4AnglesSpeedFlt	dd	-1.0,1.4,-0.8
           ; Objects datas
           Sphere4Color		dd	0.4,0.2,0.6,0.0
           Sphere4Radius		dq	0.3
           sphere4Parts		dd	16
           Sphere4Position		dd	0.0,1.5,0.0
           GlSphere4		dd	0

           ; --- Sphere 5
           ; Angles
           Sphere5AnglesFlt	dd	0.0,0.0,0.0
           ; Rotations speed
           Sphere5AnglesSpeedFlt	dd	1.0,1.8,0.8
           ; Objects datas
           Sphere5Color		dd	0.6,0.2,0.4,0.0
           Sphere5Radius		dq	0.3
           sphere5Parts		dd	16
           Sphere5Position		dd	1.5,0.0,0.0
           GlSphere5		dd	0

           ; --- Sphere 6
           ; Angles
           Sphere6AnglesFlt	dd	0.0,0.0,0.0
           ; Rotations speed
           Sphere6AnglesSpeedFlt	dd	1.0,-1.8,2.0
           ; Objects datas
           Sphere6Color		dd	0.2,0.6,0.4,0.0
           Sphere6Radius		dq	0.3
           sphere6Parts		dd	16
           Sphere6Position		dd	-1.5,0.0,0.0
           GlSphere6		dd	0

           ; --- Sphere 7
           ; Angles
           Sphere7AnglesFlt	dd	0.0,0.0,0.0
           ; Rotations speed
           Sphere7AnglesSpeedFlt	dd	-2.1,-1.8,2.0
           ; Objects datas
           Sphere7Color		dd	0.6,0.6,0.6,0.0
           Sphere7Radius		dq	0.3
           sphere7Parts		dd	16
           Sphere7Position		dd	0.0,-1.5,0.0
           GlSphere7		dd	0

           ; --------------- Procedures Declarations
                       .code

           MainInit		PROTO	:DWORD,:DWORD,:DWORD,:DWORD
           MainLoop		PROTO	:DWORD,:DWORD,:DWORD,:DWORD
           TopXY			PROTO	:DWORD,:DWORD
           DrawScene		PROTO
           GlInit			PROTO	:DWORD,:DWORD
           ResizeObject		PROTO	:DWORD,:DWORD
           CreateSphere		PROTO	:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
           SetLightSource		PROTO	:DWORD,:DWORD,:DWORD
           RotateObject		PROTO	:DWORD,:DWORD,:DWORD,:DWORD
           DeleteSpheres		PROTO

           ; --------------- Procedures Section
           CenterForm		PROC	wDim:DWORD, sDim:DWORD
                       shr	sDim,1
                       shr	wDim,1
                       mov	eax,wDim
                       sub	sDim,eax
                       return	sDim
           CenterForm		ENDP

           DoEvents		PROC
                       LOCAL	msg:MSG
           StartLoop:		; Check for waiting messages
                       invoke	PeekMessage,ADDR msg,0,0,0,PM_NOREMOVE
                       or	eax,eax
                       jz	NoMsg
                       invoke	GetMessage,ADDR msg,NULL,0,0
                       or	eax,eax
                       jz	ExitLoop
                       invoke	TranslateMessage,ADDR msg
                       invoke	DispatchMessage,ADDR msg
                       jmp	StartLoop
           NoMsg:			; No pending messages: draw the scene
                       invoke	DrawScene
                       jmp	StartLoop
           ExitLoop:		mov	eax,msg.wParam
                       ret
           DoEvents		ENDP

           ; --------------- Program start
           start:			invoke	GetModuleHandle,NULL
                       mov	hInstance, eax
                       invoke	GetCommandLine
                       mov	CommandLine,eax
                       invoke	MainInit,hInstance,NULL,CommandLine,SW_SHOWDEFAULT
                       invoke	ExitProcess,eax

           ; --------------- Program main inits
           MainInit 		PROC	hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORD
                       LOCAL	wc:WNDCLASSEX
                       LOCAL	Wwd:DWORD
                       LOCAL	Wht:DWORD
                       LOCAL	Wtx:DWORD
                       LOCAL	Wty:DWORD
                           szText	szClassName,"Win32SDI_Class"
                       mov	wc.cbSize,sizeof WNDCLASSEX
                       mov	wc.style,0
                       mov	wc.lpfnWndProc,offset MainLoop
                       mov	wc.cbClsExtra,NULL
                       mov	wc.cbWndExtra,NULL
                       m2m	wc.hInstance,hInst
                       mov	wc.hbrBackground,COLOR_WINDOWTEXT+1
                       mov	wc.lpszMenuName,NULL
                       mov	wc.lpszClassName,offset szClassName
                       invoke	LoadIcon,hInst,2
                       mov	wc.hIcon,eax
                       invoke	LoadCursor,NULL,IDC_ARROW
                       mov	wc.hCursor,eax
                       mov	wc.hIconSm,0
                       invoke	RegisterClassEx, ADDR wc
                       mov	Wwd,400
                       mov	Wht,420
                       invoke	GetSystemMetrics,SM_CXSCREEN
                       invoke	CenterForm,Wwd,eax
                       mov	Wtx,eax
                       invoke	GetSystemMetrics,SM_CYSCREEN
                       invoke	CenterForm,Wht,eax
                       mov	Wty,eax
                       invoke	CreateWindowEx,0,ADDR szClassName,
                                   ADDR szDisplayName,
                                   WS_OVERLAPPEDWINDOW or WS_CLIPSIBLINGS or WS_CLIPCHILDREN,
                                   Wtx,Wty,Wwd,Wht,
                                   NULL,NULL,
                                   hInst,NULL
                       mov	hWnd,eax
                       invoke	LoadMenu,hInst,600
                       invoke	SetMenu,hWnd,eax
                       invoke	ShowWindow,hWnd,SW_SHOWNORMAL
                       invoke	UpdateWindow,hWnd
                       call	DoEvents
                       ret
           MainInit 		ENDP

           ; --------------- Program main loop
           MainLoop		PROC	hWin:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
                       LOCAL	WINRect:RECT
                       LOCAL	PixFormat:DWORD
                       .if uMsg == WM_COMMAND
                           .if wParam == 1000
                               invoke	SendMessage,hWin,WM_SYSCOMMAND,SC_CLOSE,NULL
                           .endif
                           return	0
                       .elseif	uMsg == WM_CREATE
                           invoke	GetDC,hWin
                           mov	MainHDC,eax
                           mov	ax,SIZEOF PixFrm
                           mov	PixFrm.nSize,ax
                           mov	PixFrm.nVersion,1
                           mov	PixFrm.dwFlags,PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER
                           mov	PixFrm.dwLayerMask,PFD_MAIN_PLANE
                           mov	PixFrm.iPixelType,PFD_TYPE_RGBA
                           mov	PixFrm.cColorBits,8
                           mov	PixFrm.cDepthBits,16
                           mov	PixFrm.cAccumBits,0
                           mov	PixFrm.cStencilBits,0
                           invoke	ChoosePixelFormat,MainHDC,ADDR PixFrm
                           mov	PixFormat,eax
                           invoke	SetPixelFormat,MainHDC,PixFormat,ADDR PixFrm
                           or	eax,eax
                           jz	NoPixelFmt
                           invoke	wglCreateContext,MainHDC
                           mov	OpenDC,eax
                           invoke	wglMakeCurrent,MainHDC,OpenDC
                           invoke	GetClientRect,hWin,ADDR WINRect
                           invoke	GlInit,WINRect.right,WINRect.bottom
           NoPixelFmt:
                           return	0
                       .elseif	uMsg == WM_SIZE
                           invoke	GetClientRect,hWin,ADDR WINRect
                           invoke	ResizeObject,WINRect.right,WINRect.bottom
                           return	0

                       .elseif	uMsg == WM_CLOSE
                           szText TheText,"Are you sure ?"
                           invoke	MessageBox,hWin,ADDR TheText,ADDR szDisplayName,MB_YESNO+MB_ICONQUESTION
                           .if eax == IDNO
                               return	0
                           .endif
                           mov	eax,OpenDC
                           or	eax,eax
                           jz	NoGlDC
                           ; Delete our objects
                           invoke	DeleteSpheres
                           invoke	wglDeleteContext,OpenDC
           NoGlDC:				invoke	ReleaseDC,hWin,MainHDC
                           invoke	DestroyWindow,hWin
                           return	0
                       .elseif uMsg == WM_DESTROY
                           invoke	PostQuitMessage,NULL
                           return	0
                       .endif
                       invoke	DefWindowProc,hWin,uMsg,wParam,lParam
                       ret
           MainLoop		ENDP

           ; ------------------------------
           ; OpenGl related stuff
           ; ------------------------------

           ; --------------- Init the scene
           GlInit			PROC	ParentW:DWORD,ParentH:DWORD
                       invoke	SetLightSource,GL_LIGHT0,ADDR LightSourcePosition,ADDR LightAmbient
                       invoke	SetLightSource,GL_LIGHT1,ADDR LightSource2Position,ADDR Light2Ambient
                       invoke	SetLightSource,GL_LIGHT2,ADDR LightSource3Position,ADDR Light3Ambient
                       invoke	CreateSphere,1,GLU_FILL,GLU_SMOOTH,ADDR Sphere1Color,ADDR Sphere1Radius,sphere1Parts
                       mov	GlSphere1,eax
                       invoke	CreateSphere,2,GLU_FILL,GLU_SMOOTH,ADDR Sphere2Color,ADDR Sphere2Radius,sphere2Parts
                       mov	GlSphere2,eax
                       invoke	CreateSphere,3,GLU_FILL,GLU_SMOOTH,ADDR Sphere3Color,ADDR Sphere3Radius,sphere3Parts
                       mov	GlSphere3,eax
                       invoke	CreateSphere,4,GLU_FILL,GLU_SMOOTH,ADDR Sphere4Color,ADDR Sphere4Radius,sphere4Parts
                       mov	GlSphere4,eax
                       invoke	CreateSphere,5,GLU_FILL,GLU_SMOOTH,ADDR Sphere5Color,ADDR Sphere5Radius,sphere5Parts
                       mov	GlSphere5,eax
                       invoke	CreateSphere,6,GLU_FILL,GLU_SMOOTH,ADDR Sphere6Color,ADDR Sphere6Radius,sphere6Parts
                       mov	GlSphere6,eax
                       invoke	CreateSphere,7,GLU_FILL,GLU_SMOOTH,ADDR Sphere7Color,ADDR Sphere7Radius,sphere7Parts
                       mov	GlSphere7,eax
                       ; Set global flags
                       invoke	glEnable,GL_DEPTH_TEST
                       invoke	glEnable,GL_LIGHTING
                       invoke	glEnable,GL_CULL_FACE		; Don't render back faces
                       invoke	glShadeModel,GL_SMOOTH
                       invoke	glEnable,GL_NORMALIZE
                       ret
           GlInit			ENDP

           ; --------------- Display the scene
           DrawScene		PROC
                           invoke	glClear,GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT
                       invoke	glPushMatrix
                           invoke	RotateObject,1,ADDR Sphere1Position,ADDR Sphere1AnglesSpeedFlt,ADDR Sphere1AnglesFlt
                           invoke	glPushMatrix
                               invoke	RotateObject,2,ADDR Sphere2Position,ADDR Sphere2AnglesSpeedFlt,ADDR Sphere2AnglesFlt
                               invoke	glLightfv,GL_LIGHT0,GL_POSITION,ADDR LightSourcePosition
                           invoke	glPopMatrix
                           invoke	glPushMatrix
                               invoke	RotateObject,3,ADDR Sphere3Position,ADDR Sphere3AnglesSpeedFlt,ADDR Sphere3AnglesFlt
                               invoke	glLightfv,GL_LIGHT1,GL_POSITION,ADDR LightSource2Position
                           invoke	glPopMatrix
                           invoke	glPushMatrix
                               invoke	RotateObject,4,ADDR Sphere4Position,ADDR Sphere4AnglesSpeedFlt,ADDR Sphere4AnglesFlt
                               invoke	glLightfv,GL_LIGHT2,GL_POSITION,ADDR LightSource3Position
                           invoke	glPopMatrix
                           invoke	glPushMatrix
                               invoke	RotateObject,5,ADDR Sphere5Position,ADDR Sphere5AnglesSpeedFlt,ADDR Sphere5AnglesFlt
                           invoke	glPopMatrix
                           invoke	glPushMatrix
                               invoke	RotateObject,6,ADDR Sphere6Position,ADDR Sphere6AnglesSpeedFlt,ADDR Sphere6AnglesFlt
                           invoke	glPopMatrix
                           invoke	glPushMatrix
                               invoke	RotateObject,7,ADDR Sphere7Position,ADDR Sphere7AnglesSpeedFlt,ADDR Sphere7AnglesFlt
                           invoke	glPopMatrix
                       invoke	glPopMatrix
                       invoke	SwapBuffers,MainHDC
                       ret
           DrawScene		ENDP

           ; --------------- Resize the scene
           ResizeObject		PROC	ParentW:DWORD,ParentH:DWORD
                       invoke	glViewport,0,0,ParentW,ParentH
                       invoke	glMatrixMode,GL_PROJECTION
                       invoke	glLoadIdentity
                       invoke	gluPerspective,DWORD PTR Value45Dbl,DWORD PTR Value45Dbl+4,DWORD PTR Value1Dbl,DWORD PTR Value1Dbl+4,DWORD PTR Value3Dbl,DWORD PTR Value3Dbl+4,DWORD PTR Value7Dbl,DWORD PTR Value7Dbl+4
                       invoke	glMatrixMode,GL_MODELVIEW
                       invoke	glLoadIdentity
                       ret
           ResizeObject		ENDP

           ; --------------- Free the memory allocated for the spheres
           DeleteSpheres		PROC
                       invoke	gluDeleteQuadric,GlSphere1
                       invoke	gluDeleteQuadric,GlSphere2
                       invoke	gluDeleteQuadric,GlSphere3
                       invoke	gluDeleteQuadric,GlSphere4
                       invoke	gluDeleteQuadric,GlSphere5
                       invoke	gluDeleteQuadric,GlSphere6
                       invoke	gluDeleteQuadric,GlSphere7
                       ret
           DeleteSpheres		ENDP

           ; --------------- Create a sphere with glu object
           CreateSphere		PROC	ListNumber:DWORD,FillType:DWORD,NormalsType:DWORD,Color:DWORD,Radius:DWORD,Parts:DWORD
                       LOCAL	GlSphere:DWORD
                       invoke	glNewList,ListNumber,GL_COMPILE
                       ; Create a template
                       invoke	gluNewQuadric
                       mov	GlSphere,eax
                       ; Set object draw style
                       invoke	gluQuadricDrawStyle,GlSphere,FillType
                       ; Set normals style
                       invoke	gluQuadricNormals,GlSphere,NormalsType
                       ; Set object color
                       invoke	glMaterialfv,GL_FRONT,GL_AMBIENT_AND_DIFFUSE,Color
                       mov	eax,Radius
                       ; Create a sphere primitive
                       invoke	gluSphere,GlSphere,[eax],[eax+4],Parts,Parts
                       invoke	glEndList
                       mov	eax,GlSphere
                       ret
           CreateSphere		ENDP

           ; --------------- Set a light source
           SetLightSource		PROC	LtNumber:DWORD,LtPosition:DWORD,LtAmbient:DWORD
                       invoke	glLightfv,LtNumber,GL_POSITION,LtPosition
                       invoke	glLightfv,LtNumber,GL_DIFFUSE,ADDR LightDiffuse
                       invoke	glLightfv,LtNumber,GL_SPECULAR,ADDR LightSpecular
                       invoke	glLightfv,LtNumber,GL_AMBIENT,LtAmbient
                       invoke	glLightfv,LtNumber,GL_CONSTANT_ATTENUATION,ADDR LightConstAtt
                       invoke	glLightfv,LtNumber,GL_LINEAR_ATTENUATION,ADDR LightLinAtt
                       invoke	glLightfv,LtNumber,GL_QUADRATIC_ATTENUATION,ADDR LightQuadAtt
                       invoke	glLightfv,LtNumber,GL_SPOT_CUTOFF,ADDR SpotCut
                       invoke	glLightfv,LtNumber,GL_SPOT_EXPONENT,ADDR SpotExp
                       invoke	glLightfv,LtNumber,GL_SPOT_DIRECTION,ADDR SpotDir
                       invoke	glEnable,LtNumber
                       ret
           SetLightSource		ENDP

           ; --------------- Position and rotate an object
           RotateObject		PROC	ListNumber:DWORD,XYZPosition:DWORD,XYZRotations:DWORD,XYZAngles:DWORD
                       LOCAL	LocXSpd:DWORD
                       LOCAL	LocYSpd:DWORD
                       LOCAL	LocZSpd:DWORD
                       LOCAL	LocXAngle:DWORD
                       LOCAL	LocYAngle:DWORD
                       LOCAL	LocZAngle:DWORD
                       mov	eax,XYZPosition
                       mov	ecx,[eax]
                       mov	ebx,[eax+4]
                       mov	eax,[eax+8]
                       invoke	glTranslatef,ecx,ebx,eax
                           mov	eax,XYZAngles			; Rotate X
                           mov	ebx,XYZRotations
                           fld	DWORD PTR [eax]
                           fadd	DWORD PTR [ebx]
                           fstp	DWORD PTR [eax]
                           invoke	glRotatef,[eax],Value0Flt,Value1Flt,Value0Flt
                           mov	eax,XYZAngles			; Rotate Y
                           mov	ebx,XYZRotations
                           fld	DWORD PTR [eax+4]
                           fadd	DWORD PTR [ebx+4]
                           fstp	DWORD PTR [eax+4]
                           invoke	glRotatef,[eax+4],Value1Flt,Value0Flt,Value0Flt
                           mov	eax,XYZAngles			; Rotate Z
                           mov	ebx,XYZRotations
                           fld	DWORD PTR [eax+8]
                           fadd	DWORD PTR [ebx+8]
                           fstp	DWORD PTR [eax+8]
                           invoke	glRotatef,DWORD PTR [eax+8],Value0Flt,Value0Flt,Value1Flt
                       invoke	glCallList,ListNumber
                       ret
           RotateObject		ENDP

           ; --------------- Program end
           end start


            ; OpenGL programming example                 Vu dans FasmW  EXAMPLES  OPENGL\OpenGL.asm

            format PE GUI 4.0
            entry start

            include 'win32a.inc'

            include 'opengl.inc'

            section '.data' data readable writeable

              _title db 'OpenGL example',0
              _class db 'FASMOPENGL32',0

              theta GLfloat 0.6

              wc WNDCLASS 0,WindowProc,0,0,NULL,NULL,NULL,NULL,NULL,_class

              hwnd dd ?
              hdc dd ?
              hrc dd ?

              msg MSG
              rc RECT
              pfd PIXELFORMATDESCRIPTOR

              active dd ?

            section '.code' code readable executable

              start:

                invoke	GetModuleHandle,0
                mov	[wc.hInstance],eax
                invoke	LoadIcon,0,IDI_APPLICATION
                mov	[wc.hIcon],eax
                invoke	LoadCursor,0,IDC_ARROW
                mov	[wc.hCursor],eax
                invoke	RegisterClass,wc
                invoke	CreateWindowEx,0,_class,_title,WS_VISIBLE+WS_OVERLAPPEDWINDOW+WS_CLIPCHILDREN+WS_CLIPSIBLINGS,16,16,432,432,NULL,NULL,[wc.hInstance],NULL
                mov	[hwnd],eax

              msg_loop:
                invoke	InvalidateRect,[hwnd],NULL,FALSE
                invoke	GetMessage,msg,NULL,0,0
                or	eax,eax
                jz	end_loop
                invoke	TranslateMessage,msg
                invoke	DispatchMessage,msg
                jmp	msg_loop

              end_loop:
                invoke	ExitProcess,[msg.wParam]

            proc WindowProc hwnd,wmsg,wparam,lparam
                push	ebx esi edi
                cmp	[wmsg],WM_CREATE
                je	wmcreate
                cmp	[wmsg],WM_SIZE
                je	wmsize
                cmp	[wmsg],WM_ACTIVATEAPP
                je	wmactivateapp
                cmp	[wmsg],WM_PAINT
                je	wmpaint
                cmp	[wmsg],WM_KEYDOWN
                je	wmkeydown
                cmp	[wmsg],WM_DESTROY
                je	wmdestroy
              defwndproc:
                invoke	DefWindowProc,[hwnd],[wmsg],[wparam],[lparam]
                jmp	finish
              wmcreate:
                invoke	GetDC,[hwnd]
                mov	[hdc],eax
                mov	edi,pfd
                mov	ecx,sizeof.PIXELFORMATDESCRIPTOR shr 2
                xor	eax,eax
                rep	stosd
                mov	[pfd.nSize],sizeof.PIXELFORMATDESCRIPTOR
                mov	[pfd.nVersion],1
                mov	[pfd.dwFlags],PFD_SUPPORT_OPENGL+PFD_DOUBLEBUFFER+PFD_DRAW_TO_WINDOW
                mov	[pfd.dwLayerMask],PFD_MAIN_PLANE
                mov	[pfd.iPixelType],PFD_TYPE_RGBA
                mov	[pfd.cColorBits],16
                mov	[pfd.cDepthBits],16
                mov	[pfd.cAccumBits],0
                mov	[pfd.cStencilBits],0
                invoke	ChoosePixelFormat,[hdc],pfd
                invoke	SetPixelFormat,[hdc],eax,pfd
                invoke	wglCreateContext,[hdc]
                mov	[hrc],eax
                invoke	wglMakeCurrent,[hdc],[hrc]
                invoke	GetClientRect,[hwnd],rc
                invoke	glViewport,0,0,[rc.right],[rc.bottom]
                xor	eax,eax
                jmp	finish
              wmsize:
                invoke	GetClientRect,[hwnd],rc
                invoke	glViewport,0,0,[rc.right],[rc.bottom]
                invoke	InvalidateRect,[hwnd],NULL,FALSE
                xor	eax,eax
                jmp	finish
              wmactivateapp:
                push	[wmsg]
                pop	[active]
                xor	eax,eax
                jmp	finish
              wmpaint:
                invoke	glClear,GL_COLOR_BUFFER_BIT
                invoke	glRotatef,[theta],0.0,0.0,1.0
                invoke	glBegin,GL_QUADS
                invoke	glColor3f,1.0,0.1,0.1
                invoke	glVertex3f,-0.6,-0.6,0.0
                invoke	glColor3f,0.1,0.1,0.1
                invoke	glVertex3f,0.6,-0.6,0.0
                invoke	glColor3f,0.1,0.1,1.0
                invoke	glVertex3f,0.6,0.6,0.0
                invoke	glColor3f,1.0,0.1,1.0
                invoke	glVertex3f,-0.6,0.6,0.0
                invoke	glEnd
                invoke	SwapBuffers,[hdc]
                xor	eax,eax
                jmp	finish
              wmkeydown:
                cmp	[wparam],VK_ESCAPE
                jne	defwndproc
              wmdestroy:
                invoke	wglMakeCurrent,0,0
                invoke	wglDeleteContext,[hrc]
                invoke	ReleaseDC,[hwnd],[hdc]
                invoke	PostQuitMessage,0
                xor	eax,eax
              finish:
                pop	edi esi ebx
                ret
            endp

            section '.idata' import data readable writeable

              library kernel,'KERNEL32.DLL',\
                  user,'USER32.DLL',\
                  gdi,'GDI32.DLL',\
                  opengl,'OPENGL32.DLL',\
                  glu,'GLU32.DLL'

              import kernel,\
                 GetModuleHandle,'GetModuleHandleA',\
                 ExitProcess,'ExitProcess'

              import user,\
                 RegisterClass,'RegisterClassA',\
                 CreateWindowEx,'CreateWindowExA',\
                 DefWindowProc,'DefWindowProcA',\
                 GetMessage,'GetMessageA',\
                 TranslateMessage,'TranslateMessage',\
                 DispatchMessage,'DispatchMessageA',\
                 LoadCursor,'LoadCursorA',\
                 LoadIcon,'LoadIconA',\
                 GetClientRect,'GetClientRect',\
                 InvalidateRect,'InvalidateRect',\
                 GetDC,'GetDC',\
                 ReleaseDC,'ReleaseDC',\
                 PostQuitMessage,'PostQuitMessage'

              import gdi,\
                 ChoosePixelFormat,'ChoosePixelFormat',\
                 SetPixelFormat,'SetPixelFormat',\
                 SwapBuffers,'SwapBuffers'

              import opengl,\
                 glAccum,'glAccum',\
                 glAlphaFunc,'glAlphaFunc',\
                 glAreTexturesResident,'glAreTexturesResident',\
                 glArrayElement,'glArrayElement',\
                 glBegin,'glBegin',\
                 glBindTexture,'glBindTexture',\
                 glBitmap,'glBitmap',\
                 glBlendFunc,'glBlendFunc',\
                 glCallList,'glCallList',\
                 glCallLists,'glCallLists',\
                 glClear,'glClear',\
                 glClearAccum,'glClearAccum',\
                 glClearColor,'glClearColor',\
                 glClearDepth,'glClearDepth',\
                 glClearIndex,'glClearIndex',\
                 glClearStencil,'glClearStencil',\
                 glClipPlane,'glClipPlane',\
                 glColor3b,'glColor3b',\
                 glColor3bv,'glColor3bv',\
                 glColor3d,'glColor3d',\
                 glColor3dv,'glColor3dv',\
                 glColor3f,'glColor3f',\
                 glColor3fv,'glColor3fv',\
                 glColor3i,'glColor3i',\
                 glColor3iv,'glColor3iv',\
                 glColor3s,'glColor3s',\
                 glColor3sv,'glColor3sv',\
                 glColor3ub,'glColor3ub',\
                 glColor3ubv,'glColor3ubv',\
                 glColor3ui,'glColor3ui',\
                 glColor3uiv,'glColor3uiv',\
                 glColor3us,'glColor3us',\
                 glColor3usv,'glColor3usv',\
                 glColor4b,'glColor4b',\
                 glColor4bv,'glColor4bv',\
                 glColor4d,'glColor4d',\
                 glColor4dv,'glColor4dv',\
                 glColor4f,'glColor4f',\
                 glColor4fv,'glColor4fv',\
                 glColor4i,'glColor4i',\
                 glColor4iv,'glColor4iv',\
                 glColor4s,'glColor4s',\
                 glColor4sv,'glColor4sv',\
                 glColor4ub,'glColor4ub',\
                 glColor4ubv,'glColor4ubv',\
                 glColor4ui,'glColor4ui',\
                 glColor4uiv,'glColor4uiv',\
                 glColor4us,'glColor4us',\
                 glColor4usv,'glColor4usv',\
                 glColorMask,'glColorMask',\
                 glColorMaterial,'glColorMaterial',\
                 glColorPointer,'glColorPointer',\
                 glCopyPixels,'glCopyPixels',\
                 glCopyTexImage1D,'glCopyTexImage1D',\
                 glCopyTexImage2D,'glCopyTexImage2D',\
                 glCopyTexSubImage1D,'glCopyTexSubImage1D',\
                 glCopyTexSubImage2D,'glCopyTexSubImage2D',\
                 glCullFace,'glCullFace',\
                 glDeleteLists,'glDeleteLists',\
                 glDeleteTextures,'glDeleteTextures',\
                 glDepthFunc,'glDepthFunc',\
                 glDepthMask,'glDepthMask',\
                 glDepthRange,'glDepthRange',\
                 glDisable,'glDisable',\
                 glDisableClientState,'glDisableClientState',\
                 glDrawArrays,'glDrawArrays',\
                 glDrawBuffer,'glDrawBuffer',\
                 glDrawElements,'glDrawElements',\
                 glDrawPixels,'glDrawPixels',\
                 glEdgeFlag,'glEdgeFlag',\
                 glEdgeFlagPointer,'glEdgeFlagPointer',\
                 glEdgeFlagv,'glEdgeFlagv',\
                 glEnable,'glEnable',\
                 glEnableClientState,'glEnableClientState',\
                 glEnd,'glEnd',\
                 glEndList,'glEndList',\
                 glEvalCoord1d,'glEvalCoord1d',\
                 glEvalCoord1dv,'glEvalCoord1dv',\
                 glEvalCoord1f,'glEvalCoord1f',\
                 glEvalCoord1fv,'glEvalCoord1fv',\
                 glEvalCoord2d,'glEvalCoord2d',\
                 glEvalCoord2dv,'glEvalCoord2dv',\
                 glEvalCoord2f,'glEvalCoord2f',\
                 glEvalCoord2fv,'glEvalCoord2fv',\
                 glEvalMesh1,'glEvalMesh1',\
                 glEvalMesh2,'glEvalMesh2',\
                 glEvalPoint1,'glEvalPoint1',\
                 glEvalPoint2,'glEvalPoint2',\
                 glFeedbackBuffer,'glFeedbackBuffer',\
                 glFinish,'glFinish',\
                 glFlush,'glFlush',\
                 glFogf,'glFogf',\
                 glFogfv,'glFogfv',\
                 glFogi,'glFogi',\
                 glFogiv,'glFogiv',\
                 glFrontFace,'glFrontFace',\
                 glFrustum,'glFrustum',\
                 glGenLists,'glGenLists',\
                 glGenTextures,'glGenTextures',\
                 glGetBooleanv,'glGetBooleanv',\
                 glGetClipPlane,'glGetClipPlane',\
                 glGetDoublev,'glGetDoublev',\
                 glGetError,'glGetError',\
                 glGetFloatv,'glGetFloatv',\
                 glGetIntegerv,'glGetIntegerv',\
                 glGetLightfv,'glGetLightfv',\
                 glGetLightiv,'glGetLightiv',\
                 glGetMapdv,'glGetMapdv',\
                 glGetMapfv,'glGetMapfv',\
                 glGetMapiv,'glGetMapiv',\
                 glGetMaterialfv,'glGetMaterialfv',\
                 glGetMaterialiv,'glGetMaterialiv',\
                 glGetPixelMapfv,'glGetPixelMapfv',\
                 glGetPixelMapuiv,'glGetPixelMapuiv',\
                 glGetPixelMapusv,'glGetPixelMapusv',\
                 glGetPointerv,'glGetPointerv',\
                 glGetPolygonStipple,'glGetPolygonStipple',\
                 glGetString,'glGetString',\
                 glGetTexEnvfv,'glGetTexEnvfv',\
                 glGetTexEnviv,'glGetTexEnviv',\
                 glGetTexGendv,'glGetTexGendv',\
                 glGetTexGenfv,'glGetTexGenfv',\
                 glGetTexGeniv,'glGetTexGeniv',\
                 glGetTexImage,'glGetTexImage',\
                 glGetTexLevelParameterfv,'glGetTexLevelParameterfv',\
                 glGetTexLevelParameteriv,'glGetTexLevelParameteriv',\
                 glGetTexParameterfv,'glGetTexParameterfv',\
                 glGetTexParameteriv,'glGetTexParameteriv',\
                 glHint,'glHint',\
                 glIndexMask,'glIndexMask',\
                 glIndexPointer,'glIndexPointer',\
                 glIndexd,'glIndexd',\
                 glIndexdv,'glIndexdv',\
                 glIndexf,'glIndexf',\
                 glIndexfv,'glIndexfv',\
                 glIndexi,'glIndexi',\
                 glIndexiv,'glIndexiv',\
                 glIndexs,'glIndexs',\
                 glIndexsv,'glIndexsv',\
                 glIndexub,'glIndexub',\
                 glIndexubv,'glIndexubv',\
                 glInitNames,'glInitNames',\
                 glInterleavedArrays,'glInterleavedArrays',\
                 glIsEnabled,'glIsEnabled',\
                 glIsList,'glIsList',\
                 glIsTexture,'glIsTexture',\
                 glLightModelf,'glLightModelf',\
                 glLightModelfv,'glLightModelfv',\
                 glLightModeli,'glLightModeli',\
                 glLightModeliv,'glLightModeliv',\
                 glLightf,'glLightf',\
                 glLightfv,'glLightfv',\
                 glLighti,'glLighti',\
                 glLightiv,'glLightiv',\
                 glLineStipple,'glLineStipple',\
                 glLineWidth,'glLineWidth',\
                 glListBase,'glListBase',\
                 glLoadIdentity,'glLoadIdentity',\
                 glLoadMatrixd,'glLoadMatrixd',\
                 glLoadMatrixf,'glLoadMatrixf',\
                 glLoadName,'glLoadName',\
                 glLogicOp,'glLogicOp',\
                 glMap1d,'glMap1d',\
                 glMap1f,'glMap1f',\
                 glMap2d,'glMap2d',\
                 glMap2f,'glMap2f',\
                 glMapGrid1d,'glMapGrid1d',\
                 glMapGrid1f,'glMapGrid1f',\
                 glMapGrid2d,'glMapGrid2d',\
                 glMapGrid2f,'glMapGrid2f',\
                 glMaterialf,'glMaterialf',\
                 glMaterialfv,'glMaterialfv',\
                 glMateriali,'glMateriali',\
                 glMaterialiv,'glMaterialiv',\
                 glMatrixMode,'glMatrixMode',\
                 glMultMatrixd,'glMultMatrixd',\
                 glMultMatrixf,'glMultMatrixf',\
                 glNewList,'glNewList',\
                 glNormal3b,'glNormal3b',\
                 glNormal3bv,'glNormal3bv',\
                 glNormal3d,'glNormal3d',\
                 glNormal3dv,'glNormal3dv',\
                 glNormal3f,'glNormal3f',\
                 glNormal3fv,'glNormal3fv',\
                 glNormal3i,'glNormal3i',\
                 glNormal3iv,'glNormal3iv',\
                 glNormal3s,'glNormal3s',\
                 glNormal3sv,'glNormal3sv',\
                 glNormalPointer,'glNormalPointer',\
                 glOrtho,'glOrtho',\
                 glPassThrough,'glPassThrough',\
                 glPixelMapfv,'glPixelMapfv',\
                 glPixelMapuiv,'glPixelMapuiv',\
                 glPixelMapusv,'glPixelMapusv',\
                 glPixelStoref,'glPixelStoref',\
                 glPixelStorei,'glPixelStorei',\
                 glPixelTransferf,'glPixelTransferf',\
                 glPixelTransferi,'glPixelTransferi',\
                 glPixelZoom,'glPixelZoom',\
                 glPointSize,'glPointSize',\
                 glPolygonMode,'glPolygonMode',\
                 glPolygonOffset,'glPolygonOffset',\
                 glPolygonStipple,'glPolygonStipple',\
                 glPopAttrib,'glPopAttrib',\
                 glPopClientAttrib,'glPopClientAttrib',\
                 glPopMatrix,'glPopMatrix',\
                 glPopName,'glPopName',\
                 glPrioritizeTextures,'glPrioritizeTextures',\
                 glPushAttrib,'glPushAttrib',\
                 glPushClientAttrib,'glPushClientAttrib',\
                 glPushMatrix,'glPushMatrix',\
                 glPushName,'glPushName',\
                 glRasterPos2d,'glRasterPos2d',\
                 glRasterPos2dv,'glRasterPos2dv',\
                 glRasterPos2f,'glRasterPos2f',\
                 glRasterPos2fv,'glRasterPos2fv',\
                 glRasterPos2i,'glRasterPos2i',\
                 glRasterPos2iv,'glRasterPos2iv',\
                 glRasterPos2s,'glRasterPos2s',\
                 glRasterPos2sv,'glRasterPos2sv',\
                 glRasterPos3d,'glRasterPos3d',\
                 glRasterPos3dv,'glRasterPos3dv',\
                 glRasterPos3f,'glRasterPos3f',\
                 glRasterPos3fv,'glRasterPos3fv',\
                 glRasterPos3i,'glRasterPos3i',\
                 glRasterPos3iv,'glRasterPos3iv',\
                 glRasterPos3s,'glRasterPos3s',\
                 glRasterPos3sv,'glRasterPos3sv',\
                 glRasterPos4d,'glRasterPos4d',\
                 glRasterPos4dv,'glRasterPos4dv',\
                 glRasterPos4f,'glRasterPos4f',\
                 glRasterPos4fv,'glRasterPos4fv',\
                 glRasterPos4i,'glRasterPos4i',\
                 glRasterPos4iv,'glRasterPos4iv',\
                 glRasterPos4s,'glRasterPos4s',\
                 glRasterPos4sv,'glRasterPos4sv',\
                 glReadBuffer,'glReadBuffer',\
                 glReadPixels,'glReadPixels',\
                 glRectd,'glRectd',\
                 glRectdv,'glRectdv',\
                 glRectf,'glRectf',\
                 glRectfv,'glRectfv',\
                 glRecti,'glRecti',\
                 glRectiv,'glRectiv',\
                 glRects,'glRects',\
                 glRectsv,'glRectsv',\
                 glRenderMode,'glRenderMode',\
                 glRotated,'glRotated',\
                 glRotatef,'glRotatef',\
                 glScaled,'glScaled',\
                 glScalef,'glScalef',\
                 glScissor,'glScissor',\
                 glSelectBuffer,'glSelectBuffer',\
                 glShadeModel,'glShadeModel',\
                 glStencilFunc,'glStencilFunc',\
                 glStencilMask,'glStencilMask',\
                 glStencilOp,'glStencilOp',\
                 glTexCoord1d,'glTexCoord1d',\
                 glTexCoord1dv,'glTexCoord1dv',\
                 glTexCoord1f,'glTexCoord1f',\
                 glTexCoord1fv,'glTexCoord1fv',\
                 glTexCoord1i,'glTexCoord1i',\
                 glTexCoord1iv,'glTexCoord1iv',\
                 glTexCoord1s,'glTexCoord1s',\
                 glTexCoord1sv,'glTexCoord1sv',\
                 glTexCoord2d,'glTexCoord2d',\
                 glTexCoord2dv,'glTexCoord2dv',\
                 glTexCoord2f,'glTexCoord2f',\
                 glTexCoord2fv,'glTexCoord2fv',\
                 glTexCoord2i,'glTexCoord2i',\
                 glTexCoord2iv,'glTexCoord2iv',\
                 glTexCoord2s,'glTexCoord2s',\
                 glTexCoord2sv,'glTexCoord2sv',\
                 glTexCoord3d,'glTexCoord3d',\
                 glTexCoord3dv,'glTexCoord3dv',\
                 glTexCoord3f,'glTexCoord3f',\
                 glTexCoord3fv,'glTexCoord3fv',\
                 glTexCoord3i,'glTexCoord3i',\
                 glTexCoord3iv,'glTexCoord3iv',\
                 glTexCoord3s,'glTexCoord3s',\
                 glTexCoord3sv,'glTexCoord3sv',\
                 glTexCoord4d,'glTexCoord4d',\
                 glTexCoord4dv,'glTexCoord4dv',\
                 glTexCoord4f,'glTexCoord4f',\
                 glTexCoord4fv,'glTexCoord4fv',\
                 glTexCoord4i,'glTexCoord4i',\
                 glTexCoord4iv,'glTexCoord4iv',\
                 glTexCoord4s,'glTexCoord4s',\
                 glTexCoord4sv,'glTexCoord4sv',\
                 glTexCoordPointer,'glTexCoordPointer',\
                 glTexEnvf,'glTexEnvf',\
                 glTexEnvfv,'glTexEnvfv',\
                 glTexEnvi,'glTexEnvi',\
                 glTexEnviv,'glTexEnviv',\
                 glTexGend,'glTexGend',\
                 glTexGendv,'glTexGendv',\
                 glTexGenf,'glTexGenf',\
                 glTexGenfv,'glTexGenfv',\
                 glTexGeni,'glTexGeni',\
                 glTexGeniv,'glTexGeniv',\
                 glTexImage1D,'glTexImage1D',\
                 glTexImage2D,'glTexImage2D',\
                 glTexParameterf,'glTexParameterf',\
                 glTexParameterfv,'glTexParameterfv',\
                 glTexParameteri,'glTexParameteri',\
                 glTexParameteriv,'glTexParameteriv',\
                 glTexSubImage1D,'glTexSubImage1D',\
                 glTexSubImage2D,'glTexSubImage2D',\
                 glTranslated,'glTranslated',\
                 glTranslatef,'glTranslatef',\
                 glVertex2d,'glVertex2d',\
                 glVertex2dv,'glVertex2dv',\
                 glVertex2f,'glVertex2f',\
                 glVertex2fv,'glVertex2fv',\
                 glVertex2i,'glVertex2i',\
                 glVertex2iv,'glVertex2iv',\
                 glVertex2s,'glVertex2s',\
                 glVertex2sv,'glVertex2sv',\
                 glVertex3d,'glVertex3d',\
                 glVertex3dv,'glVertex3dv',\
                 glVertex3f,'glVertex3f',\
                 glVertex3fv,'glVertex3fv',\
                 glVertex3i,'glVertex3i',\
                 glVertex3iv,'glVertex3iv',\
                 glVertex3s,'glVertex3s',\
                 glVertex3sv,'glVertex3sv',\
                 glVertex4d,'glVertex4d',\
                 glVertex4dv,'glVertex4dv',\
                 glVertex4f,'glVertex4f',\
                 glVertex4fv,'glVertex4fv',\
                 glVertex4i,'glVertex4i',\
                 glVertex4iv,'glVertex4iv',\
                 glVertex4s,'glVertex4s',\
                 glVertex4sv,'glVertex4sv',\
                 glVertexPointer,'glVertexPointer',\
                 glViewport,'glViewport',\
                 wglGetProcAddress,'wglGetProcAddress',\
                 wglCopyContext,'wglCopyContext',\
                 wglCreateContext,'wglCreateContext',\
                 wglCreateLayerContext,'wglCreateLayerContext',\
                 wglDeleteContext,'wglDeleteContext',\
                 wglDescribeLayerPlane,'wglDescribeLayerPlane',\
                 wglGetCurrentContext,'wglGetCurrentContext',\
                 wglGetCurrentDC,'wglGetCurrentDC',\
                 wglGetLayerPaletteEntries,'wglGetLayerPaletteEntries',\
                 wglMakeCurrent,'wglMakeCurrent',\
                 wglRealizeLayerPalette,'wglRealizeLayerPalette',\
                 wglSetLayerPaletteEntries,'wglSetLayerPaletteEntries',\
                 wglShareLists,'wglShareLists',\
                 wglSwapLayerBuffers,'wglSwapLayerBuffers',\
                 wglSwapMultipleBuffers,'wglSwapMultipleBuffers',\
                 wglUseFontBitmapsA,'wglUseFontBitmapsA',\
                 wglUseFontOutlinesA,'wglUseFontOutlinesA',\
                 wglUseFontBitmapsW,'wglUseFontBitmapsW',\
                 wglUseFontOutlinesW,'wglUseFontOutlinesW',\
                 wglUseFontBitmaps,'wglUseFontBitmaps',\
                 wglUseFontOutlines,'wglUseFontOutlines',\
                 glDrawRangeElements,'glDrawRangeElements',\
                 glTexImage3D,'glTexImage3D',\
                 glBlendColor,'glBlendColor',\
                 glBlendEquation,'glBlendEquation',\
                 glColorSubTable,'glColorSubTable',\
                 glCopyColorSubTable,'glCopyColorSubTable',\
                 glColorTable,'glColorTable',\
                 glCopyColorTable,'glCopyColorTable',\
                 glColorTableParameteriv,'glColorTableParameteriv',\
                 glColorTableParameterfv,'glColorTableParameterfv',\
                 glGetColorTable,'glGetColorTable',\
                 glGetColorTableParameteriv,'glGetColorTableParameteriv',\
                 glGetColorTableParameterfv,'glGetColorTableParameterfv',\
                 glConvolutionFilter1D,'glConvolutionFilter1D',\
                 glConvolutionFilter2D,'glConvolutionFilter2D',\
                 glCopyConvolutionFilter1D,'glCopyConvolutionFilter1D',\
                 glCopyConvolutionFilter2D,'glCopyConvolutionFilter2D',\
                 glGetConvolutionFilter,'glGetConvolutionFilter',\
                 glSeparableFilter2D,'glSeparableFilter2D',\
                 glGetSeparableFilter,'glGetSeparableFilter',\
                 glConvolutionParameteri,'glConvolutionParameteri',\
                 glConvolutionParameteriv,'glConvolutionParameteriv',\
                 glConvolutionParameterf,'glConvolutionParameterf',\
                 glConvolutionParameterfv,'glConvolutionParameterfv',\
                 glGetConvolutionParameteriv,'glGetConvolutionParameteriv',\
                 glGetConvolutionParameterfv,'glGetConvolutionParameterfv',\
                 glHistogram,'glHistogram',\
                 glResetHistogram,'glResetHistogram',\
                 glGetHistogram,'glGetHistogram',\
                 glGetHistogramParameteriv,'glGetHistogramParameteriv',\
                 glGetHistogramParameterfv,'glGetHistogramParameterfv',\
                 glMinmax,'glMinmax',\
                 glResetMinmax,'glResetMinmax',\
                 glGetMinmax,'glGetMinmax',\
                 glGetMinmaxParameteriv,'glGetMinmaxParameteriv',\
                 glGetMinmaxParameterfv,'glGetMinmaxParameterfv'

              import glu,\
                 gluBeginCurve,'gluBeginCurve',\
                 gluBeginPolygon,'gluBeginPolygon',\
                 gluBeginSurface,'gluBeginSurface',\
                 gluBeginTrim,'gluBeginTrim',\
                 gluBuild1DMipmaps,'gluBuild1DMipmaps',\
                 gluBuild2DMipmaps,'gluBuild2DMipmaps',\
                 gluCylinder,'gluCylinder',\
                 gluDeleteNurbsRenderer,'gluDeleteNurbsRenderer',\
                 gluDeleteQuadric,'gluDeleteQuadric',\
                 gluDeleteTess,'gluDeleteTess',\
                 gluDisk,'gluDisk',\
                 gluEndCurve,'gluEndCurve',\
                 gluEndPolygon,'gluEndPolygon',\
                 gluEndSurface,'gluEndSurface',\
                 gluEndTrim,'gluEndTrim',\
                 gluErrorString,'gluErrorString',\
                 gluGetNurbsProperty,'gluGetNurbsProperty',\
                 gluGetString,'gluGetString',\
                 gluGetTessProperty,'gluGetTessProperty',\
                 gluLoadSamplingMatrices,'gluLoadSamplingMatrices',\
                 gluLookAt,'gluLookAt',\
                 gluNewNurbsRenderer,'gluNewNurbsRenderer',\
                 gluNewQuadric,'gluNewQuadric',\
                 gluNewTess,'gluNewTess',\
                 gluNextContour,'gluNextContour',\
                 gluNurbsCallback,'gluNurbsCallback',\
                 gluNurbsCurve,'gluNurbsCurve',\
                 gluNurbsProperty,'gluNurbsProperty',\
                 gluNurbsSurface,'gluNurbsSurface',\
                 gluOrtho2D,'gluOrtho2D',\
                 gluPartialDisk,'gluPartialDisk',\
                 gluPerspective,'gluPerspective',\
                 gluPickMatrix,'gluPickMatrix',\
                 gluProject,'gluProject',\
                 gluPwlCurve,'gluPwlCurve',\
                 gluQuadricCallback,'gluQuadricCallback',\
                 gluQuadricDrawStyle,'gluQuadricDrawStyle',\
                 gluQuadricNormals,'gluQuadricNormals',\
                 gluQuadricOrientation,'gluQuadricOrientation',\
                 gluQuadricTexture,'gluQuadricTexture',\
                 gluScaleImage,'gluScaleImage',\
                 gluSphere,'gluSphere',\
                 gluTessBeginContour,'gluTessBeginContour',\
                 gluTessBeginPolygon,'gluTessBeginPolygon',\
                 gluTessCallback,'gluTessCallback',\
                 gluTessEndContour,'gluTessEndContour',\
                 gluTessEndPolygon,'gluTessEndPolygon',\
                 gluTessNormal,'gluTessNormal',\
                 gluTessProperty,'gluTessProperty',\
                 gluTessVertex,'gluTessVertex',\
                 gluUnProject,'gluUnProject'


------------------------------------------------------------------------------  exemple J2M  J2M_MainInit.asm

           ; --------------- Assembler directives
                 .386
                       .model  flat,stdcall
                 option  casemap:none

           ; --------------- External includes

                 include D:\masm32\include\windows.inc
                 include D:\masm32\include\user32.inc
                 include D:\masm32\include\kernel32.inc
                 include D:\masm32\include\gdi32.inc
                 include D:\masm32\include\opengl32.inc
                 include D:\masm32\include\glu32.inc
                 include D:\masm32\include\Winmm.inc

                 includelib D:\masm32\lib\user32.lib
                 includelib D:\masm32\lib\kernel32.lib
                 includelib D:\masm32\lib\gdi32.lib
                 includelib D:\masm32\lib\gdi32.lib
                 includelib D:\masm32\lib\Winmm.lib
                 includelib D:\masm32\lib\opengl32.lib
                 includelib D:\masm32\lib\glu32.lib

           ; --------------- Macros Section

           szText  MACRO Name,Text:VARARG
                   LOCAL lbl
                   jmp lbl
           Name    db  Text,0
           lbl:
                   ENDM

           m2m     MACRO M1, M2
                   push  M2
                   pop M1
                   ENDM

           return  MACRO arg
                   mov eax,arg
                   ret
                   ENDM

           ; --------------- Begin DATA
           .data

            szDisplayName   db  "DDL OpenGL - ESSAIS JMM - The top new",0

            even

           CommandLine   dd  0
           hWnd          dd  0
           MainHDC       dd  0
           OpenDC        dd  0
           hInstance     dd  0

            hBmp1     DWORD  00
            memDC     DWORD  00
           AR_Son      BYTE  00

            toto       BYTE  00

           ; --------------- End DATA
           .code
           MainInit        PROTO :DWORD,:DWORD,:DWORD,:DWORD
           MainLoop        PROTO :DWORD,:DWORD,:DWORD,:DWORD

           ; --------------- Procedures Section
           CenterForm    PROC  wDim:DWORD, sDim:DWORD
                 shr sDim,1
                 shr wDim,1
                 mov eax,wDim
                 sub sDim,eax
                 return  sDim
           CenterForm    ENDP

           ; --------------- Program start

           start:

                 invoke  GetModuleHandle,NULL
                 mov     hInstance, eax
                 invoke  GetCommandLine
                 mov     CommandLine,eax
                 invoke  MainInit,hInstance,NULL,CommandLine,SW_SHOWDEFAULT
                 invoke  ExitProcess,eax

           ; --------------- Program main inits

           MainInit    PROC  hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORD

                 LOCAL wc:WNDCLASSEX
                 LOCAL Wwd:DWORD
                 LOCAL Wht:DWORD
                 LOCAL Wtx:DWORD
                 LOCAL Wty:DWORD
                 LOCAL msg:MSG

                 szText  szClassName,"Win32SDI_Class"
                 mov wc.cbSize,sizeof WNDCLASSEX
                 mov wc.style,0
                 mov wc.lpfnWndProc,offset MainLoop
                 mov wc.cbClsExtra,NULL
                 mov wc.cbWndExtra,NULL
                 m2m wc.hInstance,hInst
                 mov wc.hbrBackground,COLOR_WINDOWTEXT+1
                 mov wc.lpszMenuName,NULL
                 mov wc.lpszClassName,offset szClassName
                 invoke  LoadIcon,hInst,401
                 mov wc.hIcon,eax
                 invoke  LoadCursor,NULL,IDC_ARROW
                 mov wc.hCursor,eax
                 mov wc.hIconSm,0

                 invoke  RegisterClassEx, ADDR wc

                 mov Wwd,1280
                 mov Wht,960

                 invoke  GetSystemMetrics,SM_CXSCREEN
           ;      invoke  CenterForm,Wwd,eax
                 mov Wtx,eax
                 invoke  GetSystemMetrics,SM_CYSCREEN
           ;      invoke  CenterForm,Wht,eax
                 mov Wty,eax
                                                                                ; Wtx,Wty   Wwd,Wht

                 invoke  CreateWindowEx,0,ADDR szClassName, ADDR szDisplayName, WS_POPUP ,00,00,Wtx,Wty, NULL,NULL, hInst,NULL
                 mov hWnd,eax

           ;      invoke  LoadMenu,hInst,800
                 invoke  SetMenu,hWnd,eax
                 invoke  ShowWindow,hWnd,SW_SHOWNORMAL
                 invoke  UpdateWindow,hWnd

                   invoke GetDC,hWnd
                      mov MainHDC,eax
                   invoke LoadBitmap,hInstance,500
                      mov hBmp1,eax
                   invoke CreateCompatibleDC,MainHDC
                      mov memDC, eax
                   invoke SelectObject,memDC,hBmp1

                   invoke SetTimer,hWnd,01011,100,NULL

           StartLoop:    ; Check for waiting messages
                 invoke  PeekMessage,ADDR msg,0,0,0,PM_NOREMOVE
                 or  eax,eax
                 jz  NoMsg
                 invoke  GetMessage,ADDR msg,NULL,0,0
                 or  eax,eax
                 jz  ExitLoop
                 invoke  TranslateMessage,ADDR msg
                 invoke  DispatchMessage,ADDR msg
                 jmp StartLoop
           NoMsg:      ; No pending messages: draw the scene

                 include  ACTION.ASM  ; ---    ADTION Générale

                 jmp StartLoop

           ExitLoop:   mov eax,msg.wParam
                 ret

           MainInit    ENDP

           ; --------------- Program main loop

                     include MainLoop.ASM

           end start        ; --- Program end

--------------------------------------------------------------------------------------   J2M_MainLoop

            MainLoop  PROC  hWin:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD

                     LOCAL WINRect:RECT
                     LOCAL PixFormat:DWORD

                 .if uMsg == WM_COMMAND

                   .if wParam == 1000
                     invoke  SendMessage,hWin,WM_SYSCOMMAND,SC_CLOSE,NULL
                   .endif
                   return  0

                 .elseif uMsg == WM_CREATE

                   return  0

                 .elseif  wParam == 01011

                     mov toto,99

                   return  0

                 .elseif uMsg == WM_SIZE
                   invoke  GetClientRect,hWin,ADDR WINRect
                   return  0

                 .elseif uMsg == WM_KEYUP
                  mov AR_Son,00

                 .ELSEIF wParam ==  VK_A

                     mov toto,99

                  ;  invoke MessageBox,hWnd,NULL,NULL,MB_OK
                  return  0
                 .ELSEIF wParam ==  VK_SPACE  && AR_Son == 00
                     mov AR_Son,01
                  invoke PlaySoundA,600,00,40005h
                  return  0

                 .ELSEIF  wParam==1Bh
                   invoke DestroyWindow,hWnd
                 .elseif  uMsg == WM_CLOSE
                   szText TheText,"- QUITTER le prgm -  Oui ou Non ?  -"
                   invoke  MessageBox,hWin,ADDR TheText,ADDR szDisplayName,MB_YESNO+MB_ICONQUESTION
                   .if eax == IDNO
                   return  0
                   .endif
                   invoke  ReleaseDC,hWin,MainHDC
                   invoke  DestroyWindow,hWin
                   return  0
                 .elseif uMsg == WM_DESTROY
                   invoke  PostQuitMessage,NULL
                   return  0
                 .endif
                 invoke  DefWindowProc,hWin,uMsg,wParam,lParam
                 ret

           MainLoop  ENDP

------------------------------------------------------------------------------------------------------------------------    J2M_ACTION

            .data
             VertA    DWORD  00
             Vert     DWORD  020
            .code

                      invoke Sleep,001                 ;     TEMPORISATION

                         inc Vert
            comment¤
                     inc VertA
                    .if  VertA == 01
                     add Vert,1
                     mov VertA,00
                    .endif
            c------¤

                    .if Vert == 800
                      mov Vert, -800
                   invoke PlaySoundA,600,00,40005h
               ;    invoke SendMessage,hWnd,WM_KEYDOWN,VK_B,NULL
                   .endif

                  invoke  StretchBlt,MainHDC,220 ,Vert ,210 ,142 ,memDC,0 ,0 ,210 ,142 ,0CC0020h
                  invoke  StretchBlt,MainHDC,1060,Vert ,210 ,142 ,memDC,0 ,0 ,210 ,142 ,0CC0020h
                  invoke  StretchBlt,MainHDC,640 ,Vert ,210 ,142 ,memDC,0 ,0 ,210 ,142 ,0CC0020h

                      mov esi,810   ; inverseur
                      sub esi,Vert
                  invoke  StretchBlt,MainHDC,10  ,esi ,210 ,142 ,memDC,0 ,0 ,210 ,142 ,0CC0020h
                  invoke  StretchBlt,MainHDC,430 ,esi ,210 ,142 ,memDC,0 ,0 ,210 ,142 ,0CC0020h
                  invoke  StretchBlt,MainHDC,850 ,esi ,210 ,142 ,memDC,0 ,0 ,210 ,142 ,0CC0020h

            .data
             PosX   DWORD 10
             PosY   DWORD 05
             CF_DX   BYTE 00   ;  00_right   01_Left
             CF_DY   BYTE 00   ;
            .code

            ;.if  toto == 99           Wtx   Wty
            ; mov toto,00
                                   mov eax,Wtx
                      .if      PosX >= eax  ;    1260
                          mov CF_DX,01
                       invoke PlaySoundA,601,00,40005h
                      .elseif  PosX <    2
                          mov CF_DX,00
                       invoke PlaySoundA,601,00,40005h
                      .endif
                                   mov eax,Wty
                      .if      PosY >= eax ;   940
                          mov CF_DY,01
                       invoke PlaySoundA,601,00,40005h
                      .elseif  PosY <    2
                          mov CF_DY,00
                       invoke PlaySoundA,601,00,40005h
                      .endif

                      .if     CF_DX == 00
                          inc  PosX
                      .else
                          dec  PosX
                      .endif

                      .if     CF_DY == 00
                          inc  PosY
                      .else
                          dec  PosY
                      .endif

                   invoke SetCursorPos,PosX,PosY

            ;.endif


              ; ===========================================
              ; opengl32.inc copyright MASM32 1998 - 2005
              ; ===========================================

            IFNDEF OPENGL32_INC
            OPENGL32_INC equ <1>

            DllInitialize PROTO :DWORD,:DWORD,:DWORD
            GlmfBeginGlsBlock PROTO :DWORD
            GlmfCloseMetaFile PROTO :DWORD
            GlmfEndGlsBlock PROTO :DWORD
            GlmfEndPlayback PROTO :DWORD
            GlmfInitPlayback PROTO :DWORD,:DWORD,:DWORD
            GlmfPlayGlsRecord PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glAccum PROTO :DWORD,:DWORD
            glAlphaFunc PROTO :DWORD,:DWORD
            glAreTexturesResident PROTO :DWORD,:DWORD,:DWORD
            glArrayElement PROTO :DWORD
            glBegin PROTO :DWORD
            glBindTexture PROTO :DWORD,:DWORD
            glBitmap PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glBlendFunc PROTO :DWORD,:DWORD
            glCallList PROTO :DWORD
            glCallLists PROTO :DWORD,:DWORD,:DWORD
            glClear PROTO :DWORD
            glClearAccum PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glClearColor PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glClearDepth PROTO :DWORD,:DWORD
            glClearIndex PROTO :DWORD
            glClearStencil PROTO :DWORD
            glClipPlane PROTO :DWORD,:DWORD
            glColor3b PROTO :DWORD,:DWORD,:DWORD
            glColor3bv PROTO :DWORD
            glColor3d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glColor3dv PROTO :DWORD
            glColor3f PROTO :DWORD,:DWORD,:DWORD
            glColor3fv PROTO :DWORD
            glColor3i PROTO :DWORD,:DWORD,:DWORD
            glColor3iv PROTO :DWORD
            glColor3s PROTO :DWORD,:DWORD,:DWORD
            glColor3sv PROTO :DWORD
            glColor3ub PROTO :DWORD,:DWORD,:DWORD
            glColor3ubv PROTO :DWORD
            glColor3ui PROTO :DWORD,:DWORD,:DWORD
            glColor3uiv PROTO :DWORD
            glColor3us PROTO :DWORD,:DWORD,:DWORD
            glColor3usv PROTO :DWORD
            glColor4b PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glColor4bv PROTO :DWORD
            glColor4d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glColor4dv PROTO :DWORD
            glColor4f PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glColor4fv PROTO :DWORD
            glColor4i PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glColor4iv PROTO :DWORD
            glColor4s PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glColor4sv PROTO :DWORD
            glColor4ub PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glColor4ubv PROTO :DWORD
            glColor4ui PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glColor4uiv PROTO :DWORD
            glColor4us PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glColor4usv PROTO :DWORD
            glColorMask PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glColorMaterial PROTO :DWORD,:DWORD
            glColorPointer PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glCopyPixels PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glCopyTexImage1D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glCopyTexImage2D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glCopyTexSubImage1D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glCopyTexSubImage2D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glCullFace PROTO :DWORD
            glDebugEntry PROTO :DWORD,:DWORD
            glDeleteLists PROTO :DWORD,:DWORD
            glDeleteTextures PROTO :DWORD,:DWORD
            glDepthFunc PROTO :DWORD
            glDepthMask PROTO :DWORD
            glDepthRange PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glDisable PROTO :DWORD
            glDisableClientState PROTO :DWORD
            glDrawArrays PROTO :DWORD,:DWORD,:DWORD
            glDrawBuffer PROTO :DWORD
            glDrawElements PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glDrawPixels PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glEdgeFlag PROTO :DWORD
            glEdgeFlagPointer PROTO :DWORD,:DWORD
            glEdgeFlagv PROTO :DWORD
            glEnable PROTO :DWORD
            glEnableClientState PROTO :DWORD
            glEnd PROTO
            glEndList PROTO
            glEvalCoord1d PROTO :DWORD,:DWORD
            glEvalCoord1dv PROTO :DWORD
            glEvalCoord1f PROTO :DWORD
            glEvalCoord1fv PROTO :DWORD
            glEvalCoord2d PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glEvalCoord2dv PROTO :DWORD
            glEvalCoord2f PROTO :DWORD,:DWORD
            glEvalCoord2fv PROTO :DWORD
            glEvalMesh1 PROTO :DWORD,:DWORD,:DWORD
            glEvalMesh2 PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glEvalPoint1 PROTO :DWORD
            glEvalPoint2 PROTO :DWORD,:DWORD
            glFeedbackBuffer PROTO :DWORD,:DWORD,:DWORD
            glFinish PROTO
            glFlush PROTO
            glFogf PROTO :DWORD,:DWORD
            glFogfv PROTO :DWORD,:DWORD
            glFogi PROTO :DWORD,:DWORD
            glFogiv PROTO :DWORD,:DWORD
            glFrontFace PROTO :DWORD
            glFrustum PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glGenLists PROTO :DWORD
            glGenTextures PROTO :DWORD,:DWORD
            glGetBooleanv PROTO :DWORD,:DWORD
            glGetClipPlane PROTO :DWORD,:DWORD
            glGetDoublev PROTO :DWORD,:DWORD
            glGetError PROTO
            glGetFloatv PROTO :DWORD,:DWORD
            glGetIntegerv PROTO :DWORD,:DWORD
            glGetLightfv PROTO :DWORD,:DWORD,:DWORD
            glGetLightiv PROTO :DWORD,:DWORD,:DWORD
            glGetMapdv PROTO :DWORD,:DWORD,:DWORD
            glGetMapfv PROTO :DWORD,:DWORD,:DWORD
            glGetMapiv PROTO :DWORD,:DWORD,:DWORD
            glGetMaterialfv PROTO :DWORD,:DWORD,:DWORD
            glGetMaterialiv PROTO :DWORD,:DWORD,:DWORD
            glGetPixelMapfv PROTO :DWORD,:DWORD
            glGetPixelMapuiv PROTO :DWORD,:DWORD
            glGetPixelMapusv PROTO :DWORD,:DWORD
            glGetPointerv PROTO :DWORD,:DWORD
            glGetPolygonStipple PROTO :DWORD
            glGetString PROTO :DWORD
            glGetTexEnvfv PROTO :DWORD,:DWORD,:DWORD
            glGetTexEnviv PROTO :DWORD,:DWORD,:DWORD
            glGetTexGendv PROTO :DWORD,:DWORD,:DWORD
            glGetTexGenfv PROTO :DWORD,:DWORD,:DWORD
            glGetTexGeniv PROTO :DWORD,:DWORD,:DWORD
            glGetTexImage PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glGetTexLevelParameterfv PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glGetTexLevelParameteriv PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glGetTexParameterfv PROTO :DWORD,:DWORD,:DWORD
            glGetTexParameteriv PROTO :DWORD,:DWORD,:DWORD
            glHint PROTO :DWORD,:DWORD
            glIndexMask PROTO :DWORD
            glIndexPointer PROTO :DWORD,:DWORD,:DWORD
            glIndexd PROTO :DWORD,:DWORD
            glIndexdv PROTO :DWORD
            glIndexf PROTO :DWORD
            glIndexfv PROTO :DWORD
            glIndexi PROTO :DWORD
            glIndexiv PROTO :DWORD
            glIndexs PROTO :DWORD
            glIndexsv PROTO :DWORD
            glIndexub PROTO :DWORD
            glIndexubv PROTO :DWORD
            glInitNames PROTO
            glInterleavedArrays PROTO :DWORD,:DWORD,:DWORD
            glIsEnabled PROTO :DWORD
            glIsList PROTO :DWORD
            glIsTexture PROTO :DWORD
            glLightModelf PROTO :DWORD,:DWORD
            glLightModelfv PROTO :DWORD,:DWORD
            glLightModeli PROTO :DWORD,:DWORD
            glLightModeliv PROTO :DWORD,:DWORD
            glLightf PROTO :DWORD,:DWORD,:DWORD
            glLightfv PROTO :DWORD,:DWORD,:DWORD
            glLighti PROTO :DWORD,:DWORD,:DWORD
            glLightiv PROTO :DWORD,:DWORD,:DWORD
            glLineStipple PROTO :DWORD,:DWORD
            glLineWidth PROTO :DWORD
            glListBase PROTO :DWORD
            glLoadIdentity PROTO
            glLoadMatrixd PROTO :DWORD
            glLoadMatrixf PROTO :DWORD
            glLoadName PROTO :DWORD
            glLogicOp PROTO :DWORD
            glMap1d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glMap1f PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glMap2d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glMap2f PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glMapGrid1d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glMapGrid1f PROTO :DWORD,:DWORD,:DWORD
            glMapGrid2d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glMapGrid2f PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glMaterialf PROTO :DWORD,:DWORD,:DWORD
            glMaterialfv PROTO :DWORD,:DWORD,:DWORD
            glMateriali PROTO :DWORD,:DWORD,:DWORD
            glMaterialiv PROTO :DWORD,:DWORD,:DWORD
            glMatrixMode PROTO :DWORD
            glMultMatrixd PROTO :DWORD
            glMultMatrixf PROTO :DWORD
            glNewList PROTO :DWORD,:DWORD
            glNormal3b PROTO :DWORD,:DWORD,:DWORD
            glNormal3bv PROTO :DWORD
            glNormal3d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glNormal3dv PROTO :DWORD
            glNormal3f PROTO :DWORD,:DWORD,:DWORD
            glNormal3fv PROTO :DWORD
            glNormal3i PROTO :DWORD,:DWORD,:DWORD
            glNormal3iv PROTO :DWORD
            glNormal3s PROTO :DWORD,:DWORD,:DWORD
            glNormal3sv PROTO :DWORD
            glNormalPointer PROTO :DWORD,:DWORD,:DWORD
            glOrtho PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glPassThrough PROTO :DWORD
            glPixelMapfv PROTO :DWORD,:DWORD,:DWORD
            glPixelMapuiv PROTO :DWORD,:DWORD,:DWORD
            glPixelMapusv PROTO :DWORD,:DWORD,:DWORD
            glPixelStoref PROTO :DWORD,:DWORD
            glPixelStorei PROTO :DWORD,:DWORD
            glPixelTransferf PROTO :DWORD,:DWORD
            glPixelTransferi PROTO :DWORD,:DWORD
            glPixelZoom PROTO :DWORD,:DWORD
            glPointSize PROTO :DWORD
            glPolygonMode PROTO :DWORD,:DWORD
            glPolygonOffset PROTO :DWORD,:DWORD
            glPolygonStipple PROTO :DWORD
            glPopAttrib PROTO
            glPopClientAttrib PROTO
            glPopMatrix PROTO
            glPopName PROTO
            glPrioritizeTextures PROTO :DWORD,:DWORD,:DWORD
            glPushAttrib PROTO :DWORD
            glPushClientAttrib PROTO :DWORD
            glPushMatrix PROTO
            glPushName PROTO :DWORD
            glRasterPos2d PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glRasterPos2dv PROTO :DWORD
            glRasterPos2f PROTO :DWORD,:DWORD
            glRasterPos2fv PROTO :DWORD
            glRasterPos2i PROTO :DWORD,:DWORD
            glRasterPos2iv PROTO :DWORD
            glRasterPos2s PROTO :DWORD,:DWORD
            glRasterPos2sv PROTO :DWORD
            glRasterPos3d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glRasterPos3dv PROTO :DWORD
            glRasterPos3f PROTO :DWORD,:DWORD,:DWORD
            glRasterPos3fv PROTO :DWORD
            glRasterPos3i PROTO :DWORD,:DWORD,:DWORD
            glRasterPos3iv PROTO :DWORD
            glRasterPos3s PROTO :DWORD,:DWORD,:DWORD
            glRasterPos3sv PROTO :DWORD
            glRasterPos4d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glRasterPos4dv PROTO :DWORD
            glRasterPos4f PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glRasterPos4fv PROTO :DWORD
            glRasterPos4i PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glRasterPos4iv PROTO :DWORD
            glRasterPos4s PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glRasterPos4sv PROTO :DWORD
            glReadBuffer PROTO :DWORD
            glReadPixels PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glRectd PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glRectdv PROTO :DWORD,:DWORD
            glRectf PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glRectfv PROTO :DWORD,:DWORD
            glRecti PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glRectiv PROTO :DWORD,:DWORD
            glRects PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glRectsv PROTO :DWORD,:DWORD
            glRenderMode PROTO :DWORD
            glRotated PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glRotatef PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glScaled PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glScalef PROTO :DWORD,:DWORD,:DWORD
            glScissor PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glSelectBuffer PROTO :DWORD,:DWORD
            glShadeModel PROTO :DWORD
            glStencilFunc PROTO :DWORD,:DWORD,:DWORD
            glStencilMask PROTO :DWORD
            glStencilOp PROTO :DWORD,:DWORD,:DWORD
            glTexCoord1d PROTO :DWORD,:DWORD
            glTexCoord1dv PROTO :DWORD
            glTexCoord1f PROTO :DWORD
            glTexCoord1fv PROTO :DWORD
            glTexCoord1i PROTO :DWORD
            glTexCoord1iv PROTO :DWORD
            glTexCoord1s PROTO :DWORD
            glTexCoord1sv PROTO :DWORD
            glTexCoord2d PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glTexCoord2dv PROTO :DWORD
            glTexCoord2f PROTO :DWORD,:DWORD
            glTexCoord2fv PROTO :DWORD
            glTexCoord2i PROTO :DWORD,:DWORD
            glTexCoord2iv PROTO :DWORD
            glTexCoord2s PROTO :DWORD,:DWORD
            glTexCoord2sv PROTO :DWORD
            glTexCoord3d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glTexCoord3dv PROTO :DWORD
            glTexCoord3f PROTO :DWORD,:DWORD,:DWORD
            glTexCoord3fv PROTO :DWORD
            glTexCoord3i PROTO :DWORD,:DWORD,:DWORD
            glTexCoord3iv PROTO :DWORD
            glTexCoord3s PROTO :DWORD,:DWORD,:DWORD
            glTexCoord3sv PROTO :DWORD
            glTexCoord4d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glTexCoord4dv PROTO :DWORD
            glTexCoord4f PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glTexCoord4fv PROTO :DWORD
            glTexCoord4i PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glTexCoord4iv PROTO :DWORD
            glTexCoord4s PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glTexCoord4sv PROTO :DWORD
            glTexCoordPointer PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glTexEnvf PROTO :DWORD,:DWORD,:DWORD
            glTexEnvfv PROTO :DWORD,:DWORD,:DWORD
            glTexEnvi PROTO :DWORD,:DWORD,:DWORD
            glTexEnviv PROTO :DWORD,:DWORD,:DWORD
            glTexGend PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glTexGendv PROTO :DWORD,:DWORD,:DWORD
            glTexGenf PROTO :DWORD,:DWORD,:DWORD
            glTexGenfv PROTO :DWORD,:DWORD,:DWORD
            glTexGeni PROTO :DWORD,:DWORD,:DWORD
            glTexGeniv PROTO :DWORD,:DWORD,:DWORD
            glTexImage1D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glTexImage2D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glTexParameterf PROTO :DWORD,:DWORD,:DWORD
            glTexParameterfv PROTO :DWORD,:DWORD,:DWORD
            glTexParameteri PROTO :DWORD,:DWORD,:DWORD
            glTexParameteriv PROTO :DWORD,:DWORD,:DWORD
            glTexSubImage1D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glTexSubImage2D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glTranslated PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glTranslatef PROTO :DWORD,:DWORD,:DWORD
            glVertex2d PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glVertex2dv PROTO :DWORD
            glVertex2f PROTO :DWORD,:DWORD
            glVertex2fv PROTO :DWORD
            glVertex2i PROTO :DWORD,:DWORD
            glVertex2iv PROTO :DWORD
            glVertex2s PROTO :DWORD,:DWORD
            glVertex2sv PROTO :DWORD
            glVertex3d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glVertex3dv PROTO :DWORD
            glVertex3f PROTO :DWORD,:DWORD,:DWORD
            glVertex3fv PROTO :DWORD
            glVertex3i PROTO :DWORD,:DWORD,:DWORD
            glVertex3iv PROTO :DWORD
            glVertex3s PROTO :DWORD,:DWORD,:DWORD
            glVertex3sv PROTO :DWORD
            glVertex4d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            glVertex4dv PROTO :DWORD
            glVertex4f PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glVertex4fv PROTO :DWORD
            glVertex4i PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glVertex4iv PROTO :DWORD
            glVertex4s PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glVertex4sv PROTO :DWORD
            glVertexPointer PROTO :DWORD,:DWORD,:DWORD,:DWORD
            glViewport PROTO :DWORD,:DWORD,:DWORD,:DWORD
            wglChoosePixelFormat PROTO :DWORD,:DWORD
            wglCopyContext PROTO :DWORD,:DWORD,:DWORD
            wglCreateContext PROTO :DWORD
            wglCreateLayerContext PROTO :DWORD,:DWORD
            wglDeleteContext PROTO :DWORD
            wglDescribeLayerPlane PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            wglDescribePixelFormat PROTO :DWORD,:DWORD,:DWORD,:DWORD
            wglGetCurrentContext PROTO
            wglGetCurrentDC PROTO
            wglGetDefaultProcAddress PROTO :DWORD
            wglGetLayerPaletteEntries PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            wglGetPixelFormat PROTO :DWORD
            wglGetProcAddress PROTO :DWORD
            wglMakeCurrent PROTO :DWORD,:DWORD
            wglRealizeLayerPalette PROTO :DWORD,:DWORD,:DWORD
            wglSetLayerPaletteEntries PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            wglSetPixelFormat PROTO :DWORD,:DWORD,:DWORD
            wglShareLists PROTO :DWORD,:DWORD
            wglSwapBuffers PROTO :DWORD
            wglSwapLayerBuffers PROTO :DWORD,:DWORD
            wglSwapMultipleBuffers PROTO :DWORD,:DWORD
            wglUseFontBitmapsA PROTO :DWORD,:DWORD,:DWORD,:DWORD
            wglUseFontBitmaps equ

            wglUseFontBitmapsW PROTO :DWORD,:DWORD,:DWORD,:DWORD
            wglUseFontOutlinesA PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            wglUseFontOutlines equ

            wglUseFontOutlinesW PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD

            ELSE
            echo -----------------------------------------
            echo WARNING Duplicate include file opengl32.inc
            echo -----------------------------------------
            ENDIF


           ; ..\LIB\GLU32.LIB PROTOTYPES         Vu dans MASM32
           ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~

            gluBeginCurve PROTO :DWORD
            gluBeginPolygon PROTO :DWORD
            gluBeginSurface PROTO :DWORD
            gluBeginTrim PROTO :DWORD
            gluBuild1DMipmaps PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            gluBuild2DMipmaps PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            gluCylinder PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            gluDeleteNurbsRenderer PROTO :DWORD
            gluDeleteQuadric PROTO :DWORD
            gluDeleteTess PROTO :DWORD
            gluDisk PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            gluEndCurve PROTO :DWORD
            gluEndPolygon PROTO :DWORD
            gluEndSurface PROTO :DWORD
            gluEndTrim PROTO :DWORD
            gluErrorString PROTO :DWORD
            gluErrorUnicodeStringEXT PROTO :DWORD
            gluGetNurbsProperty PROTO :DWORD,:DWORD,:DWORD
            gluGetString PROTO :DWORD
            gluGetTessProperty PROTO :DWORD,:DWORD,:DWORD
            gluLoadSamplingMatrices PROTO :DWORD,:DWORD,:DWORD,:DWORD
            gluLookAt PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            gluNewNurbsRenderer PROTO
            gluNewQuadric PROTO
            gluNewTess PROTO
            gluNextContour PROTO :DWORD,:DWORD
            gluNurbsCallback PROTO :DWORD,:DWORD,:DWORD
            gluNurbsCurve PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            gluNurbsProperty PROTO :DWORD,:DWORD,:DWORD
            gluNurbsSurface PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            gluOrtho2D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            gluPartialDisk PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            gluPerspective PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            gluPickMatrix PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            gluProject PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            gluPwlCurve PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            gluQuadricCallback PROTO :DWORD,:DWORD,:DWORD
            gluQuadricDrawStyle PROTO :DWORD,:DWORD
            gluQuadricNormals PROTO :DWORD,:DWORD
            gluQuadricOrientation PROTO :DWORD,:DWORD
            gluQuadricTexture PROTO :DWORD,:DWORD
            gluScaleImage PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            gluSphere PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            gluTessBeginContour PROTO :DWORD
            gluTessBeginPolygon PROTO :DWORD,:DWORD
            gluTessCallback PROTO :DWORD,:DWORD,:DWORD
            gluTessEndContour PROTO :DWORD
            gluTessEndPolygon PROTO :DWORD
            gluTessNormal PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            gluTessProperty PROTO :DWORD,:DWORD,:DWORD,:DWORD
            gluTessVertex PROTO :DWORD,:DWORD,:DWORD
            gluUnProject PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD


            ; ..\LIB\GLAUX.LIB PROTOTYPES            Vu dans MASM32
            ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~

            AllocateMemory PROTO :DWORD
            AllocateZeroedMemory PROTO :DWORD
            CleanUp PROTO
            ComponentFromIndex PROTO :DWORD,:DWORD,:DWORD
            CreateCIPalette PROTO :DWORD
            CreateRGBPalette PROTO :DWORD
            DefaultHandleExpose PROTO :DWORD,:DWORD
            DefaultHandleReshape PROTO :DWORD,:DWORD
            DelayPaletteRealization PROTO
            DestroyThisWindow PROTO :DWORD
            DibNumColors PROTO :DWORD
            FillRgbPaletteEntries PROTO :DWORD,:DWORD,:DWORD
            FindBestPixelFormat PROTO :DWORD,:DWORD,:DWORD
            FindExactPixelFormat PROTO :DWORD,:DWORD,:DWORD
            FindPixelFormat PROTO :DWORD,:DWORD
            FlushPalette PROTO :DWORD,:DWORD
            ForceRedraw PROTO :DWORD
            FreeMemory PROTO :DWORD
            GetRegistrySysColors PROTO :DWORD,:DWORD
            GrabStaticEntries PROTO :DWORD
            IsPixelFormatValid PROTO :DWORD,:DWORD,:DWORD
            KeyDown PROTO :DWORD,:DWORD
            MouseDown PROTO :DWORD,:DWORD,:DWORD
            MouseLoc PROTO :DWORD,:DWORD,:DWORD
            MouseUp PROTO :DWORD,:DWORD,:DWORD
            PixelFormatDescriptorFromDc PROTO :DWORD,:DWORD
            RawImageClose PROTO :DWORD
            RawImageGetData PROTO :DWORD,:DWORD
            RawImageGetRow PROTO :DWORD,:DWORD,:DWORD,:DWORD
            RealizePaletteNow PROTO :DWORD,:DWORD,:DWORD
            ReleaseStaticEntries PROTO :DWORD
            UpdateStaticMapping PROTO :DWORD
            add3 PROTO :DWORD,:DWORD,:DWORD
            auxCloseWindow PROTO
            auxCreateFont PROTO
            auxDIBImageLoadA PROTO :DWORD
            auxDIBImageLoad equ

            auxDrawStrA PROTO :DWORD
            auxDrawStr equ

            auxExposeFunc PROTO :DWORD
            auxGetColorMapSize PROTO
            auxGetDisplayMode PROTO
            auxGetDisplayModeID PROTO
            auxGetDisplayModePolicy PROTO
            auxGetHDC PROTO
            auxGetHGLRC PROTO
            auxGetHWND PROTO
            auxGetMouseLoc PROTO :DWORD,:DWORD
            auxIdleFunc PROTO :DWORD
            auxInitDisplayMode PROTO :DWORD
            auxInitDisplayModeID PROTO :DWORD
            auxInitDisplayModePolicy PROTO :DWORD
            auxInitPosition PROTO :DWORD,:DWORD,:DWORD,:DWORD
            auxInitWindowA PROTO :DWORD
            auxInitWindow equ

            auxKeyFunc PROTO :DWORD,:DWORD
            auxMainLoop PROTO :DWORD
            auxMouseFunc PROTO :DWORD,:DWORD,:DWORD
            auxQuit PROTO
            auxRGBImageLoadA PROTO :DWORD
            auxRGBImageLoad equ

            auxReshapeFunc PROTO :DWORD
            auxSetFogRamp PROTO :DWORD,:DWORD
            auxSetGreyRamp PROTO
            auxSetOneColor PROTO :DWORD,:DWORD,:DWORD,:DWORD
            auxSetRGBMap PROTO :DWORD,:DWORD
            auxSolidBox PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            auxSolidCone PROTO :DWORD,:DWORD,:DWORD,:DWORD
            auxSolidCube PROTO :DWORD,:DWORD
            auxSolidCylinder PROTO :DWORD,:DWORD,:DWORD,:DWORD
            auxSolidDodecahedron PROTO :DWORD,:DWORD
            auxSolidIcosahedron PROTO :DWORD,:DWORD
            auxSolidOctahedron PROTO :DWORD,:DWORD
            auxSolidSphere PROTO :DWORD,:DWORD
            auxSolidTeapot PROTO :DWORD,:DWORD
            auxSolidTetrahedron PROTO :DWORD,:DWORD
            auxSolidTorus PROTO :DWORD,:DWORD,:DWORD,:DWORD
            auxSwapBuffers PROTO
            auxWireBox PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            auxWireCone PROTO :DWORD,:DWORD,:DWORD,:DWORD
            auxWireCube PROTO :DWORD,:DWORD
            auxWireCylinder PROTO :DWORD,:DWORD,:DWORD,:DWORD
            auxWireDodecahedron PROTO :DWORD,:DWORD
            auxWireIcosahedron PROTO :DWORD,:DWORD
            auxWireOctahedron PROTO :DWORD,:DWORD
            auxWireSphere PROTO :DWORD,:DWORD
            auxWireTeapot PROTO :DWORD,:DWORD
            auxWireTetrahedron PROTO :DWORD,:DWORD
            auxWireTorus PROTO :DWORD,:DWORD,:DWORD,:DWORD
            compareParams PROTO :DWORD,:DWORD,:DWORD
            copy3 PROTO :DWORD,:DWORD
            copymat3 PROTO :DWORD,:DWORD
            crossprod PROTO :DWORD,:DWORD,:DWORD
            diff3 PROTO :DWORD,:DWORD,:DWORD
            dist3 PROTO :DWORD,:DWORD
            dodecahedron PROTO :DWORD,:DWORD,:DWORD,:DWORD
            dot3 PROTO :DWORD,:DWORD
            doughnut PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            drawbox PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            drawtriangle PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            error PROTO :DWORD
            findList PROTO :DWORD,:DWORD,:DWORD
            gluCylinder PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            gluNewQuadric PROTO
            gluQuadricDrawStyle PROTO :DWORD,:DWORD
            gluQuadricNormals PROTO :DWORD,:DWORD
            gluSphere PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            icosahedron PROTO :DWORD,:DWORD,:DWORD,:DWORD
            identifymat3 PROTO :DWORD
            initdodec PROTO
            length3 PROTO :DWORD
            m_popmatrix PROTO
            m_pushmatrix PROTO
            m_scale PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            m_translate PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            m_xformpt PROTO :DWORD,:DWORD,:DWORD,:DWORD
            m_xformptonly PROTO :DWORD,:DWORD
            makeModelPtr PROTO :DWORD,:DWORD,:DWORD
            normalize PROTO :DWORD
            octahedron PROTO :DWORD,:DWORD,:DWORD,:DWORD
            pentagon PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            perpnorm PROTO :DWORD,:DWORD,:DWORD,:DWORD
            recorditem PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            samepoint PROTO :DWORD,:DWORD
            scalarmult PROTO :DWORD,:DWORD,:DWORD,:DWORD
            seterrorfunc PROTO :DWORD
            solidTeapot PROTO :DWORD,:DWORD,:DWORD
            subdivide PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
            tetrahedron PROTO :DWORD,:DWORD,:DWORD,:DWORD
            tkCloseWindow PROTO
            tkCreateBitmapFont PROTO :DWORD
            tkCreateFilledFont PROTO :DWORD
            tkCreateOutlineFont PROTO :DWORD
            tkCreateStrokeFont PROTO :DWORD
            tkDIBImageLoad PROTO :DWORD
            tkDisplayFunc PROTO :DWORD
            tkDrawStr PROTO :DWORD,:DWORD
            tkErrorPopups PROTO :DWORD
            tkExec PROTO
            tkExposeFunc PROTO :DWORD
            tkGetColorMapSize PROTO
            tkGetDisplayMode PROTO
            tkGetDisplayModeID PROTO
            tkGetDisplayModePolicy PROTO
            tkGetHDC PROTO
            tkGetHRC PROTO
            tkGetHWND PROTO
            tkGetMouseLoc PROTO :DWORD,:DWORD
            tkIdleFunc PROTO :DWORD
            tkInitDisplayMode PROTO :DWORD
            tkInitDisplayModeID PROTO :DWORD
            tkInitDisplayModePolicy PROTO :DWORD
            tkInitPosition PROTO :DWORD,:DWORD,:DWORD,:DWORD
            tkInitWindow PROTO :DWORD
            tkKeyDownFunc PROTO :DWORD
            tkMouseDownFunc PROTO :DWORD
            tkMouseMoveFunc PROTO :DWORD
            tkMouseUpFunc PROTO :DWORD
            tkQuit PROTO
            tkRGBImageLoad PROTO :DWORD
            tkReshapeFunc PROTO :DWORD
            tkSetFogRamp PROTO :DWORD,:DWORD
            tkSetGreyRamp PROTO
            tkSetOneColor PROTO :DWORD,:DWORD,:DWORD,:DWORD
            tkSetRGBMap PROTO :DWORD,:DWORD
            tkSwapBuffers PROTO
            tkWndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
            wireTeapot PROTO :DWORD,:DWORD,:DWORD
            xformvec3 PROTO :DWORD,:DWORD,:DWORD


            ; GL types                Vu dans ... Exapmles OPENG fasmW

            GLenum	   fix dd
            GLboolean  fix db
            GLbitfield fix dd
            GLbyte	   fix db
            GLshort    fix dw
            GLint	   fix dd
            GLsizei    fix dd
            GLubyte    fix db
            GLushort   fix dw
            GLuint	   fix dd
            GLfloat    fix dd
            GLclampf   fix dd
            GLdouble   fix dq
            GLclampd   fix dq

            ; GL errors

            GL_NO_ERROR	     = 0
            GL_INVALID_ENUM      = $0500
            GL_INVALID_VALUE     = $0501
            GL_INVALID_OPERATION = $0502
            GL_STACK_OVERFLOW    = $0503
            GL_STACK_UNDERFLOW   = $0504
            GL_OUT_OF_MEMORY     = $0505

            ; Attribute bits

            GL_CURRENT_BIT	       = $00000001
            GL_POINT_BIT	       = $00000002
            GL_LINE_BIT	       = $00000004
            GL_POLYGON_BIT	       = $00000008
            GL_POLYGON_STIPPLE_BIT = $00000010
            GL_PIXEL_MODE_BIT      = $00000020
            GL_LIGHTING_BIT        = $00000040
            GL_FOG_BIT	       = $00000080
            GL_DEPTH_BUFFER_BIT    = $00000100
            GL_ACCUM_BUFFER_BIT    = $00000200
            GL_STENCIL_BUFFER_BIT  = $00000400
            GL_VIEWPORT_BIT        = $00000800
            GL_TRANSFORM_BIT       = $00001000
            GL_ENABLE_BIT	       = $00002000
            GL_COLOR_BUFFER_BIT    = $00004000
            GL_HINT_BIT	       = $00008000
            GL_EVAL_BIT	       = $00010000
            GL_LIST_BIT	       = $00020000
            GL_TEXTURE_BIT	       = $00040000
            GL_SCISSOR_BIT	       = $00080000
            GL_ALL_ATTRIB_BITS     = $000FFFFF

            ; Client attribute bits

            GL_CLIENT_PIXEL_STORE_BIT  = $00000001
            GL_CLIENT_VERTEX_ARRAY_BIT = $00000002
            GL_CLIENT_ALL_ATTRIB_BITS  = $FFFFFFFF

            ; Boolean values

            GL_FALSE = 0
            GL_TRUE  = 1

            ; Primitives

            GL_POINTS	  = $0000
            GL_LINES	  = $0001
            GL_LINE_LOOP	  = $0002
            GL_LINE_STRIP	  = $0003
            GL_TRIANGLES	  = $0004
            GL_TRIANGLE_STRIP = $0005
            GL_TRIANGLE_FAN   = $0006
            GL_QUADS	  = $0007
            GL_QUAD_STRIP	  = $0008
            GL_POLYGON	  = $0009

            ; Blending

            GL_ZERO 	       = 0
            GL_ONE		       = 1
            GL_SRC_COLOR	       = $0300
            GL_ONE_MINUS_SRC_COLOR = $0301
            GL_SRC_ALPHA	       = $0302
            GL_ONE_MINUS_SRC_ALPHA = $0303
            GL_DST_ALPHA	       = $0304
            GL_ONE_MINUS_DST_ALPHA = $0305
            GL_DST_COLOR	       = $0306
            GL_ONE_MINUS_DST_COLOR = $0307
            GL_SRC_ALPHA_SATURATE  = $0308
            GL_BLEND_DST	       = $0BE0
            GL_BLEND_SRC	       = $0BE1
            GL_BLEND	       = $0BE2

            ; Blending (GL 1.2 ARB imaging)

            GL_BLEND_COLOR		    = $8005
            GL_CONSTANT_COLOR	    = $8001
            GL_ONE_MINUS_CONSTANT_COLOR = $8002
            GL_CONSTANT_ALPHA	    = $8003
            GL_ONE_MINUS_CONSTANT_ALPHA = $8004
            GL_FUNC_ADD		    = $8006
            GL_MIN			    = $8007
            GL_MAX			    = $8008
            GL_FUNC_SUBTRACT	    = $800A
            GL_FUNC_REVERSE_SUBTRACT    = $800B

            ; Color table (GL 1.2 ARB imaging)

            GL_COLOR_TABLE			       = $80D0
            GL_POST_CONVOLUTION_COLOR_TABLE        = $80D1
            GL_POST_COLOR_MATRIX_COLOR_TABLE       = $80D2
            GL_PROXY_COLOR_TABLE		       = $80D3
            GL_PROXY_POST_CONVOLUTION_COLOR_TABLE  = $80D4
            GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE = $80D5
            GL_COLOR_TABLE_SCALE		       = $80D6
            GL_COLOR_TABLE_BIAS		       = $80D7
            GL_COLOR_TABLE_FORMAT		       = $80D8
            GL_COLOR_TABLE_WIDTH		       = $80D9
            GL_COLOR_TABLE_RED_SIZE 	       = $80DA
            GL_COLOR_TABLE_GREEN_SIZE	       = $80DB
            GL_COLOR_TABLE_BLUE_SIZE	       = $80DC
            GL_COLOR_TABLE_ALPHA_SIZE	       = $80DD
            GL_COLOR_TABLE_LUMINANCE_SIZE	       = $80DE
            GL_COLOR_TABLE_INTENSITY_SIZE	       = $80DF

            ; Convolutions (GL 1.2 ARB imaging)

            GL_CONVOLUTION_1D		= $8010
            GL_CONVOLUTION_2D		= $8011
            GL_SEPARABLE_2D 		= $8012
            GL_CONVOLUTION_BORDER_MODE	= $8013
            GL_CONVOLUTION_FILTER_SCALE	= $8014
            GL_CONVOLUTION_FILTER_BIAS	= $8015
            GL_REDUCE			= $8016
            GL_CONVOLUTION_FORMAT		= $8017
            GL_CONVOLUTION_WIDTH		= $8018
            GL_CONVOLUTION_HEIGHT		= $8019
            GL_MAX_CONVOLUTION_WIDTH	= $801A
            GL_MAX_CONVOLUTION_HEIGHT	= $801B
            GL_POST_CONVOLUTION_RED_SCALE	= $801C
            GL_POST_CONVOLUTION_GREEN_SCALE = $801D
            GL_POST_CONVOLUTION_BLUE_SCALE	= $801E
            GL_POST_CONVOLUTION_ALPHA_SCALE = $801F
            GL_POST_CONVOLUTION_RED_BIAS	= $8020
            GL_POST_CONVOLUTION_GREEN_BIAS	= $8021
            GL_POST_CONVOLUTION_BLUE_BIAS	= $8022
            GL_POST_CONVOLUTION_ALPHA_BIAS	= $8023

            ; Histogram (GL 1.2 ARB imaging)

            GL_HISTOGRAM		    = $8024
            GL_PROXY_HISTOGRAM	    = $8025
            GL_HISTOGRAM_WIDTH	    = $8026
            GL_HISTOGRAM_FORMAT	    = $8027
            GL_HISTOGRAM_RED_SIZE	    = $8028
            GL_HISTOGRAM_GREEN_SIZE     = $8029
            GL_HISTOGRAM_BLUE_SIZE	    = $802A
            GL_HISTOGRAM_ALPHA_SIZE     = $802B
            GL_HISTOGRAM_LUMINANCE_SIZE = $802C
            GL_HISTOGRAM_SINK	    = $802D
            GL_MINMAX		    = $802E
            GL_MINMAX_FORMAT	    = $802F
            GL_MINMAX_SINK		    = $8030

            ; Buffers

            GL_NONE 	  = 0
            GL_FRONT_LEFT	  = $0400
            GL_FRONT_RIGHT	  = $0401
            GL_BACK_LEFT	  = $0402
            GL_BACK_RIGHT	  = $0403
            GL_FRONT	  = $0404
            GL_BACK 	  = $0405
            GL_LEFT 	  = $0406
            GL_RIGHT	  = $0407
            GL_FRONT_AND_BACK = $0408
            GL_AUX0 	  = $0409
            GL_AUX1 	  = $040A
            GL_AUX2 	  = $040B
            GL_AUX3 	  = $040C
            GL_AUX_BUFFERS	  = $0C00
            GL_DRAW_BUFFER	  = $0C01
            GL_READ_BUFFER	  = $0C02
            GL_DOUBLEBUFFER   = $0C32
            GL_STEREO	  = $0C33

            ; Depth buffer

            GL_DEPTH_RANGE	     = $0B70
            GL_DEPTH_TEST	     = $0B71
            GL_DEPTH_WRITEMASK   = $0B72
            GL_DEPTH_CLEAR_VALUE = $0B73
            GL_DEPTH_FUNC	     = $0B74
            GL_NEVER	     = $0200
            GL_LESS 	     = $0201
            GL_EQUAL	     = $0202
            GL_LEQUAL	     = $0203
            GL_GREATER	     = $0204
            GL_NOTEQUAL	     = $0205
            GL_GEQUAL	     = $0206
            GL_ALWAYS	     = $0207

            ; Accumulation buffer

            GL_ACCUM	     = $0100
            GL_LOAD 	     = $0101
            GL_RETURN	     = $0102
            GL_MULT 	     = $0103
            GL_ADD		     = $0104
            GL_ACCUM_CLEAR_VALUE = $0B80

            ; Feedback buffer

            GL_FEEDBACK_BUFFER_POINTER = $0DF0
            GL_FEEDBACK_BUFFER_SIZE    = $0DF1
            GL_FEEDBACK_BUFFER_TYPE    = $0DF2

            ; Feedback types

            GL_2D		    = $0600
            GL_3D		    = $0601
            GL_3D_COLOR	    = $0602
            GL_3D_COLOR_TEXTURE = $0603
            GL_4D_COLOR_TEXTURE = $0604

            ; Feedback tokens

            GL_PASS_THROUGH_TOKEN = $0700
            GL_POINT_TOKEN	      = $0701
            GL_LINE_TOKEN	      = $0702
            GL_POLYGON_TOKEN      = $0703
            GL_BITMAP_TOKEN       = $0704
            GL_DRAW_PIXEL_TOKEN   = $0705
            GL_COPY_PIXEL_TOKEN   = $0706
            GL_LINE_RESET_TOKEN   = $0707

            ; Fog

            GL_EXP	       = $0800
            GL_EXP2        = $0801
            GL_FOG	       = $0B60
            GL_FOG_INDEX   = $0B61
            GL_FOG_DENSITY = $0B62
            GL_FOG_START   = $0B63
            GL_FOG_END     = $0B64
            GL_FOG_MODE    = $0B65
            GL_FOG_COLOR   = $0B66

            ; Pixel mode, transfer

            GL_PIXEL_MAP_I_TO_I = $0C70
            GL_PIXEL_MAP_S_TO_S = $0C71
            GL_PIXEL_MAP_I_TO_R = $0C72
            GL_PIXEL_MAP_I_TO_G = $0C73
            GL_PIXEL_MAP_I_TO_B = $0C74
            GL_PIXEL_MAP_I_TO_A = $0C75
            GL_PIXEL_MAP_R_TO_R = $0C76
            GL_PIXEL_MAP_G_TO_G = $0C77
            GL_PIXEL_MAP_B_TO_B = $0C78
            GL_PIXEL_MAP_A_TO_A = $0C79

            ; Vertex arrays

            GL_VERTEX_ARRAY_POINTER        = $808E
            GL_NORMAL_ARRAY_POINTER        = $808F
            GL_COLOR_ARRAY_POINTER	       = $8090
            GL_INDEX_ARRAY_POINTER	       = $8091
            GL_TEXTURE_COORD_ARRAY_POINTER = $8092
            GL_EDGE_FLAG_ARRAY_POINTER     = $8093

            ; Stenciling

            GL_STENCIL_TEST 	   = $0B90
            GL_STENCIL_CLEAR_VALUE	   = $0B91
            GL_STENCIL_FUNC 	   = $0B92
            GL_STENCIL_VALUE_MASK	   = $0B93
            GL_STENCIL_FAIL 	   = $0B94
            GL_STENCIL_PASS_DEPTH_FAIL = $0B95
            GL_STENCIL_PASS_DEPTH_PASS = $0B96
            GL_STENCIL_REF		   = $0B97
            GL_STENCIL_WRITEMASK	   = $0B98
            GL_KEEP 		   = $1E00
            GL_REPLACE		   = $1E01
            GL_INCR 		   = $1E02
            GL_DECR 		   = $1E03

            ; Color material

            GL_COLOR_MATERIAL_FACE	    = $0B55
            GL_COLOR_MATERIAL_PARAMETER = $0B56
            GL_COLOR_MATERIAL	    = $0B57

            ; Points

            GL_POINT_SMOOTH 	  = $0B10
            GL_POINT_SIZE		  = $0B11
            GL_POINT_SIZE_RANGE	  = $0B12
            GL_POINT_SIZE_GRANULARITY = $0B13

            ; Lines

            GL_LINE_SMOOTH		  = $0B20
            GL_LINE_WIDTH		  = $0B21
            GL_LINE_WIDTH_RANGE	  = $0B22
            GL_LINE_WIDTH_GRANULARITY = $0B23
            GL_LINE_STIPPLE 	  = $0B24
            GL_LINE_STIPPLE_PATTERN   = $0B25
            GL_LINE_STIPPLE_REPEAT	  = $0B26

            ; Polygons

            GL_POLYGON_MODE    = $0B40
            GL_POLYGON_SMOOTH  = $0B41
            GL_POLYGON_STIPPLE = $0B42
            GL_EDGE_FLAG	   = $0B43
            GL_CULL_FACE	   = $0B44
            GL_CULL_FACE_MODE  = $0B45
            GL_FRONT_FACE	   = $0B46
            GL_CW		   = $0900
            GL_CCW		   = $0901
            GL_POINT	   = $1B00
            GL_LINE 	   = $1B01
            GL_FILL 	   = $1B02

            ; Display lists

            GL_LIST_MODE	       = $0B30
            GL_LIST_BASE	       = $0B32
            GL_LIST_INDEX	       = $0B33
            GL_COMPILE	       = $1300
            GL_COMPILE_AND_EXECUTE = $1301

            ; Lighting

            GL_LIGHTING		     = $0B50
            GL_LIGHT_MODEL_LOCAL_VIEWER  = $0B51
            GL_LIGHT_MODEL_TWO_SIDE      = $0B52
            GL_LIGHT_MODEL_AMBIENT	     = $0B53
            GL_LIGHT_MODEL_COLOR_CONTROL = $81F8
            GL_SHADE_MODEL		     = $0B54
            GL_NORMALIZE		     = $0BA1
            GL_AMBIENT		     = $1200
            GL_DIFFUSE		     = $1201
            GL_SPECULAR		     = $1202
            GL_POSITION		     = $1203
            GL_SPOT_DIRECTION	     = $1204
            GL_SPOT_EXPONENT	     = $1205
            GL_SPOT_CUTOFF		     = $1206
            GL_CONSTANT_ATTENUATION      = $1207
            GL_LINEAR_ATTENUATION	     = $1208
            GL_QUADRATIC_ATTENUATION     = $1209
            GL_EMISSION		     = $1600
            GL_SHININESS		     = $1601
            GL_AMBIENT_AND_DIFFUSE	     = $1602
            GL_COLOR_INDEXES	     = $1603
            GL_FLAT 		     = $1D00
            GL_SMOOTH		     = $1D01
            GL_LIGHT0		     = $4000
            GL_LIGHT1		     = $4001
            GL_LIGHT2		     = $4002
            GL_LIGHT3		     = $4003
            GL_LIGHT4		     = $4004
            GL_LIGHT5		     = $4005
            GL_LIGHT6		     = $4006
            GL_LIGHT7		     = $4007

            ; Matrix modes

            GL_MATRIX_MODE = $0BA0
            GL_MODELVIEW   = $1700
            GL_PROJECTION  = $1701
            GL_TEXTURE     = $1702

            ; Gets

            GL_CURRENT_COLOR		 = $0B00
            GL_CURRENT_INDEX		 = $0B01
            GL_CURRENT_NORMAL		 = $0B02
            GL_CURRENT_TEXTURE_COORDS	 = $0B03
            GL_CURRENT_RASTER_COLOR 	 = $0B04
            GL_CURRENT_RASTER_INDEX 	 = $0B05
            GL_CURRENT_RASTER_TEXTURE_COORDS = $0B06
            GL_CURRENT_RASTER_POSITION	 = $0B07
            GL_CURRENT_RASTER_POSITION_VALID = $0B08
            GL_CURRENT_RASTER_DISTANCE	 = $0B09
            GL_MAX_LIST_NESTING		 = $0B31
            GL_VIEWPORT			 = $0BA2
            GL_MODELVIEW_STACK_DEPTH	 = $0BA3
            GL_PROJECTION_STACK_DEPTH	 = $0BA4
            GL_TEXTURE_STACK_DEPTH		 = $0BA5
            GL_MODELVIEW_MATRIX		 = $0BA6
            GL_PROJECTION_MATRIX		 = $0BA7
            GL_TEXTURE_MATRIX		 = $0BA8
            GL_ATTRIB_STACK_DEPTH		 = $0BB0
            GL_CLIENT_ATTRIB_STACK_DEPTH	 = $0BB1
            GL_SINGLE_COLOR 		 = $81F9
            GL_SEPARATE_SPECULAR_COLOR	 = $81FA

            ; Alpha testing

            GL_ALPHA_TEST	   = $0BC0
            GL_ALPHA_TEST_FUNC = $0BC1
            GL_ALPHA_TEST_REF  = $0BC2

            GL_LOGIC_OP_MODE		 = $0BF0
            GL_INDEX_LOGIC_OP		 = $0BF1
            GL_LOGIC_OP			 = $0BF1
            GL_COLOR_LOGIC_OP		 = $0BF2
            GL_SCISSOR_BOX			 = $0C10
            GL_SCISSOR_TEST 		 = $0C11
            GL_INDEX_CLEAR_VALUE		 = $0C20
            GL_INDEX_WRITEMASK		 = $0C21
            GL_COLOR_CLEAR_VALUE		 = $0C22
            GL_COLOR_WRITEMASK		 = $0C23
            GL_INDEX_MODE			 = $0C30
            GL_RGBA_MODE			 = $0C31
            GL_RENDER_MODE			 = $0C40
            GL_PERSPECTIVE_CORRECTION_HINT	 = $0C50
            GL_POINT_SMOOTH_HINT		 = $0C51
            GL_LINE_SMOOTH_HINT		 = $0C52
            GL_POLYGON_SMOOTH_HINT		 = $0C53
            GL_FOG_HINT			 = $0C54
            GL_TEXTURE_GEN_S		 = $0C60
            GL_TEXTURE_GEN_T		 = $0C61
            GL_TEXTURE_GEN_R		 = $0C62
            GL_TEXTURE_GEN_Q		 = $0C63
            GL_PIXEL_MAP_I_TO_I_SIZE	 = $0CB0
            GL_PIXEL_MAP_S_TO_S_SIZE	 = $0CB1
            GL_PIXEL_MAP_I_TO_R_SIZE	 = $0CB2
            GL_PIXEL_MAP_I_TO_G_SIZE	 = $0CB3
            GL_PIXEL_MAP_I_TO_B_SIZE	 = $0CB4
            GL_PIXEL_MAP_I_TO_A_SIZE	 = $0CB5
            GL_PIXEL_MAP_R_TO_R_SIZE	 = $0CB6
            GL_PIXEL_MAP_G_TO_G_SIZE	 = $0CB7
            GL_PIXEL_MAP_B_TO_B_SIZE	 = $0CB8
            GL_PIXEL_MAP_A_TO_A_SIZE	 = $0CB9
            GL_UNPACK_SWAP_BYTES		 = $0CF0
            GL_UNPACK_LSB_FIRST		 = $0CF1
            GL_UNPACK_ROW_LENGTH		 = $0CF2
            GL_UNPACK_SKIP_ROWS		 = $0CF3
            GL_UNPACK_SKIP_PIXELS		 = $0CF4
            GL_UNPACK_ALIGNMENT		 = $0CF5
            GL_PACK_SWAP_BYTES		 = $0D00
            GL_PACK_LSB_FIRST		 = $0D01
            GL_PACK_ROW_LENGTH		 = $0D02
            GL_PACK_SKIP_ROWS		 = $0D03
            GL_PACK_SKIP_PIXELS		 = $0D04
            GL_PACK_ALIGNMENT		 = $0D05
            GL_PACK_SKIP_IMAGES		 = $806B
            GL_PACK_IMAGE_HEIGHT		 = $806C
            GL_UNPACK_SKIP_IMAGES		 = $806D
            GL_UNPACK_IMAGE_HEIGHT		 = $806E
            GL_MAP_COLOR			 = $0D10
            GL_MAP_STENCIL			 = $0D11
            GL_INDEX_SHIFT			 = $0D12
            GL_INDEX_OFFSET 		 = $0D13
            GL_RED_SCALE			 = $0D14
            GL_RED_BIAS			 = $0D15
            GL_ZOOM_X			 = $0D16
            GL_ZOOM_Y			 = $0D17
            GL_GREEN_SCALE			 = $0D18
            GL_GREEN_BIAS			 = $0D19
            GL_BLUE_SCALE			 = $0D1A
            GL_BLUE_BIAS			 = $0D1B
            GL_ALPHA_SCALE			 = $0D1C
            GL_ALPHA_BIAS			 = $0D1D
            GL_DEPTH_SCALE			 = $0D1E
            GL_DEPTH_BIAS			 = $0D1F
            GL_MAX_EVAL_ORDER		 = $0D30
            GL_MAX_LIGHTS			 = $0D31
            GL_MAX_CLIP_PLANES		 = $0D32
            GL_MAX_TEXTURE_SIZE		 = $0D33
            GL_MAX_3D_TEXTURE_SIZE		 = $8073
            GL_MAX_PIXEL_MAP_TABLE		 = $0D34
            GL_MAX_ATTRIB_STACK_DEPTH	 = $0D35
            GL_MAX_MODELVIEW_STACK_DEPTH	 = $0D36
            GL_MAX_NAME_STACK_DEPTH 	 = $0D37
            GL_MAX_PROJECTION_STACK_DEPTH	 = $0D38
            GL_MAX_TEXTURE_STACK_DEPTH	 = $0D39
            GL_MAX_VIEWPORT_DIMS		 = $0D3A
            GL_MAX_CLIENT_ATTRIB_STACK_DEPTH = $0D3B
            GL_MAX_ELEMENTS_VERTICES	 = $80E8
            GL_MAX_ELEMENTS_INDICES 	 = $80E9
            GL_RESCALE_NORMAL		 = $803A
            GL_SUBPIXEL_BITS		 = $0D50
            GL_INDEX_BITS			 = $0D51
            GL_RED_BITS			 = $0D52
            GL_GREEN_BITS			 = $0D53
            GL_BLUE_BITS			 = $0D54
            GL_ALPHA_BITS			 = $0D55
            GL_DEPTH_BITS			 = $0D56
            GL_STENCIL_BITS 		 = $0D57
            GL_ACCUM_RED_BITS		 = $0D58
            GL_ACCUM_GREEN_BITS		 = $0D59
            GL_ACCUM_BLUE_BITS		 = $0D5A
            GL_ACCUM_ALPHA_BITS		 = $0D5B
            GL_NAME_STACK_DEPTH		 = $0D70
            GL_AUTO_NORMAL			 = $0D80
            GL_MAP1_COLOR_4 		 = $0D90
            GL_MAP1_INDEX			 = $0D91
            GL_MAP1_NORMAL			 = $0D92
            GL_MAP1_TEXTURE_COORD_1 	 = $0D93
            GL_MAP1_TEXTURE_COORD_2 	 = $0D94
            GL_MAP1_TEXTURE_COORD_3 	 = $0D95
            GL_MAP1_TEXTURE_COORD_4 	 = $0D96
            GL_MAP1_VERTEX_3		 = $0D97
            GL_MAP1_VERTEX_4		 = $0D98
            GL_MAP2_COLOR_4 		 = $0DB0
            GL_MAP2_INDEX			 = $0DB1
            GL_MAP2_NORMAL			 = $0DB2
            GL_MAP2_TEXTURE_COORD_1 	 = $0DB3
            GL_MAP2_TEXTURE_COORD_2 	 = $0DB4
            GL_MAP2_TEXTURE_COORD_3 	 = $0DB5
            GL_MAP2_TEXTURE_COORD_4 	 = $0DB6
            GL_MAP2_VERTEX_3		 = $0DB7
            GL_MAP2_VERTEX_4		 = $0DB8
            GL_MAP1_GRID_DOMAIN		 = $0DD0
            GL_MAP1_GRID_SEGMENTS		 = $0DD1
            GL_MAP2_GRID_DOMAIN		 = $0DD2
            GL_MAP2_GRID_SEGMENTS		 = $0DD3
            GL_TEXTURE_1D			 = $0DE0
            GL_TEXTURE_2D			 = $0DE1
            GL_TEXTURE_3D			 = $806F
            GL_SELECTION_BUFFER_POINTER	 = $0DF3
            GL_SELECTION_BUFFER_SIZE	 = $0DF4
            GL_POLYGON_OFFSET_UNITS 	 = $2A00
            GL_POLYGON_OFFSET_POINT 	 = $2A01
            GL_POLYGON_OFFSET_LINE		 = $2A02
            GL_POLYGON_OFFSET_FILL		 = $8037
            GL_POLYGON_OFFSET_FACTOR	 = $8038
            GL_TEXTURE_BINDING_1D		 = $8068
            GL_TEXTURE_BINDING_2D		 = $8069
            GL_VERTEX_ARRAY 		 = $8074
            GL_NORMAL_ARRAY 		 = $8075
            GL_COLOR_ARRAY			 = $8076
            GL_INDEX_ARRAY			 = $8077
            GL_TEXTURE_COORD_ARRAY		 = $8078
            GL_EDGE_FLAG_ARRAY		 = $8079
            GL_VERTEX_ARRAY_SIZE		 = $807A
            GL_VERTEX_ARRAY_TYPE		 = $807B
            GL_VERTEX_ARRAY_STRIDE		 = $807C
            GL_NORMAL_ARRAY_TYPE		 = $807E
            GL_NORMAL_ARRAY_STRIDE		 = $807F
            GL_COLOR_ARRAY_SIZE		 = $8081
            GL_COLOR_ARRAY_TYPE		 = $8082
            GL_COLOR_ARRAY_STRIDE		 = $8083
            GL_INDEX_ARRAY_TYPE		 = $8085
            GL_INDEX_ARRAY_STRIDE		 = $8086
            GL_TEXTURE_COORD_ARRAY_SIZE	 = $8088
            GL_TEXTURE_COORD_ARRAY_TYPE	 = $8089
            GL_TEXTURE_COORD_ARRAY_STRIDE	 = $808A
            GL_EDGE_FLAG_ARRAY_STRIDE	 = $808C
            GL_COLOR_MATRIX 		 = $80B1
            GL_COLOR_MATRIX_STACK_DEPTH	 = $80B2
            GL_MAX_COLOR_MATRIX_STACK_DEPTH  = $80B3
            GL_POST_COLOR_MATRIX_RED_SCALE	 = $80B4
            GL_POST_COLOR_MATRIX_GREEN_SCALE = $80B5
            GL_POST_COLOR_MATRIX_BLUE_SCALE  = $80B6
            GL_POST_COLOR_MATRIX_ALPHA_SCALE = $80B7
            GL_POST_COLOR_MATRIX_RED_BIAS	 = $80B8
            GL_POST_COLOR_MATRIX_GREEN_BIAS  = $80B9
            GL_POST_COLOR_MATRIX_BLUE_BIAS	 = $80BA
            GL_POST_COLOR_MATRIX_ALPHA_BIAS  = $80BB

            ; Evaluators

            GL_COEFF  = $0A00
            GL_ORDER  = $0A01
            GL_DOMAIN = $0A02

            ; Texture mapping

            GL_TEXTURE_WIDTH	   = $1000
            GL_TEXTURE_HEIGHT	   = $1001
            GL_TEXTURE_INTERNAL_FORMAT = $1003
            GL_TEXTURE_COMPONENTS	   = $1003
            GL_TEXTURE_BORDER_COLOR    = $1004
            GL_TEXTURE_BORDER	   = $1005
            GL_TEXTURE_RED_SIZE	   = $805C
            GL_TEXTURE_GREEN_SIZE	   = $805D
            GL_TEXTURE_BLUE_SIZE	   = $805E
            GL_TEXTURE_ALPHA_SIZE	   = $805F
            GL_TEXTURE_LUMINANCE_SIZE  = $8060
            GL_TEXTURE_INTENSITY_SIZE  = $8061
            GL_TEXTURE_PRIORITY	   = $8066
            GL_TEXTURE_RESIDENT	   = $8067
            GL_BGR			   = $80E0
            GL_BGRA 		   = $80E1
            GL_S			   = $2000
            GL_T			   = $2001
            GL_R			   = $2002
            GL_Q			   = $2003
            GL_MODULATE		   = $2100
            GL_DECAL		   = $2101
            GL_TEXTURE_ENV_MODE	   = $2200
            GL_TEXTURE_ENV_COLOR	   = $2201
            GL_TEXTURE_ENV		   = $2300
            GL_EYE_LINEAR		   = $2400
            GL_OBJECT_LINEAR	   = $2401
            GL_SPHERE_MAP		   = $2402
            GL_TEXTURE_GEN_MODE	   = $2500
            GL_OBJECT_PLANE 	   = $2501
            GL_EYE_PLANE		   = $2502
            GL_NEAREST		   = $2600
            GL_LINEAR		   = $2601
            GL_NEAREST_MIPMAP_NEAREST  = $2700
            GL_LINEAR_MIPMAP_NEAREST   = $2701
            GL_NEAREST_MIPMAP_LINEAR   = $2702
            GL_LINEAR_MIPMAP_LINEAR    = $2703
            GL_TEXTURE_MAG_FILTER	   = $2800
            GL_TEXTURE_MIN_FILTER	   = $2801
            GL_TEXTURE_WRAP_R	   = $8072
            GL_TEXTURE_WRAP_S	   = $2802
            GL_TEXTURE_WRAP_T	   = $2803
            GL_CLAMP_TO_EDGE	   = $812F
            GL_TEXTURE_MIN_LOD	   = $813A
            GL_TEXTURE_MAX_LOD	   = $813B
            GL_TEXTURE_BASE_LEVEL	   = $813C
            GL_TEXTURE_MAX_LEVEL	   = $813D
            GL_TEXTURE_DEPTH	   = $8071
            GL_PROXY_TEXTURE_1D	   = $8063
            GL_PROXY_TEXTURE_2D	   = $8064
            GL_PROXY_TEXTURE_3D	   = $8070
            GL_CLAMP		   = $2900
            GL_REPEAT		   = $2901

            ; Hints

            GL_DONT_CARE = $1100
            GL_FASTEST   = $1101
            GL_NICEST    = $1102

            ; Data types

            GL_BYTE 	  = $1400
            GL_UNSIGNED_BYTE  = $1401
            GL_SHORT	  = $1402
            GL_UNSIGNED_SHORT = $1403
            GL_INT		  = $1404
            GL_UNSIGNED_INT   = $1405
            GL_FLOAT	  = $1406
            GL_2_BYTES	  = $1407
            GL_3_BYTES	  = $1408
            GL_4_BYTES	  = $1409
            GL_DOUBLE	  = $140A
            GL_DOUBLE_EXT	  = $140A

            ; Logic operations

            GL_CLEAR	 = $1500
            GL_AND		 = $1501
            GL_AND_REVERSE	 = $1502
            GL_COPY 	 = $1503
            GL_AND_INVERTED  = $1504
            GL_NOOP 	 = $1505
            GL_XOR		 = $1506
            GL_OR		 = $1507
            GL_NOR		 = $1508
            GL_EQUIV	 = $1509
            GL_INVERT	 = $150A
            GL_OR_REVERSE	 = $150B
            GL_COPY_INVERTED = $150C
            GL_OR_INVERTED	 = $150D
            GL_NAND 	 = $150E
            GL_SET		 = $150F

            ; PixelCopyType

            GL_COLOR   = $1800
            GL_DEPTH   = $1801
            GL_STENCIL = $1802

            ; Pixel formats

            GL_COLOR_INDEX	   = $1900
            GL_STENCIL_INDEX   = $1901
            GL_DEPTH_COMPONENT = $1902
            GL_RED		   = $1903
            GL_GREEN	   = $1904
            GL_BLUE 	   = $1905
            GL_ALPHA	   = $1906
            GL_RGB		   = $1907
            GL_RGBA 	   = $1908
            GL_LUMINANCE	   = $1909
            GL_LUMINANCE_ALPHA = $190A

            ; Pixel type

            GL_BITMAP = $1A00

            ; Rendering modes

            GL_RENDER   = $1C00
            GL_FEEDBACK = $1C01
            GL_SELECT   = $1C02

            ; Implementation strings

            GL_VENDOR     = $1F00
            GL_RENDERER   = $1F01
            GL_VERSION    = $1F02
            GL_EXTENSIONS = $1F03

            ; Pixel formats

            GL_R3_G3_B2		    = $2A10
            GL_ALPHA4		    = $803B
            GL_ALPHA8		    = $803C
            GL_ALPHA12		    = $803D
            GL_ALPHA16		    = $803E
            GL_LUMINANCE4		    = $803F
            GL_LUMINANCE8		    = $8040
            GL_LUMINANCE12		    = $8041
            GL_LUMINANCE16		    = $8042
            GL_LUMINANCE4_ALPHA4	    = $8043
            GL_LUMINANCE6_ALPHA2	    = $8044
            GL_LUMINANCE8_ALPHA8	    = $8045
            GL_LUMINANCE12_ALPHA4	    = $8046
            GL_LUMINANCE12_ALPHA12	    = $8047
            GL_LUMINANCE16_ALPHA16	    = $8048
            GL_INTENSITY		    = $8049
            GL_INTENSITY4		    = $804A
            GL_INTENSITY8		    = $804B
            GL_INTENSITY12		    = $804C
            GL_INTENSITY16		    = $804D
            GL_RGB4 		    = $804F
            GL_RGB5 		    = $8050
            GL_RGB8 		    = $8051
            GL_RGB10		    = $8052
            GL_RGB12		    = $8053
            GL_RGB16		    = $8054
            GL_RGBA2		    = $8055
            GL_RGBA4		    = $8056
            GL_RGB5_A1		    = $8057
            GL_RGBA8		    = $8058
            GL_RGB10_A2		    = $8059
            GL_RGBA12		    = $805A
            GL_RGBA16		    = $805B
            UNSIGNED_BYTE_3_3_2	    = $8032
            UNSIGNED_BYTE_2_3_3_REV     = $8362
            UNSIGNED_SHORT_5_6_5	    = $8363
            UNSIGNED_SHORT_5_6_5_REV    = $8364
            UNSIGNED_SHORT_4_4_4_4	    = $8033
            UNSIGNED_SHORT_4_4_4_4_REV  = $8365
            UNSIGNED_SHORT_5_5_5_1	    = $8034
            UNSIGNED_SHORT_1_5_5_5_REV  = $8366
            UNSIGNED_INT_8_8_8_8	    = $8035
            UNSIGNED_INT_8_8_8_8_REV    = $8367
            UNSIGNED_INT_10_10_10_2     = $8036
            UNSIGNED_INT_2_10_10_10_REV = $8368

            ; Interleaved arrays formats

            GL_V2F		   = $2A20
            GL_V3F		   = $2A21
            GL_C4UB_V2F	   = $2A22
            GL_C4UB_V3F	   = $2A23
            GL_C3F_V3F	   = $2A24
            GL_N3F_V3F	   = $2A25
            GL_C4F_N3F_V3F	   = $2A26
            GL_T2F_V3F	   = $2A27
            GL_T4F_V4F	   = $2A28
            GL_T2F_C4UB_V3F    = $2A29
            GL_T2F_C3F_V3F	   = $2A2A
            GL_T2F_N3F_V3F	   = $2A2B
            GL_T2F_C4F_N3F_V3F = $2A2C
            GL_T4F_C4F_N3F_V4F = $2A2D

            ; Clip planes

            GL_CLIP_PLANE0 = $3000
            GL_CLIP_PLANE1 = $3001
            GL_CLIP_PLANE2 = $3002
            GL_CLIP_PLANE3 = $3003
            GL_CLIP_PLANE4 = $3004
            GL_CLIP_PLANE5 = $3005

            ; Miscellaneous

            GL_DITHER = $0BD0

            ; EXT_abgr

            GL_ABGR_EXT = $8000

            ; EXT_packed_pixels

            GL_UNSIGNED_BYTE_3_3_2_EXT     = $8032
            GL_UNSIGNED_SHORT_4_4_4_4_EXT  = $8033
            GL_UNSIGNED_SHORT_5_5_5_1_EXT  = $8034
            GL_UNSIGNED_INT_8_8_8_8_EXT    = $8035
            GL_UNSIGNED_INT_10_10_10_2_EXT = $8036

            ; EXT_vertex_array

            GL_VERTEX_ARRAY_EXT		   = $8074
            GL_NORMAL_ARRAY_EXT		   = $8075
            GL_COLOR_ARRAY_EXT		   = $8076
            GL_INDEX_ARRAY_EXT		   = $8077
            GL_TEXTURE_COORD_ARRAY_EXT	   = $8078
            GL_EDGE_FLAG_ARRAY_EXT		   = $8079
            GL_VERTEX_ARRAY_SIZE_EXT	   = $807A
            GL_VERTEX_ARRAY_TYPE_EXT	   = $807B
            GL_VERTEX_ARRAY_STRIDE_EXT	   = $807C
            GL_VERTEX_ARRAY_COUNT_EXT	   = $807D
            GL_NORMAL_ARRAY_TYPE_EXT	   = $807E
            GL_NORMAL_ARRAY_STRIDE_EXT	   = $807F
            GL_NORMAL_ARRAY_COUNT_EXT	   = $8080
            GL_COLOR_ARRAY_SIZE_EXT 	   = $8081
            GL_COLOR_ARRAY_TYPE_EXT 	   = $8082
            GL_COLOR_ARRAY_STRIDE_EXT	   = $8083
            GL_COLOR_ARRAY_COUNT_EXT	   = $8084
            GL_INDEX_ARRAY_TYPE_EXT 	   = $8085
            GL_INDEX_ARRAY_STRIDE_EXT	   = $8086
            GL_INDEX_ARRAY_COUNT_EXT	   = $8087
            GL_TEXTURE_COORD_ARRAY_SIZE_EXT    = $8088
            GL_TEXTURE_COORD_ARRAY_TYPE_EXT    = $8089
            GL_TEXTURE_COORD_ARRAY_STRIDE_EXT  = $808A
            GL_TEXTURE_COORD_ARRAY_COUNT_EXT   = $808B
            GL_EDGE_FLAG_ARRAY_STRIDE_EXT	   = $808C
            GL_EDGE_FLAG_ARRAY_COUNT_EXT	   = $808D
            GL_VERTEX_ARRAY_POINTER_EXT	   = $808E
            GL_NORMAL_ARRAY_POINTER_EXT	   = $808F
            GL_COLOR_ARRAY_POINTER_EXT	   = $8090
            GL_INDEX_ARRAY_POINTER_EXT	   = $8091
            GL_TEXTURE_COORD_ARRAY_POINTER_EXT = $8092
            GL_EDGE_FLAG_ARRAY_POINTER_EXT	   = $8093

            ; EXT_color_table

            GL_TABLE_TOO_LARGE_EXT			   = $8031
            GL_COLOR_TABLE_EXT			   = $80D0
            GL_POST_CONVOLUTION_COLOR_TABLE_EXT	   = $80D1
            GL_POST_COLOR_MATRIX_COLOR_TABLE_EXT	   = $80D2
            GL_PROXY_COLOR_TABLE_EXT		   = $80D3
            GL_PROXY_POST_CONVOLUTION_COLOR_TABLE_EXT  = $80D4
            GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE_EXT = $80D5
            GL_COLOR_TABLE_SCALE_EXT		   = $80D6
            GL_COLOR_TABLE_BIAS_EXT 		   = $80D7
            GL_COLOR_TABLE_FORMAT_EXT		   = $80D8
            GL_COLOR_TABLE_WIDTH_EXT		   = $80D9
            GL_COLOR_TABLE_RED_SIZE_EXT		   = $80DA
            GL_COLOR_TABLE_GREEN_SIZE_EXT		   = $80DB
            GL_COLOR_TABLE_BLUE_SIZE_EXT		   = $80DC
            GL_COLOR_TABLE_ALPHA_SIZE_EXT		   = $80DD
            GL_COLOR_TABLE_LUMINANCE_SIZE_EXT	   = $80DE
            GL_COLOR_TABLE_INTENSITY_SIZE_EXT	   = $80DF

            ; EXT_bgra

            GL_BGR_EXT  = $80E0
            GL_BGRA_EXT = $80E1

            ; EXT_paletted_texture

            GL_COLOR_INDEX1_EXT  = $80E2
            GL_COLOR_INDEX2_EXT  = $80E3
            GL_COLOR_INDEX4_EXT  = $80E4
            GL_COLOR_INDEX8_EXT  = $80E5
            GL_COLOR_INDEX12_EXT = $80E6
            GL_COLOR_INDEX16_EXT = $80E7

            ; EXT_blend_color

            GL_CONSTANT_COLOR_EXT		= $8001
            GL_ONE_MINUS_CONSTANT_COLOR_EXT = $8002
            GL_CONSTANT_ALPHA_EXT		= $8003
            GL_ONE_MINUS_CONSTANT_ALPHA_EXT = $8004
            GL_BLEND_COLOR_EXT		= $8005

            ; EXT_blend_minmax

            GL_FUNC_ADD_EXT       = $8006
            GL_MIN_EXT	      = $8007
            GL_MAX_EXT	      = $8008
            GL_BLEND_EQUATION_EXT = $8009

            ; EXT_blend_subtract

            GL_FUNC_SUBTRACT_EXT	     = $800A
            GL_FUNC_REVERSE_SUBTRACT_EXT = $800B

            ; EXT_convolution

            GL_CONVOLUTION_1D_EXT		    = $8010
            GL_CONVOLUTION_2D_EXT		    = $8011
            GL_SEPARABLE_2D_EXT		    = $8012
            GL_CONVOLUTION_BORDER_MODE_EXT	    = $8013
            GL_CONVOLUTION_FILTER_SCALE_EXT     = $8014
            GL_CONVOLUTION_FILTER_BIAS_EXT	    = $8015
            GL_REDUCE_EXT			    = $8016
            GL_CONVOLUTION_FORMAT_EXT	    = $8017
            GL_CONVOLUTION_WIDTH_EXT	    = $8018
            GL_CONVOLUTION_HEIGHT_EXT	    = $8019
            GL_MAX_CONVOLUTION_WIDTH_EXT	    = $801A
            GL_MAX_CONVOLUTION_HEIGHT_EXT	    = $801B
            GL_POST_CONVOLUTION_RED_SCALE_EXT   = $801C
            GL_POST_CONVOLUTION_GREEN_SCALE_EXT = $801D
            GL_POST_CONVOLUTION_BLUE_SCALE_EXT  = $801E
            GL_POST_CONVOLUTION_ALPHA_SCALE_EXT = $801F
            GL_POST_CONVOLUTION_RED_BIAS_EXT    = $8020
            GL_POST_CONVOLUTION_GREEN_BIAS_EXT  = $8021
            GL_POST_CONVOLUTION_BLUE_BIAS_EXT   = $8022
            GL_POST_CONVOLUTION_ALPHA_BIAS_EXT  = $8023

            ; EXT_histogram

            GL_HISTOGRAM_EXT		= $8024
            GL_PROXY_HISTOGRAM_EXT		= $8025
            GL_HISTOGRAM_WIDTH_EXT		= $8026
            GL_HISTOGRAM_FORMAT_EXT 	= $8027
            GL_HISTOGRAM_RED_SIZE_EXT	= $8028
            GL_HISTOGRAM_GREEN_SIZE_EXT	= $8029
            GL_HISTOGRAM_BLUE_SIZE_EXT	= $802A
            GL_HISTOGRAM_ALPHA_SIZE_EXT	= $802B
            GL_HISTOGRAM_LUMINANCE_SIZE_EXT = $802C
            GL_HISTOGRAM_SINK_EXT		= $802D
            GL_MINMAX_EXT			= $802E
            GL_MINMAX_FORMAT_EXT		= $802F
            GL_MINMAX_SINK_EXT		= $8030

            ; EXT_polygon_offset

            GL_POLYGON_OFFSET_EXT	     = $8037
            GL_POLYGON_OFFSET_FACTOR_EXT = $8038
            GL_POLYGON_OFFSET_BIAS_EXT   = $8039

            ; EXT_texture

            GL_ALPHA4_EXT		      = $803B
            GL_ALPHA8_EXT		      = $803C
            GL_ALPHA12_EXT		      = $803D
            GL_ALPHA16_EXT		      = $803E
            GL_LUMINANCE4_EXT	      = $803F
            GL_LUMINANCE8_EXT	      = $8040
            GL_LUMINANCE12_EXT	      = $8041
            GL_LUMINANCE16_EXT	      = $8042
            GL_LUMINANCE4_ALPHA4_EXT      = $8043
            GL_LUMINANCE6_ALPHA2_EXT      = $8044
            GL_LUMINANCE8_ALPHA8_EXT      = $8045
            GL_LUMINANCE12_ALPHA4_EXT     = $8046
            GL_LUMINANCE12_ALPHA12_EXT    = $8047
            GL_LUMINANCE16_ALPHA16_EXT    = $8048
            GL_INTENSITY_EXT	      = $8049
            GL_INTENSITY4_EXT	      = $804A
            GL_INTENSITY8_EXT	      = $804B
            GL_INTENSITY12_EXT	      = $804C
            GL_INTENSITY16_EXT	      = $804D
            GL_RGB2_EXT		      = $804E
            GL_RGB4_EXT		      = $804F
            GL_RGB5_EXT		      = $8050
            GL_RGB8_EXT		      = $8051
            GL_RGB10_EXT		      = $8052
            GL_RGB12_EXT		      = $8053
            GL_RGB16_EXT		      = $8054
            GL_RGBA2_EXT		      = $8055
            GL_RGBA4_EXT		      = $8056
            GL_RGB5_A1_EXT		      = $8057
            GL_RGBA8_EXT		      = $8058
            GL_RGB10_A2_EXT 	      = $8059
            GL_RGBA12_EXT		      = $805A
            GL_RGBA16_EXT		      = $805B
            GL_TEXTURE_RED_SIZE_EXT       = $805C
            GL_TEXTURE_GREEN_SIZE_EXT     = $805D
            GL_TEXTURE_BLUE_SIZE_EXT      = $805E
            GL_TEXTURE_ALPHA_SIZE_EXT     = $805F
            GL_TEXTURE_LUMINANCE_SIZE_EXT = $8060
            GL_TEXTURE_INTENSITY_SIZE_EXT = $8061
            GL_REPLACE_EXT		      = $8062
            GL_PROXY_TEXTURE_1D_EXT       = $8063
            GL_PROXY_TEXTURE_2D_EXT       = $8064
            GL_TEXTURE_TOO_LARGE_EXT      = $8065

            ; EXT_texture_object

            GL_TEXTURE_PRIORITY_EXT   = $8066
            GL_TEXTURE_RESIDENT_EXT   = $8067
            GL_TEXTURE_1D_BINDING_EXT = $8068
            GL_TEXTURE_2D_BINDING_EXT = $8069
            GL_TEXTURE_3D_BINDING_EXT = $806A

            ; EXT_texture3D

            GL_PACK_SKIP_IMAGES_EXT    = $806B
            GL_PACK_IMAGE_HEIGHT_EXT   = $806C
            GL_UNPACK_SKIP_IMAGES_EXT  = $806D
            GL_UNPACK_IMAGE_HEIGHT_EXT = $806E
            GL_TEXTURE_3D_EXT	   = $806F
            GL_PROXY_TEXTURE_3D_EXT    = $8070
            GL_TEXTURE_DEPTH_EXT	   = $8071
            GL_TEXTURE_WRAP_R_EXT	   = $8072
            GL_MAX_3D_TEXTURE_SIZE_EXT = $8073

            ; SGI_color_matrix

            GL_COLOR_MATRIX_SGI		     = $80B1
            GL_COLOR_MATRIX_STACK_DEPTH_SGI      = $80B2
            GL_MAX_COLOR_MATRIX_STACK_DEPTH_SGI  = $80B3
            GL_POST_COLOR_MATRIX_RED_SCALE_SGI   = $80B4
            GL_POST_COLOR_MATRIX_GREEN_SCALE_SGI = $80B5
            GL_POST_COLOR_MATRIX_BLUE_SCALE_SGI  = $80B6
            GL_POST_COLOR_MATRIX_ALPHA_SCALE_SGI = $80B7
            GL_POST_COLOR_MATRIX_RED_BIAS_SGI    = $80B8
            GL_POST_COLOR_MATRIX_GREEN_BIAS_SGI  = $80B9
            GL_POST_COLOR_MATRIX_BLUE_BIAS_SGI   = $80BA
            GL_POST_COLOR_MATRIX_ALPHA_BIAS_SGI  = $80BB

            ; SGI_texture_color_table

            GL_TEXTURE_COLOR_TABLE_SGI	 = $80BC
            GL_PROXY_TEXTURE_COLOR_TABLE_SGI = $80BD
            GL_TEXTURE_COLOR_TABLE_BIAS_SGI  = $80BE
            GL_TEXTURE_COLOR_TABLE_SCALE_SGI = $80BF

            ; SGI_color_table

            GL_COLOR_TABLE_SGI			   = $80D0
            GL_POST_CONVOLUTION_COLOR_TABLE_SGI	   = $80D1
            GL_POST_COLOR_MATRIX_COLOR_TABLE_SGI	   = $80D2
            GL_PROXY_COLOR_TABLE_SGI		   = $80D3
            GL_PROXY_POST_CONVOLUTION_COLOR_TABLE_SGI  = $80D4
            GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE_SGI = $80D5
            GL_COLOR_TABLE_SCALE_SGI		   = $80D6
            GL_COLOR_TABLE_BIAS_SGI 		   = $80D7
            GL_COLOR_TABLE_FORMAT_SGI		   = $80D8
            GL_COLOR_TABLE_WIDTH_SGI		   = $80D9
            GL_COLOR_TABLE_RED_SIZE_SGI		   = $80DA
            GL_COLOR_TABLE_GREEN_SIZE_SGI		   = $80DB
            GL_COLOR_TABLE_BLUE_SIZE_SGI		   = $80DC
            GL_COLOR_TABLE_ALPHA_SIZE_SGI		   = $80DD
            GL_COLOR_TABLE_LUMINANCE_SIZE_SGI	   = $80DE
            GL_COLOR_TABLE_INTENSITY_SIZE_SGI	   = $80DF

            ; EXT_cmyka

            GL_CMYK_EXT		= $800C
            GL_CMYKA_EXT		= $800D
            GL_PACK_CMYK_HINT_EXT	= $800E
            GL_UNPACK_CMYK_HINT_EXT = $800F

            ; EXT_rescale_normal

            GL_RESCALE_NORMAL_EXT = $803A

            ; EXT_clip_volume_hint

            GL_CLIP_VOLUME_CLIPPING_HINT_EXT = $80F0

            ; EXT_cull_vertex

            GL_CULL_VERTEX_EXT		   = $81AA
            GL_CULL_VERTEX_EYE_POSITION_EXT    = $81AB
            GL_CULL_VERTEX_OBJECT_POSITION_EXT = $81AC

            ; EXT_index_array_formats

            GL_IUI_V2F_EXT	       = $81AD
            GL_IUI_V3F_EXT	       = $81AE
            GL_IUI_N3F_V2F_EXT     = $81AF
            GL_IUI_N3F_V3F_EXT     = $81B0
            GL_T2F_IUI_V2F_EXT     = $81B1
            GL_T2F_IUI_V3F_EXT     = $81B2
            GL_T2F_IUI_N3F_V2F_EXT = $81B3
            GL_T2F_IUI_N3F_V3F_EXT = $81B4

            ; EXT_index_func

            GL_INDEX_TEST_EXT      = $81B5
            GL_INDEX_TEST_FUNC_EXT = $81B6
            GL_INDEX_TEST_REF_EXT  = $81B7

            ; EXT_index_material

            GL_INDEX_MATERIAL_EXT		= $81B8
            GL_INDEX_MATERIAL_PARAMETER_EXT = $81B9
            GL_INDEX_MATERIAL_FACE_EXT	= $81BA

            ; EXT_shared_texture_palette

            GL_SHARED_TEXTURE_PALETTE_EXT = $81FB

            ; EXT_nurbs_tessellator

            GLU_NURBS_MODE_EXT	     = 100160
            GLU_NURBS_TESSELLATOR_EXT    = 100161
            GLU_NURBS_RENDERER_EXT	     = 100162
            GLU_NURBS_BEGIN_EXT	     = 100164
            GLU_NURBS_VERTEX_EXT	     = 100165
            GLU_NURBS_NORMAL_EXT	     = 100166
            GLU_NURBS_COLOR_EXT	     = 100167
            GLU_NURBS_TEX_COORD_EXT      = 100168
            GLU_NURBS_END_EXT	     = 100169
            GLU_NURBS_BEGIN_DATA_EXT     = 100170
            GLU_NURBS_VERTEX_DATA_EXT    = 100171
            GLU_NURBS_NORMAL_DATA_EXT    = 100172
            GLU_NURBS_COLOR_DATA_EXT     = 100173
            GLU_NURBS_TEX_COORD_DATA_EXT = 100174
            GLU_NURBS_END_DATA_EXT	     = 100175

            ; EXT_object_space_tess

            GLU_OBJECT_PARAMETRIC_ERROR_EXT = 100208
            GLU_OBJECT_PATH_LENGTH_EXT	= 100209

            ; EXT_point_parameters

            GL_POINT_SIZE_MIN_EXT		 = $8126
            GL_POINT_SIZE_MAX_EXT		 = $8127
            GL_POINT_FADE_THRESHOLD_SIZE_EXT = $8128
            GL_DISTANCE_ATTENUATION_EXT	 = $8129

            ; EXT_compiled_vertex_array

            GL_ARRAY_ELEMENT_LOCK_FIRST_EXT = $81A8
            GL_ARRAY_ELEMENT_LOCK_COUNT_EXT = $81A9

            ; ARB_multitexture

            GL_ACTIVE_TEXTURE_ARB	     = $84E0
            GL_CLIENT_ACTIVE_TEXTURE_ARB = $84E1
            GL_MAX_TEXTURE_UNITS_ARB     = $84E2
            GL_TEXTURE0_ARB 	     = $84C0
            GL_TEXTURE1_ARB 	     = $84C1
            GL_TEXTURE2_ARB 	     = $84C2
            GL_TEXTURE3_ARB 	     = $84C3
            GL_TEXTURE4_ARB 	     = $84C4
            GL_TEXTURE5_ARB 	     = $84C5
            GL_TEXTURE6_ARB 	     = $84C6
            GL_TEXTURE7_ARB 	     = $84C7
            GL_TEXTURE8_ARB 	     = $84C8
            GL_TEXTURE9_ARB 	     = $84C9
            GL_TEXTURE10_ARB	     = $84CA
            GL_TEXTURE11_ARB	     = $84CB
            GL_TEXTURE12_ARB	     = $84CC
            GL_TEXTURE13_ARB	     = $84CD
            GL_TEXTURE14_ARB	     = $84CE
            GL_TEXTURE15_ARB	     = $84CF
            GL_TEXTURE16_ARB	     = $84D0
            GL_TEXTURE17_ARB	     = $84D1
            GL_TEXTURE18_ARB	     = $84D2
            GL_TEXTURE19_ARB	     = $84D3
            GL_TEXTURE20_ARB	     = $84D4
            GL_TEXTURE21_ARB	     = $84D5
            GL_TEXTURE22_ARB	     = $84D6
            GL_TEXTURE23_ARB	     = $84D7
            GL_TEXTURE24_ARB	     = $84D8
            GL_TEXTURE25_ARB	     = $84D9
            GL_TEXTURE26_ARB	     = $84DA
            GL_TEXTURE27_ARB	     = $84DB
            GL_TEXTURE28_ARB	     = $84DC
            GL_TEXTURE29_ARB	     = $84DD
            GL_TEXTURE30_ARB	     = $84DE
            GL_TEXTURE31_ARB	     = $84DF

            ; EXT_stencil_wrap

            GL_INCR_WRAP_EXT = $8507
            GL_DECR_WRAP_EXT = $8508

            ; NV_texgen_reflection

            GL_NORMAL_MAP_NV     = $8511
            GL_REFLECTION_MAP_NV = $8512

            ; EXT_texture_env_combine

            GL_COMBINE_EXT	      = $8570
            GL_COMBINE_RGB_EXT    = $8571
            GL_COMBINE_ALPHA_EXT  = $8572
            GL_RGB_SCALE_EXT      = $8573
            GL_ADD_SIGNED_EXT     = $8574
            GL_INTERPOLATE_EXT    = $8575
            GL_CONSTANT_EXT       = $8576
            GL_PRIMARY_COLOR_EXT  = $8577
            GL_PREVIOUS_EXT       = $8578
            GL_SOURCE0_RGB_EXT    = $8580
            GL_SOURCE1_RGB_EXT    = $8581
            GL_SOURCE2_RGB_EXT    = $8582
            GL_SOURCE0_ALPHA_EXT  = $8588
            GL_SOURCE1_ALPHA_EXT  = $8589
            GL_SOURCE2_ALPHA_EXT  = $858A
            GL_OPERAND0_RGB_EXT   = $8590
            GL_OPERAND1_RGB_EXT   = $8591
            GL_OPERAND2_RGB_EXT   = $8592
            GL_OPERAND0_ALPHA_EXT = $8598
            GL_OPERAND1_ALPHA_EXT = $8599
            GL_OPERAND2_ALPHA_EXT = $859A

            ; NV_texture_env_combine4

            GL_COMBINE4_NV	     = $8503
            GL_SOURCE3_RGB_NV    = $8583
            GL_SOURCE3_ALPHA_NV  = $858B
            GL_OPERAND3_RGB_NV   = $8593
            GL_OPERAND3_ALPHA_NV = $859B

            GL_BLEND_EQUATION	       = $8009
            GL_TABLE_TOO_LARGE	       = $8031
            GL_UNSIGNED_BYTE_3_3_2	       = $8032
            GL_UNSIGNED_SHORT_4_4_4_4      = $8033
            GL_UNSIGNED_SHORT_5_5_5_1      = $8034
            GL_UNSIGNED_INT_8_8_8_8        = $8035
            GL_UNSIGNED_INT_10_10_10_2     = $8036
            GL_UNSIGNED_BYTE_2_3_3_REV     = $8362
            GL_UNSIGNED_SHORT_5_6_5        = $8363
            GL_UNSIGNED_SHORT_5_6_5_REV    = $8364
            GL_UNSIGNED_SHORT_4_4_4_4_REV  = $8365
            GL_UNSIGNED_SHORT_1_5_5_5_REV  = $8366
            GL_UNSIGNED_INT_8_8_8_8_REV    = $8367
            GL_UNSIGNED_INT_2_10_10_10_REV = $8368

            ; GL_ARB_transpose_matrix

            GL_TRANSPOSE_MODELVIEW_MATRIX_ARB  = $84E3
            GL_TRANSPOSE_PROJECTION_MATRIX_ARB = $84E4
            GL_TRANSPOSE_TEXTURE_MATRIX_ARB    = $84E5
            GL_TRANSPOSE_COLOR_MATRIX_ARB	   = $84E6

            ; GL_ARB_multisample

            GL_MULTISAMPLE_ARB		= $809D
            GL_SAMPLE_ALPHA_TO_COVERAGE_ARB = $809E
            GL_SAMPLE_ALPHA_TO_ONE_ARB	= $809F
            GL_SAMPLE_COVERAGE_ARB		= $80A0
            GL_SAMPLE_BUFFERS_ARB		= $80A8
            GL_SAMPLES_ARB			= $80A9
            GL_SAMPLE_COVERAGE_VALUE_ARB	= $80AA
            GL_SAMPLE_COVERAGE_INVERT_ARB	= $80AB
            GL_MULTISAMPLE_BIT_ARB		= $20000000

            ; GL_ARB_texture_cube_map

            GL_NORMAL_MAP_ARB		   = $8511
            GL_REFLECTION_MAP_ARB		   = $8512
            GL_TEXTURE_CUBE_MAP_ARB 	   = $8513
            GL_TEXTURE_BINDING_CUBE_MAP_ARB    = $8514
            GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB = $8515
            GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB = $8516
            GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB = $8517
            GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB = $8518
            GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB = $8519
            GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB = $851A
            GL_PROXY_TEXTURE_CUBE_MAP_ARB	   = $851B
            GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB   = $851C

            ; GL_ARB_texture_compression

            GL_COMPRESSED_ALPHA_ARB 	      = $84E9
            GL_COMPRESSED_LUMINANCE_ARB	      = $84EA
            GL_COMPRESSED_LUMINANCE_ALPHA_ARB     = $84EB
            GL_COMPRESSED_INTENSITY_ARB	      = $84EC
            GL_COMPRESSED_RGB_ARB		      = $84ED
            GL_COMPRESSED_RGBA_ARB		      = $84EE
            GL_TEXTURE_COMPRESSION_HINT_ARB       = $84EF
            GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB  = $86A0
            GL_TEXTURE_COMPRESSED_ARB	      = $86A1
            GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB = $86A2
            GL_COMPRESSED_TEXTURE_FORMATS_ARB     = $86A3

            ; GL_ARB_vertex_blend

            GL_MAX_VERTEX_UNITS_ARB     = $86A4
            GL_ACTIVE_VERTEX_UNITS_ARB  = $86A5
            GL_WEIGHT_SUM_UNITY_ARB     = $86A6
            GL_VERTEX_BLEND_ARB	    = $86A7
            GL_CURRENT_WEIGHT_ARB	    = $86A8
            GL_WEIGHT_ARRAY_TYPE_ARB    = $86A9
            GL_WEIGHT_ARRAY_STRIDE_ARB  = $86AA
            GL_WEIGHT_ARRAY_SIZE_ARB    = $86AB
            GL_WEIGHT_ARRAY_POINTER_ARB = $86AC
            GL_WEIGHT_ARRAY_ARB	    = $86AD
            GL_MODELVIEW0_ARB	    = $1700
            GL_MODELVIEW1_ARB	    = $850A
            GL_MODELVIEW2_ARB	    = $8722
            GL_MODELVIEW3_ARB	    = $8723
            GL_MODELVIEW4_ARB	    = $8724
            GL_MODELVIEW5_ARB	    = $8725
            GL_MODELVIEW6_ARB	    = $8726
            GL_MODELVIEW7_ARB	    = $8727
            GL_MODELVIEW8_ARB	    = $8728
            GL_MODELVIEW9_ARB	    = $8729
            GL_MODELVIEW10_ARB	    = $872A
            GL_MODELVIEW11_ARB	    = $872B
            GL_MODELVIEW12_ARB	    = $872C
            GL_MODELVIEW13_ARB	    = $872D
            GL_MODELVIEW14_ARB	    = $872E
            GL_MODELVIEW15_ARB	    = $872F
            GL_MODELVIEW16_ARB	    = $8730
            GL_MODELVIEW17_ARB	    = $8731
            GL_MODELVIEW18_ARB	    = $8732
            GL_MODELVIEW19_ARB	    = $8733
            GL_MODELVIEW20_ARB	    = $8734
            GL_MODELVIEW21_ARB	    = $8735
            GL_MODELVIEW22_ARB	    = $8736
            GL_MODELVIEW23_ARB	    = $8737
            GL_MODELVIEW24_ARB	    = $8738
            GL_MODELVIEW25_ARB	    = $8739
            GL_MODELVIEW26_ARB	    = $873A
            GL_MODELVIEW27_ARB	    = $873B
            GL_MODELVIEW28_ARB	    = $873C
            GL_MODELVIEW29_ARB	    = $873D
            GL_MODELVIEW30_ARB	    = $873E
            GL_MODELVIEW31_ARB	    = $873F

            ; GL_SGIS_texture_filter4

            GL_FILTER4_SGIS 	     = $8146
            GL_TEXTURE_FILTER4_SIZE_SGIS = $8147

            ; GL_SGIS_pixel_texture

            GL_PIXEL_TEXTURE_SGIS		    = $8353
            GL_PIXEL_FRAGMENT_RGB_SOURCE_SGIS   = $8354
            GL_PIXEL_FRAGMENT_ALPHA_SOURCE_SGIS = $8355
            GL_PIXEL_GROUP_COLOR_SGIS	    = $8356

            ; GL_SGIX_pixel_texture

            GL_PIXEL_TEX_GEN_SGIX	   = $8139
            GL_PIXEL_TEX_GEN_MODE_SGIX = $832B

            ; GL_SGIS_texture4D

            GL_PACK_SKIP_VOLUMES_SGIS   = $8130
            GL_PACK_IMAGE_DEPTH_SGIS    = $8131
            GL_UNPACK_SKIP_VOLUMES_SGIS = $8132
            GL_UNPACK_IMAGE_DEPTH_SGIS  = $8133
            GL_TEXTURE_4D_SGIS	    = $8134
            GL_PROXY_TEXTURE_4D_SGIS    = $8135
            GL_TEXTURE_4DSIZE_SGIS	    = $8136
            GL_TEXTURE_WRAP_Q_SGIS	    = $8137
            GL_MAX_4D_TEXTURE_SIZE_SGIS = $8138
            GL_TEXTURE_4D_BINDING_SGIS  = $814F

            ; GL_SGIS_detail_texture

            GL_DETAIL_TEXTURE_2D_SGIS	   = $8095
            GL_DETAIL_TEXTURE_2D_BINDING_SGIS  = $8096
            GL_LINEAR_DETAIL_SGIS		   = $8097
            GL_LINEAR_DETAIL_ALPHA_SGIS	   = $8098
            GL_LINEAR_DETAIL_COLOR_SGIS	   = $8099
            GL_DETAIL_TEXTURE_LEVEL_SGIS	   = $809A
            GL_DETAIL_TEXTURE_MODE_SGIS	   = $809B
            GL_DETAIL_TEXTURE_FUNC_POINTS_SGIS = $809C

            ; GL_SGIS_sharpen_texture

            GL_LINEAR_SHARPEN_SGIS		    = $80AD
            GL_LINEAR_SHARPEN_ALPHA_SGIS	    = $80AE
            GL_LINEAR_SHARPEN_COLOR_SGIS	    = $80AF
            GL_SHARPEN_TEXTURE_FUNC_POINTS_SGIS = $80B0

            ; GL_SGIS_texture_lod

            GL_TEXTURE_MIN_LOD_SGIS    = $813A
            GL_TEXTURE_MAX_LOD_SGIS    = $813B
            GL_TEXTURE_BASE_LEVEL_SGIS = $813C
            GL_TEXTURE_MAX_LEVEL_SGIS  = $813D

            ; GL_SGIS_multisample

            GL_MULTISAMPLE_SGIS	     = $809D
            GL_SAMPLE_ALPHA_TO_MASK_SGIS = $809E
            GL_SAMPLE_ALPHA_TO_ONE_SGIS  = $809F
            GL_SAMPLE_MASK_SGIS	     = $80A0
            GL_1PASS_SGIS		     = $80A1
            GL_2PASS_0_SGIS 	     = $80A2
            GL_2PASS_1_SGIS 	     = $80A3
            GL_4PASS_0_SGIS 	     = $80A4
            GL_4PASS_1_SGIS 	     = $80A5
            GL_4PASS_2_SGIS 	     = $80A6
            GL_4PASS_3_SGIS 	     = $80A7
            GL_SAMPLE_BUFFERS_SGIS	     = $80A8
            GL_SAMPLES_SGIS 	     = $80A9
            GL_SAMPLE_MASK_VALUE_SGIS    = $80AA
            GL_SAMPLE_MASK_INVERT_SGIS   = $80AB
            GL_SAMPLE_PATTERN_SGIS	     = $80AC

            ; GL_SGIS_generate_mipmap

            GL_GENERATE_MIPMAP_SGIS      = $8191
            GL_GENERATE_MIPMAP_HINT_SGIS = $8192

            ; GL_SGIX_clipmap

            GL_LINEAR_CLIPMAP_LINEAR_SGIX	      = $8170
            GL_TEXTURE_CLIPMAP_CENTER_SGIX	      = $8171
            GL_TEXTURE_CLIPMAP_FRAME_SGIX	      = $8172
            GL_TEXTURE_CLIPMAP_OFFSET_SGIX	      = $8173
            GL_TEXTURE_CLIPMAP_VIRTUAL_DEPTH_SGIX = $8174
            GL_TEXTURE_CLIPMAP_LOD_OFFSET_SGIX    = $8175
            GL_TEXTURE_CLIPMAP_DEPTH_SGIX	      = $8176
            GL_MAX_CLIPMAP_DEPTH_SGIX	      = $8177
            GL_MAX_CLIPMAP_VIRTUAL_DEPTH_SGIX     = $8178
            GL_NEAREST_CLIPMAP_NEAREST_SGIX       = $844D
            GL_NEAREST_CLIPMAP_LINEAR_SGIX	      = $844E
            GL_LINEAR_CLIPMAP_NEAREST_SGIX	      = $844F

            ; GL_SGIX_shadow

            GL_TEXTURE_COMPARE_SGIX 	 = $819A
            GL_TEXTURE_COMPARE_OPERATOR_SGIX = $819B
            GL_TEXTURE_LEQUAL_R_SGIX	 = $819C
            GL_TEXTURE_GEQUAL_R_SGIX	 = $819D

            ; GL_SGIS_texture_edge_clamp

            GL_CLAMP_TO_EDGE_SGIS = $812F

            ; GL_SGIS_texture_border_clamp

            GL_CLAMP_TO_BORDER_SGIS = $812D

            ; GL_SGIX_interlace

            GL_INTERLACE_SGIX = $8094

            ; GL_SGIX_pixel_tiles

            GL_PIXEL_TILE_BEST_ALIGNMENT_SGIX  = $813E
            GL_PIXEL_TILE_CACHE_INCREMENT_SGIX = $813F
            GL_PIXEL_TILE_WIDTH_SGIX	   = $8140
            GL_PIXEL_TILE_HEIGHT_SGIX	   = $8141
            GL_PIXEL_TILE_GRID_WIDTH_SGIX	   = $8142
            GL_PIXEL_TILE_GRID_HEIGHT_SGIX	   = $8143
            GL_PIXEL_TILE_GRID_DEPTH_SGIX	   = $8144
            GL_PIXEL_TILE_CACHE_SIZE_SGIX	   = $8145

            ; GL_SGIS_texture_select

            GL_DUAL_ALPHA4_SGIS	      = $8110
            GL_DUAL_ALPHA8_SGIS	      = $8111
            GL_DUAL_ALPHA12_SGIS	      = $8112
            GL_DUAL_ALPHA16_SGIS	      = $8113
            GL_DUAL_LUMINANCE4_SGIS       = $8114
            GL_DUAL_LUMINANCE8_SGIS       = $8115
            GL_DUAL_LUMINANCE12_SGIS      = $8116
            GL_DUAL_LUMINANCE16_SGIS      = $8117
            GL_DUAL_INTENSITY4_SGIS       = $8118
            GL_DUAL_INTENSITY8_SGIS       = $8119
            GL_DUAL_INTENSITY12_SGIS      = $811A
            GL_DUAL_INTENSITY16_SGIS      = $811B
            GL_DUAL_LUMINANCE_ALPHA4_SGIS = $811C
            GL_DUAL_LUMINANCE_ALPHA8_SGIS = $811D
            GL_QUAD_ALPHA4_SGIS	      = $811E
            GL_QUAD_ALPHA8_SGIS	      = $811F
            GL_QUAD_LUMINANCE4_SGIS       = $8120
            GL_QUAD_LUMINANCE8_SGIS       = $8121
            GL_QUAD_INTENSITY4_SGIS       = $8122
            GL_QUAD_INTENSITY8_SGIS       = $8123
            GL_DUAL_TEXTURE_SELECT_SGIS   = $8124
            GL_QUAD_TEXTURE_SELECT_SGIS   = $8125

            ; GL_SGIX_sprite

            GL_SPRITE_SGIX		      = $8148
            GL_SPRITE_MODE_SGIX	      = $8149
            GL_SPRITE_AXIS_SGIX	      = $814A
            GL_SPRITE_TRANSLATION_SGIX    = $814B
            GL_SPRITE_AXIAL_SGIX	      = $814C
            GL_SPRITE_OBJECT_ALIGNED_SGIX = $814D
            GL_SPRITE_EYE_ALIGNED_SGIX    = $814E

            ; GL_SGIX_texture_multi_buffer

            GL_TEXTURE_MULTI_BUFFER_HINT_SGIX = $812E

            ; GL_SGIS_point_parameters

            GL_POINT_SIZE_MIN_SGIS		  = $8126
            GL_POINT_SIZE_MAX_SGIS		  = $8127
            GL_POINT_FADE_THRESHOLD_SIZE_SGIS = $8128
            GL_DISTANCE_ATTENUATION_SGIS	  = $8129

            ; GL_SGIX_instruments

            GL_INSTRUMENT_BUFFER_POINTER_SGIX = $8180
            GL_INSTRUMENT_MEASUREMENTS_SGIX   = $8181

            ; GL_SGIX_texture_scale_bias

            GL_POST_TEXTURE_FILTER_BIAS_SGIX	= $8179
            GL_POST_TEXTURE_FILTER_SCALE_SGIX	= $817A
            GL_POST_TEXTURE_FILTER_BIAS_RANGE_SGIX	= $817B
            GL_POST_TEXTURE_FILTER_SCALE_RANGE_SGIX = $817C

            ; GL_SGIX_framezoom

            GL_FRAMEZOOM_SGIX	     = $818B
            GL_FRAMEZOOM_FACTOR_SGIX     = $818C
            GL_MAX_FRAMEZOOM_FACTOR_SGIX = $818D

            ; GL_FfdMaskSGIX

            GL_TEXTURE_DEFORMATION_BIT_SGIX  = $00000001
            GL_GEOMETRY_DEFORMATION_BIT_SGIX = $00000002

            ; GL_SGIX_polynomial_ffd

            GL_GEOMETRY_DEFORMATION_SGIX  = $8194
            GL_TEXTURE_DEFORMATION_SGIX   = $8195
            GL_DEFORMATIONS_MASK_SGIX     = $8196
            GL_MAX_DEFORMATION_ORDER_SGIX = $8197

            ; GL_SGIX_reference_plane

            GL_REFERENCE_PLANE_SGIX 	 = $817D
            GL_REFERENCE_PLANE_EQUATION_SGIX = $817E

            ; GL_SGIX_depth_texture

            GL_DEPTH_COMPONENT16_SGIX = $81A5
            GL_DEPTH_COMPONENT24_SGIX = $81A6
            GL_DEPTH_COMPONENT32_SGIX = $81A7

            ; GL_SGIS_fog_function

            GL_FOG_FUNC_SGIS	    = $812A
            GL_FOG_FUNC_POINTS_SGIS     = $812B
            GL_MAX_FOG_FUNC_POINTS_SGIS = $812C

            ; GL_SGIX_fog_offset

            GL_FOG_OFFSET_SGIX	 = $8198
            GL_FOG_OFFSET_VALUE_SGIX = $8199

            ; GL_HP_image_transform

            GL_IMAGE_SCALE_X_HP			     = $8155
            GL_IMAGE_SCALE_Y_HP			     = $8156
            GL_IMAGE_TRANSLATE_X_HP 		     = $8157
            GL_IMAGE_TRANSLATE_Y_HP 		     = $8158
            GL_IMAGE_ROTATE_ANGLE_HP		     = $8159
            GL_IMAGE_ROTATE_ORIGIN_X_HP		     = $815A
            GL_IMAGE_ROTATE_ORIGIN_Y_HP		     = $815B
            GL_IMAGE_MAG_FILTER_HP			     = $815C
            GL_IMAGE_MIN_FILTER_HP			     = $815D
            GL_IMAGE_CUBIC_WEIGHT_HP		     = $815E
            GL_CUBIC_HP				     = $815F
            GL_AVERAGE_HP				     = $8160
            GL_IMAGE_TRANSFORM_2D_HP		     = $8161
            GL_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP	     = $8162
            GL_PROXY_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP = $8163

            ; GL_HP_convolution_border_modes

            GL_IGNORE_BORDER_HP	       = $8150
            GL_CONSTANT_BORDER_HP	       = $8151
            GL_REPLICATE_BORDER_HP	       = $8153
            GL_CONVOLUTION_BORDER_COLOR_HP = $8154

            ; GL_SGIX_texture_add_env

            GL_TEXTURE_ENV_BIAS_SGIX = $80BE

            ; GL_PGI_vertex_hints

            GL_VERTEX_DATA_HINT_PGI 	   = $1A22A
            GL_VERTEX_CONSISTENT_HINT_PGI	   = $1A22B
            GL_MATERIAL_SIDE_HINT_PGI	   = $1A22C
            GL_MAX_VERTEX_HINT_PGI		   = $1A22D
            GL_COLOR3_BIT_PGI		   = $00010000
            GL_COLOR4_BIT_PGI		   = $00020000
            GL_EDGEFLAG_BIT_PGI		   = $00040000
            GL_INDEX_BIT_PGI		   = $00080000
            GL_MAT_AMBIENT_BIT_PGI		   = $00100000
            GL_MAT_AMBIENT_AND_DIFFUSE_BIT_PGI = $00200000
            GL_MAT_DIFFUSE_BIT_PGI		   = $00400000
            GL_MAT_EMISSION_BIT_PGI 	   = $00800000
            GL_MAT_COLOR_INDEXES_BIT_PGI	   = $01000000
            GL_MAT_SHININESS_BIT_PGI	   = $02000000
            GL_MAT_SPECULAR_BIT_PGI 	   = $04000000
            GL_NORMAL_BIT_PGI		   = $08000000
            GL_TEXCOORD1_BIT_PGI		   = $10000000
            GL_TEXCOORD2_BIT_PGI		   = $20000000
            GL_TEXCOORD3_BIT_PGI		   = $40000000
            GL_TEXCOORD4_BIT_PGI		   = $80000000
            GL_VERTEX23_BIT_PGI		   = $00000004
            GL_VERTEX4_BIT_PGI		   = $00000008

            ; GL_PGI_misc_hints

            GL_PREFER_DOUBLEBUFFER_HINT_PGI   = $1A1F8
            GL_CONSERVE_MEMORY_HINT_PGI	  = $1A1FD
            GL_RECLAIM_MEMORY_HINT_PGI	  = $1A1FE
            GL_NATIVE_GRAPHICS_HANDLE_PGI	  = $1A202
            GL_NATIVE_GRAPHICS_BEGIN_HINT_PGI = $1A203
            GL_NATIVE_GRAPHICS_END_HINT_PGI   = $1A204
            GL_ALWAYS_FAST_HINT_PGI 	  = $1A20C
            GL_ALWAYS_SOFT_HINT_PGI 	  = $1A20D
            GL_ALLOW_DRAW_OBJ_HINT_PGI	  = $1A20E
            GL_ALLOW_DRAW_WIN_HINT_PGI	  = $1A20F
            GL_ALLOW_DRAW_FRG_HINT_PGI	  = $1A210
            GL_ALLOW_DRAW_MEM_HINT_PGI	  = $1A211
            GL_STRICT_DEPTHFUNC_HINT_PGI	  = $1A216
            GL_STRICT_LIGHTING_HINT_PGI	  = $1A217
            GL_STRICT_SCISSOR_HINT_PGI	  = $1A218
            GL_FULL_STIPPLE_HINT_PGI	  = $1A219
            GL_CLIP_NEAR_HINT_PGI		  = $1A220
            GL_CLIP_FAR_HINT_PGI		  = $1A221
            GL_WIDE_LINE_HINT_PGI		  = $1A222
            GL_BACK_NORMALS_HINT_PGI	  = $1A223

            ; GL_EXT_paletted_texture

            GL_TEXTURE_INDEX_SIZE_EXT = $80ED

            ; GL_SGIX_list_priority

            GL_LIST_PRIORITY_SGIX = $8182

            ; GL_SGIX_ir_instrument1

            GL_IR_INSTRUMENT1_SGIX = $817F

            ; GL_SGIX_calligraphic_fragment

            GL_CALLIGRAPHIC_FRAGMENT_SGIX = $8183

            ; GL_SGIX_texture_lod_bias

            GL_TEXTURE_LOD_BIAS_S_SGIX = $818E
            GL_TEXTURE_LOD_BIAS_T_SGIX = $818F
            GL_TEXTURE_LOD_BIAS_R_SGIX = $8190

            ; GL_SGIX_shadow_ambient

            GL_SHADOW_AMBIENT_SGIX = $80BF

            ; GL_SGIX_ycrcb

            GL_YCRCB_422_SGIX = $81BB
            GL_YCRCB_444_SGIX = $81BC

            ; GL_SGIX_fragment_lighting

            GL_FRAGMENT_LIGHTING_SGIX			  = $8400
            GL_FRAGMENT_COLOR_MATERIAL_SGIX 		  = $8401
            GL_FRAGMENT_COLOR_MATERIAL_FACE_SGIX		  = $8402
            GL_FRAGMENT_COLOR_MATERIAL_PARAMETER_SGIX	  = $8403
            GL_MAX_FRAGMENT_LIGHTS_SGIX			  = $8404
            GL_MAX_ACTIVE_LIGHTS_SGIX			  = $8405
            GL_CURRENT_RASTER_NORMAL_SGIX			  = $8406
            GL_LIGHT_ENV_MODE_SGIX				  = $8407
            GL_FRAGMENT_LIGHT_MODEL_LOCAL_VIEWER_SGIX	  = $8408
            GL_FRAGMENT_LIGHT_MODEL_TWO_SIDE_SGIX		  = $8409
            GL_FRAGMENT_LIGHT_MODEL_AMBIENT_SGIX		  = $840A
            GL_FRAGMENT_LIGHT_MODEL_NORMAL_INTERPOLATION_SGIX = $840B
            GL_FRAGMENT_LIGHT0_SGIX 			  = $840C
            GL_FRAGMENT_LIGHT1_SGIX 			  = $840D
            GL_FRAGMENT_LIGHT2_SGIX 			  = $840E
            GL_FRAGMENT_LIGHT3_SGIX 			  = $840F
            GL_FRAGMENT_LIGHT4_SGIX 			  = $8410
            GL_FRAGMENT_LIGHT5_SGIX 			  = $8411
            GL_FRAGMENT_LIGHT6_SGIX 			  = $8412
            GL_FRAGMENT_LIGHT7_SGIX 			  = $8413

            ; GL_IBM_rasterpos_clip

            GL_RASTER_POSITION_UNCLIPPED_IBM = $19262

            ; GL_HP_texture_lighting

            GL_TEXTURE_LIGHTING_MODE_HP = $8167
            GL_TEXTURE_POST_SPECULAR_HP = $8168
            GL_TEXTURE_PRE_SPECULAR_HP  = $8169

            ; GL_EXT_draw_range_elements

            GL_MAX_ELEMENTS_VERTICES_EXT = $80E8
            GL_MAX_ELEMENTS_INDICES_EXT  = $80E9

            ; GL_WIN_phong_shading

            GL_PHONG_WIN	  = $80EA
            GL_PHONG_HINT_WIN = $80EB

            ; GL_WIN_specular_fog

            GL_FOG_SPECULAR_TEXTURE_WIN = $80EC

            ; GL_EXT_light_texture

            GL_FRAGMENT_MATERIAL_EXT	  = $8349
            GL_FRAGMENT_NORMAL_EXT		  = $834A
            GL_FRAGMENT_COLOR_EXT		  = $834C
            GL_ATTENUATION_EXT		  = $834D
            GL_SHADOW_ATTENUATION_EXT	  = $834E
            GL_TEXTURE_APPLICATION_MODE_EXT   = $834F
            GL_TEXTURE_LIGHT_EXT		  = $8350
            GL_TEXTURE_MATERIAL_FACE_EXT	  = $8351
            GL_TEXTURE_MATERIAL_PARAMETER_EXT = $8352

            ; GL_SGIX_blend_alpha_minmax

            GL_ALPHA_MIN_SGIX = $8320
            GL_ALPHA_MAX_SGIX = $8321

            ; GL_SGIX_async

            GL_ASYNC_MARKER_SGIX  = $8329

            ; GL_SGIX_async_pixel

            GL_ASYNC_TEX_IMAGE_SGIX       = $835C
            GL_ASYNC_DRAW_PIXELS_SGIX     = $835D
            GL_ASYNC_READ_PIXELS_SGIX     = $835E
            GL_MAX_ASYNC_TEX_IMAGE_SGIX   = $835F
            GL_MAX_ASYNC_DRAW_PIXELS_SGIX = $8360
            GL_MAX_ASYNC_READ_PIXELS_SGIX = $8361

            ; GL_SGIX_async_histogram

            GL_ASYNC_HISTOGRAM_SGIX     = $832C
            GL_MAX_ASYNC_HISTOGRAM_SGIX = $832D

            ; GL_INTEL_parallel_arrays

            GL_PARALLEL_ARRAYS_INTEL		       = $83F4
            GL_VERTEX_ARRAY_PARALLEL_POINTERS_INTEL        = $83F5
            GL_NORMAL_ARRAY_PARALLEL_POINTERS_INTEL        = $83F6
            GL_COLOR_ARRAY_PARALLEL_POINTERS_INTEL	       = $83F7
            GL_TEXTURE_COORD_ARRAY_PARALLEL_POINTERS_INTEL = $83F8

            ; GL_HP_occlusion_test

            GL_OCCLUSION_TEST_HP	    = $8165
            GL_OCCLUSION_TEST_RESULT_HP = $8166

            ; GL_EXT_pixel_transform

            GL_PIXEL_TRANSFORM_2D_EXT		  = $8330
            GL_PIXEL_MAG_FILTER_EXT 		  = $8331
            GL_PIXEL_MIN_FILTER_EXT 		  = $8332
            GL_PIXEL_CUBIC_WEIGHT_EXT		  = $8333
            GL_CUBIC_EXT				  = $8334
            GL_AVERAGE_EXT				  = $8335
            GL_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT	  = $8336
            GL_MAX_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT = $8337
            GL_PIXEL_TRANSFORM_2D_MATRIX_EXT	  = $8338

            ; GL_EXT_separate_specular_color

            GL_LIGHT_MODEL_COLOR_CONTROL_EXT = $81F8
            GL_SINGLE_COLOR_EXT		 = $81F9
            GL_SEPARATE_SPECULAR_COLOR_EXT	 = $81FA

            ; GL_EXT_secondary_color

            GL_COLOR_SUM_EXT		     = $8458
            GL_CURRENT_SECONDARY_COLOR_EXT	     = $8459
            GL_SECONDARY_COLOR_ARRAY_SIZE_EXT    = $845A
            GL_SECONDARY_COLOR_ARRAY_TYPE_EXT    = $845B
            GL_SECONDARY_COLOR_ARRAY_STRIDE_EXT  = $845C
            GL_SECONDARY_COLOR_ARRAY_POINTER_EXT = $845D
            GL_SECONDARY_COLOR_ARRAY_EXT	     = $845E

            ; GL_EXT_texture_perturb_normal

            GL_PERTURB_EXT	      = $85AE
            GL_TEXTURE_NORMAL_EXT = $85AF

            ; GL_EXT_fog_coord

            GL_FOG_COORDINATE_SOURCE_EXT	    = $8450
            GL_FOG_COORDINATE_EXT		    = $8451
            GL_FRAGMENT_DEPTH_EXT		    = $8452
            GL_CURRENT_FOG_COORDINATE_EXT	    = $8453
            GL_FOG_COORDINATE_ARRAY_TYPE_EXT    = $8454
            GL_FOG_COORDINATE_ARRAY_STRIDE_EXT  = $8455
            GL_FOG_COORDINATE_ARRAY_POINTER_EXT = $8456
            GL_FOG_COORDINATE_ARRAY_EXT	    = $8457

            ; GL_REND_screen_coordinates

            GL_SCREEN_COORDINATES_REND = $8490
            GL_INVERTED_SCREEN_W_REND  = $8491

            ; GL_EXT_coordinate_frame

            GL_TANGENT_ARRAY_EXT	      = $8439
            GL_BINORMAL_ARRAY_EXT	      = $843A
            GL_CURRENT_TANGENT_EXT	      = $843B
            GL_CURRENT_BINORMAL_EXT       = $843C
            GL_TANGENT_ARRAY_TYPE_EXT     = $843E
            GL_TANGENT_ARRAY_STRIDE_EXT   = $843F
            GL_BINORMAL_ARRAY_TYPE_EXT    = $8440
            GL_BINORMAL_ARRAY_STRIDE_EXT  = $8441
            GL_TANGENT_ARRAY_POINTER_EXT  = $8442
            GL_BINORMAL_ARRAY_POINTER_EXT = $8443
            GL_MAP1_TANGENT_EXT	      = $8444
            GL_MAP2_TANGENT_EXT	      = $8445
            GL_MAP1_BINORMAL_EXT	      = $8446
            GL_MAP2_BINORMAL_EXT	      = $8447

            ; GL_EXT_texture_env_combine

            GL_SOURCE3_RGB_EXT    = $8583
            GL_SOURCE4_RGB_EXT    = $8584
            GL_SOURCE5_RGB_EXT    = $8585
            GL_SOURCE6_RGB_EXT    = $8586
            GL_SOURCE7_RGB_EXT    = $8587
            GL_SOURCE3_ALPHA_EXT  = $858B
            GL_SOURCE4_ALPHA_EXT  = $858C
            GL_SOURCE5_ALPHA_EXT  = $858D
            GL_SOURCE6_ALPHA_EXT  = $858E
            GL_SOURCE7_ALPHA_EXT  = $858F
            GL_OPERAND3_RGB_EXT   = $8593
            GL_OPERAND4_RGB_EXT   = $8594
            GL_OPERAND5_RGB_EXT   = $8595
            GL_OPERAND6_RGB_EXT   = $8596
            GL_OPERAND7_RGB_EXT   = $8597
            GL_OPERAND3_ALPHA_EXT = $859B
            GL_OPERAND4_ALPHA_EXT = $859C
            GL_OPERAND5_ALPHA_EXT = $859D
            GL_OPERAND6_ALPHA_EXT = $859E
            GL_OPERAND7_ALPHA_EXT = $859F

            ; GL_APPLE_specular_vector

            GL_LIGHT_MODEL_SPECULAR_VECTOR_APPLE = $85B0

            ; GL_APPLE_transform_hint

            GL_TRANSFORM_HINT_APPLE = $85B1

            ; GL_SGIX_fog_scale

            GL_FOG_SCALE_SGIX	= $81FC
            GL_FOG_SCALE_VALUE_SGIX = $81FD

            ; GL_SUNX_constant_data

            GL_UNPACK_CONSTANT_DATA_SUNX  = $81D5
            GL_TEXTURE_CONSTANT_DATA_SUNX = $81D6

            ; GL_SUN_global_alpha

            GL_GLOBAL_ALPHA_SUN	   = $81D9
            GL_GLOBAL_ALPHA_FACTOR_SUN = $81DA

            ; GL_SUN_triangle_list

            GL_RESTART_SUN			      = $01
            GL_REPLACE_MIDDLE_SUN		      = $02
            GL_REPLACE_OLDEST_SUN		      = $03
            GL_TRIANGLE_LIST_SUN		      = $81D7
            GL_REPLACEMENT_CODE_SUN 	      = $81D8
            GL_REPLACEMENT_CODE_ARRAY_SUN	      = $85C0
            GL_REPLACEMENT_CODE_ARRAY_TYPE_SUN    = $85C1
            GL_REPLACEMENT_CODE_ARRAY_STRIDE_SUN  = $85C2
            GL_REPLACEMENT_CODE_ARRAY_POINTER_SUN = $85C3
            GL_R1UI_V3F_SUN 		      = $85C4
            GL_R1UI_C4UB_V3F_SUN		      = $85C5
            GL_R1UI_C3F_V3F_SUN		      = $85C6
            GL_R1UI_N3F_V3F_SUN		      = $85C7
            GL_R1UI_C4F_N3F_V3F_SUN 	      = $85C8
            GL_R1UI_T2F_V3F_SUN		      = $85C9
            GL_R1UI_T2F_N3F_V3F_SUN 	      = $85CA
            GL_R1UI_T2F_C4F_N3F_V3F_SUN	      = $85CB

            ; GL_EXT_blend_func_separate

            GL_BLEND_DST_RGB_EXT   = $80C8
            GL_BLEND_SRC_RGB_EXT   = $80C9
            GL_BLEND_DST_ALPHA_EXT = $80CA
            GL_BLEND_SRC_ALPHA_EXT = $80CB

            ; GL_INGR_color_clamp

            GL_RED_MIN_CLAMP_INGR	= $8560
            GL_GREEN_MIN_CLAMP_INGR = $8561
            GL_BLUE_MIN_CLAMP_INGR	= $8562
            GL_ALPHA_MIN_CLAMP_INGR = $8563
            GL_RED_MAX_CLAMP_INGR	= $8564
            GL_GREEN_MAX_CLAMP_INGR = $8565
            GL_BLUE_MAX_CLAMP_INGR	= $8566
            GL_ALPHA_MAX_CLAMP_INGR = $8567

            ; GL_INGR_interlace_read

            GL_INTERLACE_READ_INGR = $8568

            ; GL_EXT_422_pixels

            GL_422_EXT	       = $80CC
            GL_422_REV_EXT	       = $80CD
            GL_422_AVERAGE_EXT     = $80CE
            GL_422_REV_AVERAGE_EXT = $80CF

            ; GL_EXT_texture_cube_map

            GL_NORMAL_MAP_EXT		   = $8511
            GL_REFLECTION_MAP_EXT		   = $8512
            GL_TEXTURE_CUBE_MAP_EXT 	   = $8513
            GL_TEXTURE_BINDING_CUBE_MAP_EXT    = $8514
            GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT = $8515
            GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT = $8516
            GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT = $8517
            GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT = $8518
            GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT = $8519
            GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT = $851A
            GL_PROXY_TEXTURE_CUBE_MAP_EXT	   = $851B
            GL_MAX_CUBE_MAP_TEXTURE_SIZE_EXT   = $851C

            ; GL_SUN_convolution_border_modes

            GL_WRAP_BORDER_SUN = $81D4

            ; GL_EXT_texture_lod_bias

            GL_MAX_TEXTURE_LOD_BIAS_EXT   = $84FD
            GL_TEXTURE_FILTER_CONTROL_EXT = $8500
            GL_TEXTURE_LOD_BIAS_EXT       = $8501

            ; GL_EXT_texture_filter_anisotropic

            GL_TEXTURE_MAX_ANISOTROPY_EXT	  = $84FE
            GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT = $84FF

            ; GL_EXT_vertex_weighting

            GL_MODELVIEW0_STACK_DEPTH_EXT	   = GL_MODELVIEW_STACK_DEPTH
            GL_MODELVIEW1_STACK_DEPTH_EXT	   = $8502
            GL_MODELVIEW0_MATRIX_EXT	   = GL_MODELVIEW_MATRIX
            GL_MODELVIEW_MATRIX1_EXT	   = $8506
            GL_VERTEX_WEIGHTING_EXT 	   = $8509
            GL_MODELVIEW0_EXT		   = GL_MODELVIEW
            GL_MODELVIEW1_EXT		   = $850A
            GL_CURRENT_VERTEX_WEIGHT_EXT	   = $850B
            GL_VERTEX_WEIGHT_ARRAY_EXT	   = $850C
            GL_VERTEX_WEIGHT_ARRAY_SIZE_EXT    = $850D
            GL_VERTEX_WEIGHT_ARRAY_TYPE_EXT    = $850E
            GL_VERTEX_WEIGHT_ARRAY_STRIDE_EXT  = $850F
            GL_VERTEX_WEIGHT_ARRAY_POINTER_EXT = $8510

            ; GL_NV_light_max_exponent

            GL_MAX_SHININESS_NV	= $8504
            GL_MAX_SPOT_EXPONENT_NV = $8505

            ; GL_NV_vertex_array_range

            GL_VERTEX_ARRAY_RANGE_NV	     = $851D
            GL_VERTEX_ARRAY_RANGE_LENGTH_NV      = $851E
            GL_VERTEX_ARRAY_RANGE_VALID_NV	     = $851F
            GL_MAX_VERTEX_ARRAY_RANGE_ELEMENT_NV = $8520
            GL_VERTEX_ARRAY_RANGE_POINTER_NV     = $8521

            ; GL_NV_register_combiners

            GL_REGISTER_COMBINERS_NV	  = $8522
            GL_VARIABLE_A_NV		  = $8523
            GL_VARIABLE_B_NV		  = $8524
            GL_VARIABLE_C_NV		  = $8525
            GL_VARIABLE_D_NV		  = $8526
            GL_VARIABLE_E_NV		  = $8527
            GL_VARIABLE_F_NV		  = $8528
            GL_VARIABLE_G_NV		  = $8529
            GL_CONSTANT_COLOR0_NV		  = $852A
            GL_CONSTANT_COLOR1_NV		  = $852B
            GL_PRIMARY_COLOR_NV		  = $852C
            GL_SECONDARY_COLOR_NV		  = $852D
            GL_SPARE0_NV			  = $852E
            GL_SPARE1_NV			  = $852F
            GL_DISCARD_NV			  = $8530
            GL_E_TIMES_F_NV 		  = $8531
            GL_SPARE0_PLUS_SECONDARY_COLOR_NV = $8532
            GL_UNSIGNED_IDENTITY_NV 	  = $8536
            GL_UNSIGNED_INVERT_NV		  = $8537
            GL_EXPAND_NORMAL_NV		  = $8538
            GL_EXPAND_NEGATE_NV		  = $8539
            GL_HALF_BIAS_NORMAL_NV		  = $853A
            GL_HALF_BIAS_NEGATE_NV		  = $853B
            GL_SIGNED_IDENTITY_NV		  = $853C
            GL_SIGNED_NEGATE_NV		  = $853D
            GL_SCALE_BY_TWO_NV		  = $853E
            GL_SCALE_BY_FOUR_NV		  = $853F
            GL_SCALE_BY_ONE_HALF_NV 	  = $8540
            GL_BIAS_BY_NEGATIVE_ONE_HALF_NV   = $8541
            GL_COMBINER_INPUT_NV		  = $8542
            GL_COMBINER_MAPPING_NV		  = $8543
            GL_COMBINER_COMPONENT_USAGE_NV	  = $8544
            GL_COMBINER_AB_DOT_PRODUCT_NV	  = $8545
            GL_COMBINER_CD_DOT_PRODUCT_NV	  = $8546
            GL_COMBINER_MUX_SUM_NV		  = $8547
            GL_COMBINER_SCALE_NV		  = $8548
            GL_COMBINER_BIAS_NV		  = $8549
            GL_COMBINER_AB_OUTPUT_NV	  = $854A
            GL_COMBINER_CD_OUTPUT_NV	  = $854B
            GL_COMBINER_SUM_OUTPUT_NV	  = $854C
            GL_MAX_GENERAL_COMBINERS_NV	  = $854D
            GL_NUM_GENERAL_COMBINERS_NV	  = $854E
            GL_COLOR_SUM_CLAMP_NV		  = $854F
            GL_COMBINER0_NV 		  = $8550
            GL_COMBINER1_NV 		  = $8551
            GL_COMBINER2_NV 		  = $8552
            GL_COMBINER3_NV 		  = $8553
            GL_COMBINER4_NV 		  = $8554
            GL_COMBINER5_NV 		  = $8555
            GL_COMBINER6_NV 		  = $8556
            GL_COMBINER7_NV 		  = $8557

            ; GL_NV_fog_distance

            GL_FOG_DISTANCE_MODE_NV  = $855A
            GL_EYE_RADIAL_NV	 = $855B
            GL_EYE_PLANE_ABSOLUTE_NV = $855C

            ; GL_NV_texgen_emboss

            GL_EMBOSS_LIGHT_NV    = $855D
            GL_EMBOSS_CONSTANT_NV = $855E
            GL_EMBOSS_MAP_NV      = $855F

            ; GL_EXT_texture_compression_s3tc

            GL_COMPRESSED_RGB_S3TC_DXT1_EXT  = $83F0
            GL_COMPRESSED_RGBA_S3TC_DXT1_EXT = $83F1
            GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = $83F2
            GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = $83F3

            ; GL_IBM_cull_vertex

            GL_CULL_VERTEX_IBM = 103050

            ; GL_IBM_vertex_array_lists

            GL_VERTEX_ARRAY_LIST_IBM		 = 103070
            GL_NORMAL_ARRAY_LIST_IBM		 = 103071
            GL_COLOR_ARRAY_LIST_IBM 		 = 103072
            GL_INDEX_ARRAY_LIST_IBM 		 = 103073
            GL_TEXTURE_COORD_ARRAY_LIST_IBM 	 = 103074
            GL_EDGE_FLAG_ARRAY_LIST_IBM		 = 103075
            GL_FOG_COORDINATE_ARRAY_LIST_IBM	 = 103076
            GL_SECONDARY_COLOR_ARRAY_LIST_IBM	 = 103077
            GL_VERTEX_ARRAY_LIST_STRIDE_IBM 	 = 103080
            GL_NORMAL_ARRAY_LIST_STRIDE_IBM 	 = 103081
            GL_COLOR_ARRAY_LIST_STRIDE_IBM		 = 103082
            GL_INDEX_ARRAY_LIST_STRIDE_IBM		 = 103083
            GL_TEXTURE_COORD_ARRAY_LIST_STRIDE_IBM	 = 103084
            GL_EDGE_FLAG_ARRAY_LIST_STRIDE_IBM	 = 103085
            GL_FOG_COORDINATE_ARRAY_LIST_STRIDE_IBM  = 103086
            GL_SECONDARY_COLOR_ARRAY_LIST_STRIDE_IBM = 103087

            ; GL_SGIX_subsample

            GL_PACK_SUBSAMPLE_RATE_SGIX   = $85A0
            GL_UNPACK_SUBSAMPLE_RATE_SGIX = $85A1
            GL_PIXEL_SUBSAMPLE_4444_SGIX  = $85A2
            GL_PIXEL_SUBSAMPLE_2424_SGIX  = $85A3
            GL_PIXEL_SUBSAMPLE_4242_SGIX  = $85A4

            ; GL_SGIX_ycrcba

            GL_YCRCB_SGIX  = $8318
            GL_YCRCBA_SGIX = $8319

            ; GL_SGI_depth_pass_instrument

            GL_DEPTH_PASS_INSTRUMENT_SGIX	       = $8310
            GL_DEPTH_PASS_INSTRUMENT_COUNTERS_SGIX = $8311
            GL_DEPTH_PASS_INSTRUMENT_MAX_SGIX      = $8312

            ; GL_3DFX_texture_compression_FXT1

            GL_COMPRESSED_RGB_FXT1_3DFX  = $86B0
            GL_COMPRESSED_RGBA_FXT1_3DFX = $86B1

            ; GL_3DFX_multisample

            GL_MULTISAMPLE_3DFX	= $86B2
            GL_SAMPLE_BUFFERS_3DFX	= $86B3
            GL_SAMPLES_3DFX 	= $86B4
            GL_MULTISAMPLE_BIT_3DFX = $20000000

            ; GL_EXT_multisample

            GL_MULTISAMPLE_EXT	    = $809D
            GL_SAMPLE_ALPHA_TO_MASK_EXT = $809E
            GL_SAMPLE_ALPHA_TO_ONE_EXT  = $809F
            GL_SAMPLE_MASK_EXT	    = $80A0
            GL_1PASS_EXT		    = $80A1
            GL_2PASS_0_EXT		    = $80A2
            GL_2PASS_1_EXT		    = $80A3
            GL_4PASS_0_EXT		    = $80A4
            GL_4PASS_1_EXT		    = $80A5
            GL_4PASS_2_EXT		    = $80A6
            GL_4PASS_3_EXT		    = $80A7
            GL_SAMPLE_BUFFERS_EXT	    = $80A8
            GL_SAMPLES_EXT		    = $80A9
            GL_SAMPLE_MASK_VALUE_EXT    = $80AA
            GL_SAMPLE_MASK_INVERT_EXT   = $80AB
            GL_SAMPLE_PATTERN_EXT	    = $80AC

            ; GL_SGIX_vertex_preclip

            GL_VERTEX_PRECLIP_SGIX	    = $83EE
            GL_VERTEX_PRECLIP_HINT_SGIX = $83EF

            ; GL_SGIX_convolution_accuracy

            GL_CONVOLUTION_HINT_SGIX = $8316

            ; GL_SGIX_resample

            GL_PACK_RESAMPLE_SGIX	   = $842C
            GL_UNPACK_RESAMPLE_SGIX    = $842D
            GL_RESAMPLE_REPLICATE_SGIX = $842E
            GL_RESAMPLE_ZERO_FILL_SGIX = $842F
            GL_RESAMPLE_DECIMATE_SGIX  = $8430

            ; GL_SGIS_point_line_texgen

            GL_EYE_DISTANCE_TO_POINT_SGIS	 = $81F0
            GL_OBJECT_DISTANCE_TO_POINT_SGIS = $81F1
            GL_EYE_DISTANCE_TO_LINE_SGIS	 = $81F2
            GL_OBJECT_DISTANCE_TO_LINE_SGIS  = $81F3
            GL_EYE_POINT_SGIS		 = $81F4
            GL_OBJECT_POINT_SGIS		 = $81F5
            GL_EYE_LINE_SGIS		 = $81F6
            GL_OBJECT_LINE_SGIS		 = $81F7

            ; GL_SGIS_texture_color_mask

            GL_TEXTURE_COLOR_WRITEMASK_SGIS = $81EF

            ; GL_NV_vertex_program

            GL_VERTEX_PROGRAM_NV		   = $8620
            GL_VERTEX_STATE_PROGRAM_NV	   = $8621
            GL_ATTRIB_ARRAY_SIZE_NV 	   = $8623
            GL_ATTRIB_ARRAY_STRIDE_NV	   = $8624
            GL_ATTRIB_ARRAY_TYPE_NV 	   = $8625
            GL_CURRENT_ATTRIB_NV		   = $8626
            GL_PROGRAM_LENGTH_NV		   = $8627
            GL_PROGRAM_STRING_NV		   = $8628
            GL_MODELVIEW_PROJECTION_NV	   = $8629
            GL_IDENTITY_NV			   = $862A
            GL_INVERSE_NV			   = $862B
            GL_TRANSPOSE_NV 		   = $862C
            GL_INVERSE_TRANSPOSE_NV 	   = $862D
            GL_MAX_TRACK_MATRIX_STACK_DEPTH_NV = $862E
            GL_MAX_TRACK_MATRICES_NV	   = $862F
            GL_MATRIX0_NV			   = $8630
            GL_MATRIX1_NV			   = $8631
            GL_MATRIX2_NV			   = $8632
            GL_MATRIX3_NV			   = $8633
            GL_MATRIX4_NV			   = $8634
            GL_MATRIX5_NV			   = $8635
            GL_MATRIX6_NV			   = $8636
            GL_MATRIX7_NV			   = $8637
            GL_CURRENT_MATRIX_STACK_DEPTH_NV   = $8640
            GL_CURRENT_MATRIX_NV		   = $8641
            GL_VERTEX_PROGRAM_POINT_SIZE_NV    = $8642
            GL_VERTEX_PROGRAM_TWO_SIDE_NV	   = $8643
            GL_PROGRAM_PARAMETER_NV 	   = $8644
            GL_ATTRIB_ARRAY_POINTER_NV	   = $8645
            GL_PROGRAM_TARGET_NV		   = $8646
            GL_PROGRAM_RESIDENT_NV		   = $8647
            GL_TRACK_MATRIX_NV		   = $8648
            GL_TRACK_MATRIX_TRANSFORM_NV	   = $8649
            GL_VERTEX_PROGRAM_BINDING_NV	   = $864A
            GL_PROGRAM_ERROR_POSITION_NV	   = $864B
            GL_VERTEX_ATTRIB_ARRAY0_NV	   = $8650
            GL_VERTEX_ATTRIB_ARRAY1_NV	   = $8651
            GL_VERTEX_ATTRIB_ARRAY2_NV	   = $8652
            GL_VERTEX_ATTRIB_ARRAY3_NV	   = $8653
            GL_VERTEX_ATTRIB_ARRAY4_NV	   = $8654
            GL_VERTEX_ATTRIB_ARRAY5_NV	   = $8655
            GL_VERTEX_ATTRIB_ARRAY6_NV	   = $8656
            GL_VERTEX_ATTRIB_ARRAY7_NV	   = $8657
            GL_VERTEX_ATTRIB_ARRAY8_NV	   = $8658
            GL_VERTEX_ATTRIB_ARRAY9_NV	   = $8659
            GL_VERTEX_ATTRIB_ARRAY10_NV	   = $865A
            GL_VERTEX_ATTRIB_ARRAY11_NV	   = $865B
            GL_VERTEX_ATTRIB_ARRAY12_NV	   = $865C
            GL_VERTEX_ATTRIB_ARRAY13_NV	   = $865D
            GL_VERTEX_ATTRIB_ARRAY14_NV	   = $865E
            GL_VERTEX_ATTRIB_ARRAY15_NV	   = $865F
            GL_MAP1_VERTEX_ATTRIB0_4_NV	   = $8660
            GL_MAP1_VERTEX_ATTRIB1_4_NV	   = $8661
            GL_MAP1_VERTEX_ATTRIB2_4_NV	   = $8662
            GL_MAP1_VERTEX_ATTRIB3_4_NV	   = $8663
            GL_MAP1_VERTEX_ATTRIB4_4_NV	   = $8664
            GL_MAP1_VERTEX_ATTRIB5_4_NV	   = $8665
            GL_MAP1_VERTEX_ATTRIB6_4_NV	   = $8666
            GL_MAP1_VERTEX_ATTRIB7_4_NV	   = $8667
            GL_MAP1_VERTEX_ATTRIB8_4_NV	   = $8668
            GL_MAP1_VERTEX_ATTRIB9_4_NV	   = $8669
            GL_MAP1_VERTEX_ATTRIB10_4_NV	   = $866A
            GL_MAP1_VERTEX_ATTRIB11_4_NV	   = $866B
            GL_MAP1_VERTEX_ATTRIB12_4_NV	   = $866C
            GL_MAP1_VERTEX_ATTRIB13_4_NV	   = $866D
            GL_MAP1_VERTEX_ATTRIB14_4_NV	   = $866E
            GL_MAP1_VERTEX_ATTRIB15_4_NV	   = $866F
            GL_MAP2_VERTEX_ATTRIB0_4_NV	   = $8670
            GL_MAP2_VERTEX_ATTRIB1_4_NV	   = $8671
            GL_MAP2_VERTEX_ATTRIB2_4_NV	   = $8672
            GL_MAP2_VERTEX_ATTRIB3_4_NV	   = $8673
            GL_MAP2_VERTEX_ATTRIB4_4_NV	   = $8674
            GL_MAP2_VERTEX_ATTRIB5_4_NV	   = $8675
            GL_MAP2_VERTEX_ATTRIB6_4_NV	   = $8676
            GL_MAP2_VERTEX_ATTRIB7_4_NV	   = $8677
            GL_MAP2_VERTEX_ATTRIB8_4_NV	   = $8678
            GL_MAP2_VERTEX_ATTRIB9_4_NV	   = $8679
            GL_MAP2_VERTEX_ATTRIB10_4_NV	   = $867A
            GL_MAP2_VERTEX_ATTRIB11_4_NV	   = $867B
            GL_MAP2_VERTEX_ATTRIB12_4_NV	   = $867C
            GL_MAP2_VERTEX_ATTRIB13_4_NV	   = $867D
            GL_MAP2_VERTEX_ATTRIB14_4_NV	   = $867E
            GL_MAP2_VERTEX_ATTRIB15_4_NV	   = $867F

            ; GLU errors

            GLU_INVALID_ENUM	    = 100900
            GLU_INVALID_VALUE	    = 100901
            GLU_OUT_OF_MEMORY	    = 100902
            GLU_INCOMPATIBLE_GL_VERSION = 100903

            ; GLU boolean values

            GLU_TRUE  = GL_TRUE
            GLU_FALSE = GL_FALSE

            ; Quadric constants

            GLU_SMOOTH     = 100000
            GLU_FLAT       = 100001
            GLU_NONE       = 100002
            GLU_POINT      = 100010
            GLU_LINE       = 100011
            GLU_FILL       = 100012
            GLU_SILHOUETTE = 100013
            GLU_OUTSIDE    = 100020
            GLU_INSIDE     = 100021

            ; Tesselation constants

            GLU_TESS_MAX_COORD	       = 1.0e150
            GLU_TESS_WINDING_RULE	       = 100140
            GLU_TESS_BOUNDARY_ONLY	       = 100141
            GLU_TESS_TOLERANCE	       = 100142
            GLU_TESS_WINDING_ODD	       = 100130
            GLU_TESS_WINDING_NONZERO       = 100131
            GLU_TESS_WINDING_POSITIVE      = 100132
            GLU_TESS_WINDING_NEGATIVE      = 100133
            GLU_TESS_WINDING_ABS_GEQ_TWO   = 100134
            GLU_TESS_BEGIN		       = 100100
            GLU_TESS_VERTEX 	       = 100101
            GLU_TESS_END		       = 100102
            GLU_TESS_ERROR		       = 100103
            GLU_TESS_EDGE_FLAG	       = 100104
            GLU_TESS_COMBINE	       = 100105
            GLU_TESS_BEGIN_DATA	       = 100106
            GLU_TESS_VERTEX_DATA	       = 100107
            GLU_TESS_END_DATA	       = 100108
            GLU_TESS_ERROR_DATA	       = 100109
            GLU_TESS_EDGE_FLAG_DATA        = 100110
            GLU_TESS_COMBINE_DATA	       = 100111
            GLU_TESS_ERROR1 	       = 100151
            GLU_TESS_ERROR2 	       = 100152
            GLU_TESS_ERROR3 	       = 100153
            GLU_TESS_ERROR4 	       = 100154
            GLU_TESS_ERROR5 	       = 100155
            GLU_TESS_ERROR6 	       = 100156
            GLU_TESS_ERROR7 	       = 100157
            GLU_TESS_ERROR8 	       = 100158
            GLU_TESS_MISSING_BEGIN_POLYGON = GLU_TESS_ERROR1
            GLU_TESS_MISSING_BEGIN_CONTOUR = GLU_TESS_ERROR2
            GLU_TESS_MISSING_END_POLYGON   = GLU_TESS_ERROR3
            GLU_TESS_MISSING_END_CONTOUR   = GLU_TESS_ERROR4
            GLU_TESS_COORD_TOO_LARGE       = GLU_TESS_ERROR5
            GLU_TESS_NEED_COMBINE_CALLBACK = GLU_TESS_ERROR6

            ; NURBS constants

            GLU_AUTO_LOAD_MATRIX	  = 100200
            GLU_CULLING		  = 100201
            GLU_SAMPLING_TOLERANCE	  = 100203
            GLU_DISPLAY_MODE	  = 100204
            GLU_PARAMETRIC_TOLERANCE  = 100202
            GLU_SAMPLING_METHOD	  = 100205
            GLU_U_STEP		  = 100206
            GLU_V_STEP		  = 100207
            GLU_PATH_LENGTH 	  = 100215
            GLU_PARAMETRIC_ERROR	  = 100216
            GLU_DOMAIN_DISTANCE	  = 100217
            GLU_MAP1_TRIM_2 	  = 100210
            GLU_MAP1_TRIM_3 	  = 100211
            GLU_OUTLINE_POLYGON	  = 100240
            GLU_OUTLINE_PATCH	  = 100241
            GLU_NURBS_ERROR1	  = 100251
            GLU_NURBS_ERROR2	  = 100252
            GLU_NURBS_ERROR3	  = 100253
            GLU_NURBS_ERROR4	  = 100254
            GLU_NURBS_ERROR5	  = 100255
            GLU_NURBS_ERROR6	  = 100256
            GLU_NURBS_ERROR7	  = 100257
            GLU_NURBS_ERROR8	  = 100258
            GLU_NURBS_ERROR9	  = 100259
            GLU_NURBS_ERROR10	  = 100260
            GLU_NURBS_ERROR11	  = 100261
            GLU_NURBS_ERROR12	  = 100262
            GLU_NURBS_ERROR13	  = 100263
            GLU_NURBS_ERROR14	  = 100264
            GLU_NURBS_ERROR15	  = 100265
            GLU_NURBS_ERROR16	  = 100266
            GLU_NURBS_ERROR17	  = 100267
            GLU_NURBS_ERROR18	  = 100268
            GLU_NURBS_ERROR19	  = 100269
            GLU_NURBS_ERROR20	  = 100270
            GLU_NURBS_ERROR21	  = 100271
            GLU_NURBS_ERROR22	  = 100272
            GLU_NURBS_ERROR23	  = 100273
            GLU_NURBS_ERROR24	  = 100274
            GLU_NURBS_ERROR25	  = 100275
            GLU_NURBS_ERROR26	  = 100276
            GLU_NURBS_ERROR27	  = 100277
            GLU_NURBS_ERROR28	  = 100278
            GLU_NURBS_ERROR29	  = 100279
            GLU_NURBS_ERROR30	  = 100280
            GLU_NURBS_ERROR31	  = 100281
            GLU_NURBS_ERROR32	  = 100282
            GLU_NURBS_ERROR33	  = 100283
            GLU_NURBS_ERROR34	  = 100284
            GLU_NURBS_ERROR35	  = 100285
            GLU_NURBS_ERROR36	  = 100286
            GLU_NURBS_ERROR37	  = 100287

            ; Contours types

            GLU_CW	     = 100120
            GLU_CCW      = 100121
            GLU_INTERIOR = 100122
            GLU_EXTERIOR = 100123
            GLU_UNKNOWN  = 100124


gl.DEF

; Copyright 1992-1997 Silicon Graphics, Inc. All Rights Reserved.
; Transformed to this form by Sergey Chaban
; (C) hardCode 1999-2000
; http://bizarrecreations.webjump.com
; e-mail: soul_cat@mindless.com

IFNDEF _OPENGL_ASMDEF_
_OPENGL_ASMDEF_=1

GLenum		   TYPEDEF		DWORD
GLboolean	 TYPEDEF		BYTE
GLbitfield	TYPEDEF		DWORD
GLbyte		   TYPEDEF		SBYTE
GLshort  		TYPEDEF		SWORD
GLint		    TYPEDEF		DWORD
GLsizei  		TYPEDEF		DWORD
GLubyte		  TYPEDEF		BYTE
GLushort  	TYPEDEF		WORD
GLuint  	 	TYPEDEF		DWORD
GLfloat	  	TYPEDEF		REAL4
GLclampf	  TYPEDEF		REAL4  ; float clamped to [0,1]
GLdouble  	TYPEDEF		REAL8
GLclampd  	TYPEDEF		REAL8  ; double clamped to [0,1]

; GLvoid	TYPEDEF

; GL matrices are stored in column-major order.
; Members of this structure are named after D3DMATRIX members,
; what allows easy porting of matrix code from D3D.
glMATRIX STRUCT
_11 GLfloat ?
_21 GLfloat ?
_31 GLfloat ?
_41 GLfloat ?
_12 GLfloat ?
_22 GLfloat ?
_32 GLfloat ?
_42 GLfloat ?
_13 GLfloat ?
_23 GLfloat ?
_33 GLfloat ?
_43 GLfloat ?
_14 GLfloat ?
_24 GLfloat ?
_34 GLfloat ?
_44 GLfloat ?
glMATRIX ENDS

glVec3f struc
 x GLfloat ?
 y GLfloat ?
 z GLfloat ?
glVec3f ends

;/* Extensions */
GL_VERSION_1_1			EQU	1
GL_EXT_abgr			EQU	1
GL_EXT_bgra			EQU	1
GL_EXT_packed_pixels		EQU	1
GL_EXT_paletted_texture		EQU	1
GL_EXT_vertex_array		EQU	1
GL_SGI_compiled_vertex_array	EQU	1
GL_SGI_cull_vertex		EQU	1
GL_SGI_index_array_formats	EQU	1
GL_SGI_index_func		EQU	1
GL_SGI_index_material		EQU	1
GL_SGI_index_texture		EQU	1
GL_WIN_swap_hint		EQU	1

;/* AttribMask */
GL_CURRENT_BIT			EQU		000000001h
GL_POINT_BIT			EQU		000000002h
GL_LINE_BIT			EQU		000000004h
GL_POLYGON_BIT			EQU		000000008h
GL_POLYGON_STIPPLE_BIT		EQU		000000010h
GL_PIXEL_MODE_BIT		EQU		000000020h
GL_LIGHTING_BIT			EQU		000000040h
GL_FOG_BIT			EQU		000000080h
GL_DEPTH_BUFFER_BIT		EQU		000000100h
GL_ACCUM_BUFFER_BIT		EQU		000000200h
GL_STENCIL_BUFFER_BIT		EQU		000000400h
GL_VIEWPORT_BIT			EQU		000000800h
GL_TRANSFORM_BIT		EQU		000001000h
GL_ENABLE_BIT			EQU		000002000h
GL_COLOR_BUFFER_BIT		EQU		000004000h
GL_HINT_BIT			EQU		000008000h
GL_EVAL_BIT			EQU		000010000h
GL_LIST_BIT			EQU		000020000h
GL_TEXTURE_BIT			EQU		000040000h
GL_SCISSOR_BIT			EQU		000080000h
GL_ALL_ATTRIB_BITS		EQU		0000fffffh

;/* ClientAttribMask */
GL_CLIENT_PIXEL_STORE_BIT	EQU		000000001h
GL_CLIENT_VERTEX_ARRAY_BIT	EQU		000000002h
GL_CLIENT_ALL_ATTRIB_BITS	EQU		0ffffffffh

;/* Boolean */
GL_FALSE		EQU		0
GL_TRUE			EQU		1

;/* BeginMode */
GL_POINTS		EQU		000000000h
GL_LINES		EQU		000000001h
GL_LINE_LOOP		EQU		000000002h
GL_LINE_STRIP		EQU		000000003h
GL_TRIANGLES		EQU		000000004h
GL_TRIANGLE_STRIP	EQU		000000005h
GL_TRIANGLE_FAN		EQU		000000006h
GL_QUADS		EQU		000000007h
GL_QUAD_STRIP		EQU		000000008h
GL_POLYGON		EQU		000000009h

;/* AccumOp */
GL_ACCUM		EQU		000000100h
GL_LOAD			EQU		000000101h
GL_RETURN		EQU		000000102h
GL_MULT			EQU		000000103h
GL_ADD			EQU		000000104h

;/* AlphaFunction */
GL_NEVER		EQU		000000200h
GL_LESS			EQU		000000201h
GL_EQUAL		EQU		000000202h
GL_LEQUAL		EQU		000000203h
GL_GREATER		EQU		000000204h
GL_NOTEQUAL		EQU		000000205h
GL_GEQUAL		EQU		000000206h
GL_ALWAYS		EQU		000000207h

;/* BlendingFactorDest */
GL_ZERO			EQU		0
GL_ONE			EQU		1
GL_SRC_COLOR		EQU		000000300h
GL_ONE_MINUS_SRC_COLOR	EQU		000000301h
GL_SRC_ALPHA		EQU		000000302h
GL_ONE_MINUS_SRC_ALPHA	EQU		000000303h
GL_DST_ALPHA		EQU		000000304h
GL_ONE_MINUS_DST_ALPHA	EQU		000000305h

;/* BlendingFactorSrc */
GL_DST_COLOR		EQU		000000306h
GL_ONE_MINUS_DST_COLOR	EQU		000000307h
GL_SRC_ALPHA_SATURATE		EQU		000000308h

;/* DrawBufferMode */
GL_NONE			EQU		0
GL_FRONT_LEFT		EQU		000000400h
GL_FRONT_RIGHT		EQU		000000401h
GL_BACK_LEFT		EQU		000000402h
GL_BACK_RIGHT		EQU		000000403h
GL_FRONT		EQU		000000404h
GL_BACK			EQU		000000405h
GL_LEFT			EQU		000000406h
GL_RIGHT		EQU		000000407h
GL_FRONT_AND_BACK	EQU		000000408h
GL_AUX0			EQU		000000409h
GL_AUX1			EQU		00000040ah
GL_AUX2			EQU		00000040bh
GL_AUX3			EQU		00000040ch

;/* ErrorCode */
GL_NO_ERROR		EQU		0
GL_INVALID_ENUM		EQU		000000500h
GL_INVALID_VALUE	EQU		000000501h
GL_INVALID_OPERATION	EQU		000000502h
GL_STACK_OVERFLOW	EQU		000000503h
GL_STACK_UNDERFLOW	EQU		000000504h
GL_OUT_OF_MEMORY	EQU		000000505h

;/* FeedbackType */
GL_2D			EQU		000000600h
GL_3D			EQU		000000601h
GL_3D_COLOR		EQU		000000602h
GL_3D_COLOR_TEXTURE	EQU		000000603h
GL_4D_COLOR_TEXTURE	EQU		000000604h

;/* FeedBackToken */
GL_PASS_THROUGH_TOKEN	EQU		000000700h
GL_POINT_TOKEN		EQU		000000701h
GL_LINE_TOKEN		EQU		000000702h
GL_POLYGON_TOKEN	EQU		000000703h
GL_BITMAP_TOKEN		EQU		000000704h
GL_DRAW_PIXEL_TOKEN	EQU		000000705h
GL_COPY_PIXEL_TOKEN	EQU		000000706h
GL_LINE_RESET_TOKEN	EQU		000000707h

;/* FogMode */
;GL_LINEAR
GL_EXP			EQU		000000800h
GL_EXP2			EQU		000000801h
;/* FogParameter */
;      GL_FOG_COLOR
;      GL_FOG_DENSITY
;      GL_FOG_END
;      GL_FOG_INDEX
;      GL_FOG_MODE
;      GL_FOG_START

;/* FrontFaceDirection */
GL_CW			EQU		000000900h
GL_CCW			EQU		000000901h

;/* GetMapQuery */
GL_COEFF		EQU		000000a00h
GL_ORDER		EQU		000000a01h
GL_DOMAIN		EQU		000000a02h

;/* GetPixelMap */
GL_PIXEL_MAP_I_TO_I	EQU		000000c70h
GL_PIXEL_MAP_S_TO_S	EQU		000000c71h
GL_PIXEL_MAP_I_TO_R	EQU		000000c72h
GL_PIXEL_MAP_I_TO_G	EQU		000000c73h
GL_PIXEL_MAP_I_TO_B	EQU		000000c74h
GL_PIXEL_MAP_I_TO_A	EQU		000000c75h
GL_PIXEL_MAP_R_TO_R	EQU		000000c76h
GL_PIXEL_MAP_G_TO_G	EQU		000000c77h
GL_PIXEL_MAP_B_TO_B	EQU		000000c78h
GL_PIXEL_MAP_A_TO_A	EQU		000000c79h

;/* GetPointervPName */
GL_VERTEX_ARRAY_POINTER		EQU	00000808eh
GL_NORMAL_ARRAY_POINTER		EQU	00000808fh
GL_COLOR_ARRAY_POINTER		EQU	000008090h
GL_INDEX_ARRAY_POINTER		EQU	000008091h
GL_TEXTURE_COORD_ARRAY_POINTER	EQU	000008092h
GL_EDGE_FLAG_ARRAY_POINTER	EQU	000008093h

;/* GetPName */
GL_CURRENT_COLOR			EQU		000000b00h
GL_CURRENT_INDEX			EQU		000000b01h
GL_CURRENT_NORMAL			EQU		000000b02h
GL_CURRENT_TEXTURE_COORDS		EQU		000000b03h
GL_CURRENT_RASTER_COLOR			EQU		000000b04h
GL_CURRENT_RASTER_INDEX			EQU		000000b05h
GL_CURRENT_RASTER_TEXTURE_COORDS	EQU		000000b06h
GL_CURRENT_RASTER_POSITION		EQU		000000b07h
GL_CURRENT_RASTER_POSITION_VALID	EQU		000000b08h
GL_CURRENT_RASTER_DISTANCE		EQU		000000b09h
GL_POINT_SMOOTH				EQU		000000b10h
GL_POINT_SIZE				EQU		000000b11h
GL_POINT_SIZE_RANGE			EQU		000000b12h
GL_POINT_SIZE_GRANULARITY		EQU		000000b13h
GL_LINE_SMOOTH				EQU		000000b20h
GL_LINE_WIDTH				EQU		000000b21h
GL_LINE_WIDTH_RANGE			EQU		000000b22h
GL_LINE_WIDTH_GRANULARITY		EQU		000000b23h
GL_LINE_STIPPLE				EQU		000000b24h
GL_LINE_STIPPLE_PATTERN			EQU		000000b25h
GL_LINE_STIPPLE_REPEAT			EQU		000000b26h
GL_LIST_MODE				EQU		000000b30h
GL_MAX_LIST_NESTING			EQU		000000b31h
GL_LIST_BASE				EQU		000000b32h
GL_LIST_INDEX				EQU		000000b33h
GL_POLYGON_MODE				EQU		000000b40h
GL_POLYGON_SMOOTH			EQU		000000b41h
GL_POLYGON_STIPPLE			EQU		000000b42h
GL_EDGE_FLAG				EQU		000000b43h
GL_CULL_FACE				EQU		000000b44h
GL_CULL_FACE_MODE			EQU		000000b45h
GL_FRONT_FACE				EQU		000000b46h
GL_LIGHTING				EQU		000000b50h
GL_LIGHT_MODEL_LOCAL_VIEWER		EQU		000000b51h
GL_LIGHT_MODEL_TWO_SIDE			EQU		000000b52h
GL_LIGHT_MODEL_AMBIENT			EQU		000000b53h
GL_SHADE_MODEL				EQU		000000b54h
GL_COLOR_MATERIAL_FACE			EQU		000000b55h
GL_COLOR_MATERIAL_PARAMETER		EQU		000000b56h
GL_COLOR_MATERIAL			EQU		000000b57h
GL_FOG					EQU		000000b60h
GL_FOG_INDEX				EQU		000000b61h
GL_FOG_DENSITY				EQU		000000b62h
GL_FOG_START				EQU		000000b63h
GL_FOG_END				EQU		000000b64h
GL_FOG_MODE				EQU		000000b65h
GL_FOG_COLOR				EQU		000000b66h
GL_DEPTH_RANGE				EQU		000000b70h
GL_DEPTH_TEST				EQU		000000b71h
GL_DEPTH_WRITEMASK			EQU		000000b72h
GL_DEPTH_CLEAR_VALUE			EQU		000000b73h
GL_DEPTH_FUNC				EQU		000000b74h
GL_ACCUM_CLEAR_VALUE			EQU		000000b80h
GL_STENCIL_TEST				EQU		000000b90h
GL_STENCIL_CLEAR_VALUE			EQU		000000b91h
GL_STENCIL_FUNC				EQU		000000b92h
GL_STENCIL_VALUE_MASK			EQU		000000b93h
GL_STENCIL_FAIL				EQU		000000b94h
GL_STENCIL_PASS_DEPTH_FAIL		EQU		000000b95h
GL_STENCIL_PASS_DEPTH_PASS		EQU		000000b96h
GL_STENCIL_REF				EQU		000000b97h
GL_STENCIL_WRITEMASK			EQU		000000b98h
GL_MATRIX_MODE				EQU		000000ba0h
GL_NORMALIZE				EQU		000000ba1h
GL_VIEWPORT				EQU		000000ba2h
GL_MODELVIEW_STACK_DEPTH		EQU		000000ba3h
GL_PROJECTION_STACK_DEPTH		EQU		000000ba4h
GL_TEXTURE_STACK_DEPTH			EQU		000000ba5h
GL_MODELVIEW_MATRIX			EQU		000000ba6h
GL_PROJECTION_MATRIX			EQU		000000ba7h
GL_TEXTURE_MATRIX			EQU		000000ba8h
GL_ATTRIB_STACK_DEPTH			EQU		000000bb0h
GL_CLIENT_ATTRIB_STACK_DEPTH		EQU		000000bb1h
GL_ALPHA_TEST				EQU		000000bc0h
GL_ALPHA_TEST_FUNC			EQU		000000bc1h
GL_ALPHA_TEST_REF			EQU		000000bc2h
GL_DITHER				EQU		000000bd0h
GL_BLEND_DST				EQU		000000be0h
GL_BLEND_SRC				EQU		000000be1h
GL_BLEND				EQU		000000be2h
GL_LOGIC_OP_MODE			EQU		000000bf0h
GL_INDEX_LOGIC_OP			EQU		000000bf1h
GL_LOGIC_OP				EQU		GL_INDEX_LOGIC_OP
GL_COLOR_LOGIC_OP			EQU		000000bf2h
GL_AUX_BUFFERS				EQU		000000c00h
GL_DRAW_BUFFER				EQU		000000c01h
GL_READ_BUFFER				EQU		000000c02h
GL_SCISSOR_BOX				EQU		000000c10h
GL_SCISSOR_TEST				EQU		000000c11h
GL_INDEX_CLEAR_VALUE			EQU		000000c20h
GL_INDEX_WRITEMASK			EQU		000000c21h
GL_COLOR_CLEAR_VALUE			EQU		000000c22h
GL_COLOR_WRITEMASK			EQU		000000c23h
GL_INDEX_MODE				EQU		000000c30h
GL_RGBA_MODE				EQU		000000c31h
GL_DOUBLEBUFFER				EQU		000000c32h
GL_STEREO				EQU		000000c33h
GL_RENDER_MODE				EQU		000000c40h
GL_PERSPECTIVE_CORRECTION_HINT		EQU		000000c50h
GL_POINT_SMOOTH_HINT			EQU		000000c51h
GL_LINE_SMOOTH_HINT			EQU		000000c52h
GL_POLYGON_SMOOTH_HINT			EQU		000000c53h
GL_FOG_HINT				EQU		000000c54h
GL_TEXTURE_GEN_S			EQU		000000c60h
GL_TEXTURE_GEN_T			EQU		000000c61h
GL_TEXTURE_GEN_R			EQU		000000c62h
GL_TEXTURE_GEN_Q			EQU		000000c63h
GL_PIXEL_MAP_I_TO_I_SIZE		EQU		000000cb0h
GL_PIXEL_MAP_S_TO_S_SIZE		EQU		000000cb1h
GL_PIXEL_MAP_I_TO_R_SIZE		EQU		000000cb2h
GL_PIXEL_MAP_I_TO_G_SIZE		EQU		000000cb3h
GL_PIXEL_MAP_I_TO_B_SIZE		EQU		000000cb4h
GL_PIXEL_MAP_I_TO_A_SIZE		EQU		000000cb5h
GL_PIXEL_MAP_R_TO_R_SIZE		EQU		000000cb6h
GL_PIXEL_MAP_G_TO_G_SIZE		EQU		000000cb7h
GL_PIXEL_MAP_B_TO_B_SIZE		EQU		000000cb8h
GL_PIXEL_MAP_A_TO_A_SIZE		EQU		000000cb9h
GL_UNPACK_SWAP_BYTES			EQU		000000cf0h
GL_UNPACK_LSB_FIRST			EQU		000000cf1h
GL_UNPACK_ROW_LENGTH			EQU		000000cf2h
GL_UNPACK_SKIP_ROWS			EQU		000000cf3h
GL_UNPACK_SKIP_PIXELS			EQU		000000cf4h
GL_UNPACK_ALIGNMENT			EQU		000000cf5h
GL_PACK_SWAP_BYTES			EQU		000000d00h
GL_PACK_LSB_FIRST			EQU		000000d01h
GL_PACK_ROW_LENGTH			EQU		000000d02h
GL_PACK_SKIP_ROWS			EQU		000000d03h
GL_PACK_SKIP_PIXELS			EQU		000000d04h
GL_PACK_ALIGNMENT			EQU		000000d05h
GL_MAP_COLOR				EQU		000000d10h
GL_MAP_STENCIL				EQU		000000d11h
GL_INDEX_SHIFT				EQU		000000d12h
GL_INDEX_OFFSET				EQU		000000d13h
GL_RED_SCALE				EQU		000000d14h
GL_RED_BIAS				EQU		000000d15h
GL_ZOOM_X				EQU		000000d16h
GL_ZOOM_Y				EQU		000000d17h
GL_GREEN_SCALE				EQU		000000d18h
GL_GREEN_BIAS				EQU		000000d19h
GL_BLUE_SCALE				EQU		000000d1ah
GL_BLUE_BIAS				EQU		000000d1bh
GL_ALPHA_SCALE				EQU		000000d1ch
GL_ALPHA_BIAS				EQU		000000d1dh
GL_DEPTH_SCALE				EQU		000000d1eh
GL_DEPTH_BIAS				EQU		000000d1fh
GL_MAX_EVAL_ORDER			EQU		000000d30h
GL_MAX_LIGHTS				EQU		000000d31h
GL_MAX_CLIP_PLANES			EQU		000000d32h
GL_MAX_TEXTURE_SIZE			EQU		000000d33h
GL_MAX_PIXEL_MAP_TABLE			EQU		000000d34h
GL_MAX_ATTRIB_STACK_DEPTH		EQU		000000d35h
GL_MAX_MODELVIEW_STACK_DEPTH		EQU		000000d36h
GL_MAX_NAME_STACK_DEPTH			EQU		000000d37h
GL_MAX_PROJECTION_STACK_DEPTH		EQU		000000d38h
GL_MAX_TEXTURE_STACK_DEPTH		EQU		000000d39h
GL_MAX_VIEWPORT_DIMS			EQU		000000d3ah
GL_MAX_CLIENT_ATTRIB_STACK_DEPTH	EQU		000000d3bh
GL_SUBPIXEL_BITS			EQU		000000d50h
GL_INDEX_BITS				EQU		000000d51h
GL_RED_BITS				EQU		000000d52h
GL_GREEN_BITS				EQU		000000d53h
GL_BLUE_BITS				EQU		000000d54h
GL_ALPHA_BITS				EQU		000000d55h
GL_DEPTH_BITS				EQU		000000d56h
GL_STENCIL_BITS				EQU		000000d57h
GL_ACCUM_RED_BITS			EQU		000000d58h
GL_ACCUM_GREEN_BITS			EQU		000000d59h
GL_ACCUM_BLUE_BITS			EQU		000000d5ah
GL_ACCUM_ALPHA_BITS			EQU		000000d5bh
GL_NAME_STACK_DEPTH			EQU		000000d70h
GL_AUTO_NORMAL				EQU		000000d80h
GL_MAP1_COLOR_4				EQU		000000d90h
GL_MAP1_INDEX				EQU		000000d91h
GL_MAP1_NORMAL				EQU		000000d92h
GL_MAP1_TEXTURE_COORD_1			EQU		000000d93h
GL_MAP1_TEXTURE_COORD_2			EQU		000000d94h
GL_MAP1_TEXTURE_COORD_3			EQU		000000d95h
GL_MAP1_TEXTURE_COORD_4			EQU		000000d96h
GL_MAP1_VERTEX_3			EQU		000000d97h
GL_MAP1_VERTEX_4			EQU		000000d98h
GL_MAP2_COLOR_4				EQU		000000db0h
GL_MAP2_INDEX				EQU		000000db1h
GL_MAP2_NORMAL				EQU		000000db2h
GL_MAP2_TEXTURE_COORD_1			EQU		000000db3h
GL_MAP2_TEXTURE_COORD_2			EQU		000000db4h
GL_MAP2_TEXTURE_COORD_3			EQU		000000db5h
GL_MAP2_TEXTURE_COORD_4			EQU		000000db6h
GL_MAP2_VERTEX_3			EQU		000000db7h
GL_MAP2_VERTEX_4			EQU		000000db8h
GL_MAP1_GRID_DOMAIN			EQU		000000dd0h
GL_MAP1_GRID_SEGMENTS			EQU		000000dd1h
GL_MAP2_GRID_DOMAIN			EQU		000000dd2h
GL_MAP2_GRID_SEGMENTS			EQU		000000dd3h
GL_TEXTURE_1D				EQU		000000de0h
GL_TEXTURE_2D				EQU		000000de1h
GL_FEEDBACK_BUFFER_POINTER		EQU		000000df0h
GL_FEEDBACK_BUFFER_SIZE			EQU		000000df1h
GL_FEEDBACK_BUFFER_TYPE			EQU		000000df2h
GL_SELECTION_BUFFER_POINTER		EQU		000000df3h
GL_SELECTION_BUFFER_SIZE		EQU		000000df4h
GL_POLYGON_OFFSET_UNITS			EQU		000002a00h
GL_POLYGON_OFFSET_POINT			EQU		000002a01h
GL_POLYGON_OFFSET_LINE			EQU		000002a02h
GL_POLYGON_OFFSET_FILL			EQU		000008037h
GL_POLYGON_OFFSET_FACTOR		EQU		000008038h
GL_TEXTURE_BINDING_1D			EQU		000008068h
GL_TEXTURE_BINDING_2D			EQU		000008069h
GL_VERTEX_ARRAY				EQU		000008074h
GL_NORMAL_ARRAY				EQU		000008075h
GL_COLOR_ARRAY				EQU		000008076h
GL_INDEX_ARRAY				EQU		000008077h
GL_TEXTURE_COORD_ARRAY			EQU		000008078h
GL_EDGE_FLAG_ARRAY			EQU		000008079h
GL_VERTEX_ARRAY_SIZE			EQU		00000807ah
GL_VERTEX_ARRAY_TYPE			EQU		00000807bh
GL_VERTEX_ARRAY_STRIDE			EQU		00000807ch
GL_NORMAL_ARRAY_TYPE			EQU		00000807eh
GL_NORMAL_ARRAY_STRIDE			EQU		00000807fh
GL_COLOR_ARRAY_SIZE			EQU		000008081h
GL_COLOR_ARRAY_TYPE			EQU		000008082h
GL_COLOR_ARRAY_STRIDE			EQU		000008083h
GL_INDEX_ARRAY_TYPE			EQU		000008085h
GL_INDEX_ARRAY_STRIDE			EQU		000008086h
GL_TEXTURE_COORD_ARRAY_SIZE		EQU		000008088h
GL_TEXTURE_COORD_ARRAY_TYPE		EQU		000008089h
GL_TEXTURE_COORD_ARRAY_STRIDE		EQU		00000808ah
GL_EDGE_FLAG_ARRAY_STRIDE		EQU		00000808ch

;/* GetTextureParameter */
GL_TEXTURE_WIDTH		EQU		000001000h
GL_TEXTURE_HEIGHT		EQU		000001001h
GL_TEXTURE_INTERNAL_FORMAT	EQU		000001003h
GL_TEXTURE_COMPONENTS		EQU		000001003h
GL_TEXTURE_BORDER_COLOR		EQU		000001004h
GL_TEXTURE_BORDER		EQU		000001005h
GL_TEXTURE_RED_SIZE		EQU		00000805ch
GL_TEXTURE_GREEN_SIZE		EQU		00000805dh
GL_TEXTURE_BLUE_SIZE		EQU		00000805eh
GL_TEXTURE_ALPHA_SIZE		EQU		00000805fh
GL_TEXTURE_LUMINANCE_SIZE	EQU		000008060h
GL_TEXTURE_INTENSITY_SIZE	EQU		000008061h
GL_TEXTURE_PRIORITY		EQU		000008066h
GL_TEXTURE_RESIDENT		EQU		000008067h

;/* HintMode */
GL_DONT_CARE			EQU		000001100h
GL_FASTEST			EQU		000001101h
GL_NICEST			EQU		000001102h

;/* LightParameter */
GL_AMBIENT			EQU		000001200h
GL_DIFFUSE			EQU		000001201h
GL_SPECULAR			EQU		000001202h
GL_POSITION			EQU		000001203h
GL_SPOT_DIRECTION		EQU		000001204h
GL_SPOT_EXPONENT		EQU		000001205h
GL_SPOT_CUTOFF			EQU		000001206h
GL_CONSTANT_ATTENUATION		EQU		000001207h
GL_LINEAR_ATTENUATION		EQU		000001208h
GL_QUADRATIC_ATTENUATION	EQU		000001209h

;/* ListMode */
GL_COMPILE			EQU		000001300h
GL_COMPILE_AND_EXECUTE		EQU		000001301h

;/* DataType */
GL_BYTE				EQU		000001400h
GL_UNSIGNED_BYTE		EQU		000001401h
GL_SHORT			EQU		000001402h
GL_UNSIGNED_SHORT		EQU		000001403h
GL_INT				EQU		000001404h
GL_UNSIGNED_INT			EQU		000001405h
GL_FLOAT			EQU		000001406h
GL_2_BYTES			EQU		000001407h
GL_3_BYTES			EQU		000001408h
GL_4_BYTES			EQU		000001409h
GL_DOUBLE			EQU		00000140ah
GL_DOUBLE_EXT			EQU		00000140ah

;/* ListNameType */
;  GL_BYTE
;  GL_UNSIGNED_BYTE */
;  GL_SHORT */
;  GL_UNSIGNED_SHORT */
;  GL_INT */
;  GL_UNSIGNED_INT */
;  GL_FLOAT */
;  GL_2_BYTES */
;  GL_3_BYTES */
;  GL_4_BYTES */

;/* LogicOp */
GL_CLEAR			EQU		000001500h
GL_AND				EQU		000001501h
GL_AND_REVERSE			EQU		000001502h
GL_COPY				EQU		000001503h
GL_AND_INVERTED			EQU		000001504h
GL_NOOP				EQU		000001505h
GL_XOR				EQU		000001506h
GL_OR				EQU		000001507h
GL_NOR				EQU		000001508h
GL_EQUIV			EQU		000001509h
GL_INVERT			EQU		00000150ah
GL_OR_REVERSE			EQU		00000150bh
GL_COPY_INVERTED		EQU		00000150ch
GL_OR_INVERTED			EQU		00000150dh
GL_NAND				EQU		00000150eh
GL_SET				EQU		00000150fh

;/* MaterialParameter */
GL_EMISSION		EQU		000001600h
GL_SHININESS		EQU		000001601h
GL_AMBIENT_AND_DIFFUSE	EQU		000001602h
GL_COLOR_INDEXES	EQU		000001603h
; GL_AMBIENT
; GL_DIFFUSE
; GL_SPECULAR

;/* MatrixMode */
GL_MODELVIEW		EQU		000001700h
GL_PROJECTION		EQU		000001701h
GL_TEXTURE		EQU		000001702h

COMMENT ~
;/* MeshMode1 */
     GL_POINT
     GL_LINE

;/* MeshMode2 */
;    GL_POINT
;    GL_LINE
;    GL_FILL

;/* NormalPointerType */
;    GL_BYTE
;    GL_SHORT
;    GL_INT
;    GL_FLOAT
;    GL_DOUBLE
~

;/* PixelCopyType */
GL_COLOR		EQU		000001800h
GL_DEPTH		EQU		000001801h
GL_STENCIL		EQU		000001802h

;/* PixelFormat */
GL_COLOR_INDEX		EQU		000001900h
GL_STENCIL_INDEX	EQU		000001901h
GL_DEPTH_COMPONENT	EQU		000001902h
GL_RED			EQU		000001903h
GL_GREEN		EQU		000001904h
GL_BLUE			EQU		000001905h
GL_ALPHA		EQU		000001906h
GL_RGB			EQU		000001907h
GL_RGBA			EQU		000001908h
GL_LUMINANCE		EQU		000001909h
GL_LUMINANCE_ALPHA	EQU		00000190ah

;/* PixelType */
GL_BITMAP		EQU		000001a00h

;/* PolygonMode */
GL_POINT		EQU		000001b00h
GL_LINE			EQU		000001b01h
GL_FILL			EQU		000001b02h

;/* RenderingMode */
GL_RENDER		EQU		000001c00h
GL_FEEDBACK		EQU		000001c01h
GL_SELECT		EQU		000001c02h

;/* ShadingModel */
GL_FLAT			EQU		000001d00h
GL_SMOOTH		EQU		000001d01h

;/* StencilOp */
; GL_ZERO
GL_KEEP			EQU		000001e00h
GL_REPLACE		EQU		000001e01h
GL_INCR			EQU		000001e02h
GL_DECR			EQU		000001e03h
; GL_INVERT

;/* StringName */
GL_VENDOR		EQU		000001f00h
GL_RENDERER		EQU		000001f01h
GL_VERSION		EQU		000001f02h
GL_EXTENSIONS		EQU		000001f03h

;/* TextureCoordName */
GL_S			EQU		000002000h
GL_T			EQU		000002001h
GL_R			EQU		000002002h
GL_Q			EQU		000002003h

;/* TextureEnvMode */
GL_MODULATE		EQU		000002100h
GL_DECAL		EQU		000002101h
; GL_BLEND
; GL_REPLACE
; GL_ADD

;/* TextureEnvParameter */
GL_TEXTURE_ENV_MODE		EQU		000002200h
GL_TEXTURE_ENV_COLOR		EQU		000002201h

;/* TextureEnvTarget */
GL_TEXTURE_ENV		EQU		000002300h

;/* TextureGenMode */
GL_EYE_LINEAR		EQU		000002400h
GL_OBJECT_LINEAR	EQU		000002401h
GL_SPHERE_MAP		EQU		000002402h

;/* TextureGenParameter */
GL_TEXTURE_GEN_MODE	EQU		000002500h
GL_OBJECT_PLANE		EQU		000002501h
GL_EYE_PLANE		EQU		000002502h

;/* TextureMagFilter */
GL_NEAREST		EQU		000002600h
GL_LINEAR		EQU		000002601h

;/* TextureMinFilter */
; GL_NEAREST
;  GL_LINEAR
GL_NEAREST_MIPMAP_NEAREST	EQU		000002700h
GL_LINEAR_MIPMAP_NEAREST	EQU		000002701h
GL_NEAREST_MIPMAP_LINEAR	EQU		000002702h
GL_LINEAR_MIPMAP_LINEAR		EQU		000002703h

;/* TextureParameterName */
GL_TEXTURE_MAG_FILTER	EQU		000002800h
GL_TEXTURE_MIN_FILTER	EQU		000002801h
GL_TEXTURE_WRAP_S	EQU		000002802h
GL_TEXTURE_WRAP_T	EQU		000002803h

;/* TextureTarget */
GL_PROXY_TEXTURE_1D	EQU		000008063h
GL_PROXY_TEXTURE_2D	EQU		000008064h

;/* TextureWrapMode */
GL_CLAMP		EQU		000002900h
GL_REPEAT		EQU		000002901h

;/* PixelInternalFormat */
GL_R3_G3_B2		EQU		000002a10h
GL_ALPHA4		EQU		00000803bh
GL_ALPHA8		EQU		00000803ch
GL_ALPHA12		EQU		00000803dh
GL_ALPHA16		EQU		00000803eh
GL_LUMINANCE4		EQU		00000803fh
GL_LUMINANCE8		EQU		000008040h
GL_LUMINANCE12		EQU		000008041h
GL_LUMINANCE16		EQU		000008042h
GL_LUMINANCE4_ALPHA4	EQU		000008043h
GL_LUMINANCE6_ALPHA2	EQU		000008044h
GL_LUMINANCE8_ALPHA8	EQU		000008045h
GL_LUMINANCE12_ALPHA4	EQU		000008046h
GL_LUMINANCE12_ALPHA12	EQU		000008047h
GL_LUMINANCE16_ALPHA16	EQU		000008048h
GL_INTENSITY		EQU		000008049h
GL_INTENSITY4		EQU		00000804ah
GL_INTENSITY8		EQU		00000804bh
GL_INTENSITY12		EQU		00000804ch
GL_INTENSITY16		EQU		00000804dh
GL_RGB4			EQU		00000804fh
GL_RGB5			EQU		000008050h
GL_RGB8			EQU		000008051h
GL_RGB10		EQU		000008052h
GL_RGB12		EQU		000008053h
GL_RGB16		EQU		000008054h
GL_RGBA2		EQU		000008055h
GL_RGBA4		EQU		000008056h
GL_RGB5_A1		EQU		000008057h
GL_RGBA8		EQU		000008058h
GL_RGB10_A2		EQU		000008059h
GL_RGBA12		EQU		00000805ah
GL_RGBA16		EQU		00000805bh

;/* InterleavedArrayFormat */
GL_V2F			EQU		000002a20h
GL_V3F			EQU		000002a21h
GL_C4UB_V2F		EQU		000002a22h
GL_C4UB_V3F		EQU		000002a23h
GL_C3F_V3F		EQU		000002a24h
GL_N3F_V3F		EQU		000002a25h
GL_C4F_N3F_V3F		EQU		000002a26h
GL_T2F_V3F		EQU		000002a27h
GL_T4F_V4F		EQU		000002a28h
GL_T2F_C4UB_V3F		EQU		000002a29h
GL_T2F_C3F_V3F		EQU		000002a2ah
GL_T2F_N3F_V3F		EQU		000002a2bh
GL_T2F_C4F_N3F_V3F	EQU		000002a2ch
GL_T4F_C4F_N3F_V4F	EQU		000002a2dh

;/* ClipPlaneName */
GL_CLIP_PLANE0		EQU		000003000h
GL_CLIP_PLANE1		EQU		000003001h
GL_CLIP_PLANE2		EQU		000003002h
GL_CLIP_PLANE3		EQU		000003003h
GL_CLIP_PLANE4		EQU		000003004h
GL_CLIP_PLANE5		EQU		000003005h

;/* LightName */
GL_LIGHT0		EQU		000004000h
GL_LIGHT1		EQU		000004001h
GL_LIGHT2		EQU		000004002h
GL_LIGHT3		EQU		000004003h
GL_LIGHT4		EQU		000004004h
GL_LIGHT5		EQU		000004005h
GL_LIGHT6		EQU		000004006h
GL_LIGHT7		EQU		000004007h

;/* EXT_abgr */
GL_ABGR_EXT		EQU		000008000h

;/* EXT_packed_pixels */
GL_UNSIGNED_BYTE_3_3_2_EXT		EQU		000008032h
GL_UNSIGNED_SHORT_4_4_4_4_EXT		EQU		000008033h
GL_UNSIGNED_SHORT_5_5_5_1_EXT		EQU		000008034h
GL_UNSIGNED_INT_8_8_8_8_EXT		EQU		000008035h
GL_UNSIGNED_INT_10_10_10_2_EXT		EQU		000008036h

;/* EXT_vertex_array */
GL_VERTEX_ARRAY_EXT			EQU		000008074h
GL_NORMAL_ARRAY_EXT			EQU		000008075h
GL_COLOR_ARRAY_EXT			EQU		000008076h
GL_INDEX_ARRAY_EXT			EQU		000008077h
GL_TEXTURE_COORD_ARRAY_EXT		EQU		000008078h
GL_EDGE_FLAG_ARRAY_EXT			EQU		000008079h
GL_VERTEX_ARRAY_SIZE_EXT		EQU		00000807ah
GL_VERTEX_ARRAY_TYPE_EXT		EQU		00000807bh
GL_VERTEX_ARRAY_STRIDE_EXT		EQU		00000807ch
GL_VERTEX_ARRAY_COUNT_EXT		EQU		00000807dh
GL_NORMAL_ARRAY_TYPE_EXT		EQU		00000807eh
GL_NORMAL_ARRAY_STRIDE_EXT		EQU		00000807fh
GL_NORMAL_ARRAY_COUNT_EXT		EQU		000008080h
GL_COLOR_ARRAY_SIZE_EXT			EQU		000008081h
GL_COLOR_ARRAY_TYPE_EXT			EQU		000008082h
GL_COLOR_ARRAY_STRIDE_EXT		EQU		000008083h
GL_COLOR_ARRAY_COUNT_EXT		EQU		000008084h
GL_INDEX_ARRAY_TYPE_EXT			EQU		000008085h
GL_INDEX_ARRAY_STRIDE_EXT		EQU		000008086h
GL_INDEX_ARRAY_COUNT_EXT		EQU		000008087h
GL_TEXTURE_COORD_ARRAY_SIZE_EXT		EQU		000008088h
GL_TEXTURE_COORD_ARRAY_TYPE_EXT		EQU		000008089h
GL_TEXTURE_COORD_ARRAY_STRIDE_EXT	EQU		00000808ah
GL_TEXTURE_COORD_ARRAY_COUNT_EXT	EQU		00000808bh
GL_EDGE_FLAG_ARRAY_STRIDE_EXT		EQU		00000808ch
GL_EDGE_FLAG_ARRAY_COUNT_EXT		EQU		00000808dh
GL_VERTEX_ARRAY_POINTER_EXT		EQU		00000808eh
GL_NORMAL_ARRAY_POINTER_EXT		EQU		00000808fh
GL_COLOR_ARRAY_POINTER_EXT		EQU		000008090h
GL_INDEX_ARRAY_POINTER_EXT		EQU		000008091h
GL_TEXTURE_COORD_ARRAY_POINTER_EXT	EQU		000008092h
GL_EDGE_FLAG_ARRAY_POINTER_EXT		EQU		000008093h

;/* EXT_color_table */
GL_TABLE_TOO_LARGE_EXT			EQU		000008031h
GL_COLOR_TABLE_FORMAT_EXT		EQU		0000080d8h
GL_COLOR_TABLE_WIDTH_EXT		EQU		0000080d9h
GL_COLOR_TABLE_RED_SIZE_EXT		EQU		0000080dah
GL_COLOR_TABLE_GREEN_SIZE_EXT		EQU		0000080dbh
GL_COLOR_TABLE_BLUE_SIZE_EXT		EQU		0000080dch
GL_COLOR_TABLE_ALPHA_SIZE_EXT		EQU		0000080ddh
GL_COLOR_TABLE_LUMINANCE_SIZE_EXT	EQU		0000080deh
GL_COLOR_TABLE_INTENSITY_SIZE_EXT	EQU		0000080dfh

;/* EXT_bgra */
GL_BGR_EXT		EQU		0000080e0h
GL_BGRA_EXT		EQU		0000080e1h

;/* EXT_paletted_texture */
GL_COLOR_INDEX1_EXT		EQU		0000080e2h
GL_COLOR_INDEX2_EXT		EQU		0000080e3h
GL_COLOR_INDEX4_EXT		EQU		0000080e4h
GL_COLOR_INDEX8_EXT		EQU		0000080e5h
GL_COLOR_INDEX12_EXT		EQU		0000080e6h
GL_COLOR_INDEX16_EXT		EQU		0000080e7h

;/* SGI_compiled_vertex_array */
GL_ARRAY_ELEMENT_LOCK_FIRST_SGI		EQU		0000081a8h
GL_ARRAY_ELEMENT_LOCK_COUNT_SGI		EQU		0000081a9h

;/* SGI_index_array_formats */
GL_CULL_VERTEX_SGI			EQU		0000081aah
GL_CULL_VERTEX_EYE_POSITION_SGI		EQU		0000081abh
GL_CULL_VERTEX_OBJECT_POSITION_SGI	EQU		0000081ach
GL_IUI_V2F_SGI				EQU		0000081adh
GL_IUI_V3F_SGI				EQU		0000081aeh
GL_IUI_N3F_V2F_SGI			EQU		0000081afh
GL_IUI_N3F_V3F_SGI			EQU		0000081b0h
GL_T2F_IUI_V2F_SGI			EQU		0000081b1h
GL_T2F_IUI_V3F_SGI			EQU		0000081b2h
GL_T2F_IUI_N3F_V2F_SGI			EQU		0000081b3h
GL_T2F_IUI_N3F_V3F_SGI			EQU		0000081b4h

;/* SGI_index_func */
GL_INDEX_TEST_SGI		EQU		0000081b5h
GL_INDEX_TEST_FUNC_SGI		EQU		0000081b6h
GL_INDEX_TEST_REF_SGI		EQU		0000081b7h

;/* SGI_index_material */
GL_INDEX_MATERIAL_SGI		EQU		0000081b8h
GL_INDEX_MATERIAL_PARAMETER_SGI	EQU		0000081b9h
GL_INDEX_MATERIAL_FACE_SGI	EQU		0000081bah

;**************************************************************************
; functions prototypes for opengl.lib
ChoosePixelFormat PROTO :DWORD,:DWORD
DescribePixelFormat PROTO :DWORD,:DWORD,:DWORD,:DWORD
GetPixelFormat PROTO :DWORD
SetPixelFormat PROTO :DWORD,:DWORD,:DWORD
SwapBuffers PROTO :DWORD
glAccum PROTO :DWORD,:DWORD
glAlphaFunc PROTO :DWORD,:DWORD
glAreTexturesResident PROTO :DWORD,:DWORD,:DWORD
glArrayElement PROTO :DWORD
glBegin PROTO :DWORD
glBindTexture PROTO :DWORD,:DWORD
glBitmap PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glBlendFunc PROTO :DWORD,:DWORD
glCallList PROTO :DWORD
glCallLists PROTO :DWORD,:DWORD,:DWORD
glClear PROTO :DWORD
glClearAccum PROTO :DWORD,:DWORD,:DWORD,:DWORD
glClearColor PROTO :DWORD,:DWORD,:DWORD,:DWORD
glClearDepth PROTO :DWORD,:DWORD
glClearIndex PROTO :DWORD
glClearStencil PROTO :DWORD
glClipPlane PROTO :DWORD,:DWORD
glColor3b PROTO :DWORD,:DWORD,:DWORD
glColor3bv PROTO :DWORD
glColor3d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glColor3dv PROTO :DWORD
glColor3f PROTO :DWORD,:DWORD,:DWORD
glColor3fv PROTO :DWORD
glColor3i PROTO :DWORD,:DWORD,:DWORD
glColor3iv PROTO :DWORD
glColor3s PROTO :DWORD,:DWORD,:DWORD
glColor3sv PROTO :DWORD
glColor3ub PROTO :DWORD,:DWORD,:DWORD
glColor3ubv PROTO :DWORD
glColor3ui PROTO :DWORD,:DWORD,:DWORD
glColor3uiv PROTO :DWORD
glColor3us PROTO :DWORD,:DWORD,:DWORD
glColor3usv PROTO :DWORD
glColor4b PROTO :DWORD,:DWORD,:DWORD,:DWORD
glColor4bv PROTO :DWORD
glColor4d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glColor4dv PROTO :DWORD
glColor4f PROTO :DWORD,:DWORD,:DWORD,:DWORD
glColor4fv PROTO :DWORD
glColor4i PROTO :DWORD,:DWORD,:DWORD,:DWORD
glColor4iv PROTO :DWORD
glColor4s PROTO :DWORD,:DWORD,:DWORD,:DWORD
glColor4sv PROTO :DWORD
glColor4ub PROTO :DWORD,:DWORD,:DWORD,:DWORD
glColor4ubv PROTO :DWORD
glColor4ui PROTO :DWORD,:DWORD,:DWORD,:DWORD
glColor4uiv PROTO :DWORD
glColor4us PROTO :DWORD,:DWORD,:DWORD,:DWORD
glColor4usv PROTO :DWORD
glColorMask PROTO :DWORD,:DWORD,:DWORD,:DWORD
glColorMaterial PROTO :DWORD,:DWORD
glColorPointer PROTO :DWORD,:DWORD,:DWORD,:DWORD
glCopyPixels PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glCopyTexImage1D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glCopyTexImage2D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glCopyTexSubImage1D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glCopyTexSubImage2D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glCullFace PROTO :DWORD
glDeleteLists PROTO :DWORD,:DWORD
glDeleteTextures PROTO :DWORD,:DWORD
glDepthFunc PROTO :DWORD
glDepthMask PROTO :DWORD
glDepthRange PROTO :DWORD,:DWORD,:DWORD,:DWORD  ; params are of GLclampd type
glDisable PROTO :DWORD
glDisableClientState PROTO :DWORD
glDrawArrays PROTO :DWORD,:DWORD,:DWORD
glDrawBuffer PROTO :DWORD
glDrawElements PROTO :DWORD,:DWORD,:DWORD,:DWORD
glDrawPixels PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glEdgeFlag PROTO :DWORD
glEdgeFlagPointer PROTO :DWORD,:DWORD
glEdgeFlagv PROTO :DWORD
glEnable PROTO :DWORD
glEnableClientState PROTO :DWORD
glEnd PROTO
glEndList PROTO
glEvalCoord1d PROTO :DWORD,:DWORD
glEvalCoord1dv PROTO :DWORD
glEvalCoord1f PROTO :DWORD
glEvalCoord1fv PROTO :DWORD
glEvalCoord2d PROTO :DWORD,:DWORD,:DWORD,:DWORD
glEvalCoord2dv PROTO :DWORD
glEvalCoord2f PROTO :DWORD,:DWORD
glEvalCoord2fv PROTO :DWORD
glEvalMesh1 PROTO :DWORD,:DWORD,:DWORD
glEvalMesh2 PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glEvalPoint1 PROTO :DWORD
glEvalPoint2 PROTO :DWORD,:DWORD
glFeedbackBuffer PROTO :DWORD,:DWORD,:DWORD
glFinish PROTO
glFlush PROTO
glFogf PROTO :DWORD,:DWORD
glFogfv PROTO :DWORD,:DWORD
glFogi PROTO :DWORD,:DWORD
glFogiv PROTO :DWORD,:DWORD
glFrontFace PROTO :DWORD
;!!!! actual parameters are of GLdouble type, to call use macro below
glFrustum PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glGenLists PROTO :DWORD
glGenTextures PROTO :DWORD,:DWORD
glGetBooleanv PROTO :DWORD,:DWORD
glGetClipPlane PROTO :DWORD,:DWORD
glGetDoublev PROTO :DWORD,:DWORD
glGetError PROTO
glGetFloatv PROTO :DWORD,:DWORD
glGetIntegerv PROTO :DWORD,:DWORD
glGetLightfv PROTO :DWORD,:DWORD,:DWORD
glGetLightiv PROTO :DWORD,:DWORD,:DWORD
glGetMapdv PROTO :DWORD,:DWORD,:DWORD
glGetMapfv PROTO :DWORD,:DWORD,:DWORD
glGetMapiv PROTO :DWORD,:DWORD,:DWORD
glGetMaterialfv PROTO :DWORD,:DWORD,:DWORD
glGetMaterialiv PROTO :DWORD,:DWORD,:DWORD
glGetPixelMapfv PROTO :DWORD,:DWORD
glGetPixelMapuiv PROTO :DWORD,:DWORD
glGetPixelMapusv PROTO :DWORD,:DWORD
glGetPointerv PROTO :DWORD,:DWORD
glGetPolygonStipple PROTO :DWORD
glGetString PROTO :DWORD
glGetTexEnvfv PROTO :DWORD,:DWORD,:DWORD
glGetTexEnviv PROTO :DWORD,:DWORD,:DWORD
glGetTexGendv PROTO :DWORD,:DWORD,:DWORD
glGetTexGenfv PROTO :DWORD,:DWORD,:DWORD
glGetTexGeniv PROTO :DWORD,:DWORD,:DWORD
glGetTexImage PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glGetTexLevelParameterfv PROTO :DWORD,:DWORD,:DWORD,:DWORD
glGetTexLevelParameteriv PROTO :DWORD,:DWORD,:DWORD,:DWORD
glGetTexParameterfv PROTO :DWORD,:DWORD,:DWORD
glGetTexParameteriv PROTO :DWORD,:DWORD,:DWORD
glHint PROTO :DWORD,:DWORD
glIndexMask PROTO :DWORD
glIndexPointer PROTO :DWORD,:DWORD,:DWORD
glIndexd PROTO :DWORD,:DWORD
glIndexdv PROTO :DWORD
glIndexf PROTO :DWORD
glIndexfv PROTO :DWORD
glIndexi PROTO :DWORD
glIndexiv PROTO :DWORD
glIndexs PROTO :DWORD
glIndexsv PROTO :DWORD
glIndexub PROTO :DWORD
glIndexubv PROTO :DWORD
glInitNames PROTO
glInterleavedArrays PROTO :DWORD,:DWORD,:DWORD
glIsEnabled PROTO :DWORD
glIsList PROTO :DWORD
glIsTexture PROTO :DWORD
glLightModelf PROTO :DWORD,:DWORD
glLightModelfv PROTO :DWORD,:DWORD
glLightModeli PROTO :DWORD,:DWORD
glLightModeliv PROTO :DWORD,:DWORD
glLightf PROTO :DWORD,:DWORD,:DWORD
glLightfv PROTO :DWORD,:DWORD,:DWORD
glLighti PROTO :DWORD,:DWORD,:DWORD
glLightiv PROTO :DWORD,:DWORD,:DWORD
glLineStipple PROTO :DWORD,:DWORD
glLineWidth PROTO :DWORD
glListBase PROTO :DWORD
glLoadIdentity PROTO
glLoadMatrixd PROTO :DWORD
glLoadMatrixf PROTO :DWORD
glLoadName PROTO :DWORD
glLogicOp PROTO :DWORD
glMap1d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glMap1f PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glMap2d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glMap2f PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glMapGrid1d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glMapGrid1f PROTO :DWORD,:DWORD,:DWORD
glMapGrid2d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glMapGrid2f PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glMaterialf PROTO :DWORD,:DWORD,:DWORD
glMaterialfv PROTO :DWORD,:DWORD,:DWORD
glMateriali PROTO :DWORD,:DWORD,:DWORD
glMaterialiv PROTO :DWORD,:DWORD,:DWORD
glMatrixMode PROTO :DWORD
glMultMatrixd PROTO :DWORD
glMultMatrixf PROTO :DWORD
glNewList PROTO :DWORD,:DWORD
glNormal3b PROTO :DWORD,:DWORD,:DWORD
glNormal3bv PROTO :DWORD
glNormal3d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glNormal3dv PROTO :DWORD
glNormal3f PROTO :GLfloat,:GLfloat,:GLfloat
glNormal3fv PROTO :DWORD
glNormal3i PROTO :DWORD,:DWORD,:DWORD
glNormal3iv PROTO :DWORD
glNormal3s PROTO :DWORD,:DWORD,:DWORD
glNormal3sv PROTO :DWORD
glNormalPointer PROTO :DWORD,:DWORD,:DWORD
glOrtho PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glPassThrough PROTO :DWORD
glPixelMapfv PROTO :DWORD,:DWORD,:DWORD
glPixelMapuiv PROTO :DWORD,:DWORD,:DWORD
glPixelMapusv PROTO :DWORD,:DWORD,:DWORD
glPixelStoref PROTO :DWORD,:DWORD
glPixelStorei PROTO :DWORD,:DWORD
glPixelTransferf PROTO :DWORD,:DWORD
glPixelTransferi PROTO :DWORD,:DWORD
glPixelZoom PROTO :DWORD,:DWORD
glPointSize PROTO :DWORD
glPolygonMode PROTO :DWORD,:DWORD
glPolygonOffset PROTO :DWORD,:DWORD
glPolygonStipple PROTO :DWORD
glPopAttrib PROTO
glPopClientAttrib PROTO
glPopMatrix PROTO
glPopName PROTO
glPrioritizeTextures PROTO :DWORD,:DWORD,:DWORD
glPushAttrib PROTO :DWORD
glPushClientAttrib PROTO :DWORD
glPushMatrix PROTO
glPushName PROTO :DWORD
glRasterPos2d PROTO :DWORD,:DWORD,:DWORD,:DWORD
glRasterPos2dv PROTO :DWORD
glRasterPos2f PROTO :DWORD,:DWORD
glRasterPos2fv PROTO :DWORD
glRasterPos2i PROTO :DWORD,:DWORD
glRasterPos2iv PROTO :DWORD
glRasterPos2s PROTO :DWORD,:DWORD
glRasterPos2sv PROTO :DWORD
glRasterPos3d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glRasterPos3dv PROTO :DWORD
glRasterPos3f PROTO :DWORD,:DWORD,:DWORD
glRasterPos3fv PROTO :DWORD
glRasterPos3i PROTO :DWORD,:DWORD,:DWORD
glRasterPos3iv PROTO :DWORD
glRasterPos3s PROTO :DWORD,:DWORD,:DWORD
glRasterPos3sv PROTO :DWORD
glRasterPos4d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glRasterPos4dv PROTO :DWORD
glRasterPos4f PROTO :DWORD,:DWORD,:DWORD,:DWORD
glRasterPos4fv PROTO :DWORD
glRasterPos4i PROTO :DWORD,:DWORD,:DWORD,:DWORD
glRasterPos4iv PROTO :DWORD
glRasterPos4s PROTO :DWORD,:DWORD,:DWORD,:DWORD
glRasterPos4sv PROTO :DWORD
glReadBuffer PROTO :DWORD
glReadPixels PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glRectd PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glRectdv PROTO :DWORD,:DWORD
glRectf PROTO :DWORD,:DWORD,:DWORD,:DWORD
glRectfv PROTO :DWORD,:DWORD
glRecti PROTO :DWORD,:DWORD,:DWORD,:DWORD
glRectiv PROTO :DWORD,:DWORD
glRects PROTO :DWORD,:DWORD,:DWORD,:DWORD
glRectsv PROTO :DWORD,:DWORD
glRenderMode PROTO :DWORD
glRotated PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glRotatef PROTO :DWORD,:DWORD,:DWORD,:DWORD
glScaled PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glScalef PROTO :DWORD,:DWORD,:DWORD
glScissor PROTO :DWORD,:DWORD,:DWORD,:DWORD
glSelectBuffer PROTO :DWORD,:DWORD
glShadeModel PROTO :DWORD
glStencilFunc PROTO :DWORD,:DWORD,:DWORD
glStencilMask PROTO :DWORD
glStencilOp PROTO :DWORD,:DWORD,:DWORD
glTexCoord1d PROTO :DWORD,:DWORD
glTexCoord1dv PROTO :DWORD
glTexCoord1f PROTO :DWORD
glTexCoord1fv PROTO :DWORD
glTexCoord1i PROTO :DWORD
glTexCoord1iv PROTO :DWORD
glTexCoord1s PROTO :DWORD
glTexCoord1sv PROTO :DWORD
glTexCoord2d PROTO :DWORD,:DWORD,:DWORD,:DWORD
glTexCoord2dv PROTO :DWORD
glTexCoord2f PROTO :DWORD,:DWORD
glTexCoord2fv PROTO :DWORD
glTexCoord2i PROTO :DWORD,:DWORD
glTexCoord2iv PROTO :DWORD
glTexCoord2s PROTO :DWORD,:DWORD
glTexCoord2sv PROTO :DWORD
glTexCoord3d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glTexCoord3dv PROTO :DWORD
glTexCoord3f PROTO :DWORD,:DWORD,:DWORD
glTexCoord3fv PROTO :DWORD
glTexCoord3i PROTO :DWORD,:DWORD,:DWORD
glTexCoord3iv PROTO :DWORD
glTexCoord3s PROTO :DWORD,:DWORD,:DWORD
glTexCoord3sv PROTO :DWORD
glTexCoord4d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glTexCoord4dv PROTO :DWORD
glTexCoord4f PROTO :DWORD,:DWORD,:DWORD,:DWORD
glTexCoord4fv PROTO :DWORD
glTexCoord4i PROTO :DWORD,:DWORD,:DWORD,:DWORD
glTexCoord4iv PROTO :DWORD
glTexCoord4s PROTO :DWORD,:DWORD,:DWORD,:DWORD
glTexCoord4sv PROTO :DWORD
glTexCoordPointer PROTO :DWORD,:DWORD,:DWORD,:DWORD
glTexEnvf PROTO :DWORD,:DWORD,:DWORD
glTexEnvfv PROTO :DWORD,:DWORD,:DWORD
glTexEnvi PROTO :DWORD,:DWORD,:DWORD
glTexEnviv PROTO :DWORD,:DWORD,:DWORD
glTexGend PROTO :DWORD,:DWORD,:DWORD,:DWORD
glTexGendv PROTO :DWORD,:DWORD,:DWORD
glTexGenf PROTO :DWORD,:DWORD,:DWORD
glTexGenfv PROTO :DWORD,:DWORD,:DWORD
glTexGeni PROTO :DWORD,:DWORD,:DWORD
glTexGeniv PROTO :DWORD,:DWORD,:DWORD
glTexImage1D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glTexImage2D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glTexParameterf PROTO :DWORD,:DWORD,:DWORD
glTexParameterfv PROTO :DWORD,:DWORD,:DWORD
glTexParameteri PROTO :DWORD,:DWORD,:DWORD
glTexParameteriv PROTO :DWORD,:DWORD,:DWORD
glTexSubImage1D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glTexSubImage2D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glTranslated PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glTranslatef PROTO :DWORD,:DWORD,:DWORD
glVertex2d PROTO :DWORD,:DWORD,:DWORD,:DWORD
glVertex2dv PROTO :DWORD
glVertex2f PROTO :DWORD,:DWORD
glVertex2fv PROTO :DWORD
glVertex2i PROTO :DWORD,:DWORD
glVertex2iv PROTO :DWORD
glVertex2s PROTO :DWORD,:DWORD
glVertex2sv PROTO :DWORD
glVertex3d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glVertex3dv PROTO :DWORD
glVertex3f PROTO :GLfloat,:GLfloat,:GLfloat
glVertex3fv PROTO :DWORD
glVertex3i PROTO :DWORD,:DWORD,:DWORD
glVertex3iv PROTO :DWORD
glVertex3s PROTO :DWORD,:DWORD,:DWORD
glVertex3sv PROTO :DWORD
glVertex4d PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
glVertex4dv PROTO :DWORD
glVertex4f PROTO :DWORD,:DWORD,:DWORD,:DWORD
glVertex4fv PROTO :DWORD
glVertex4i PROTO :DWORD,:DWORD,:DWORD,:DWORD
glVertex4iv PROTO :DWORD
glVertex4s PROTO :DWORD,:DWORD,:DWORD,:DWORD
glVertex4sv PROTO :DWORD
glVertexPointer PROTO :DWORD,:DWORD,:DWORD,:DWORD
glViewport PROTO :DWORD,:DWORD,:DWORD,:DWORD
mglCosmoCheckVisual PROTO :DWORD,:DWORD
mglCosmoChooseVisual PROTO :DWORD,:DWORD
mglCosmoCreateContext PROTO :DWORD,:DWORD
mglCosmoCreateSurface PROTO :DWORD,:DWORD
mglCosmoDeleteContext PROTO :DWORD
mglCosmoDeleteSurface PROTO :DWORD
mglCosmoMakeCurrent PROTO :DWORD,:DWORD
mglCosmoSetSurfacePtr PROTO :DWORD,:DWORD,:DWORD
wglChoosePixelFormat PROTO :DWORD,:DWORD
wglCopyContext PROTO :DWORD,:DWORD,:DWORD
wglCreateContext PROTO :DWORD
wglCreateLayerContext PROTO :DWORD,:DWORD
wglDeleteContext PROTO :DWORD
wglDescribeLayerPlane PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
wglDescribePixelFormat PROTO :DWORD,:DWORD,:DWORD,:DWORD
wglGetCurrentContext PROTO
wglGetCurrentDC PROTO
wglGetDefaultProcAddress PROTO :DWORD
wglGetLayerPaletteEntries PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
wglGetPixelFormat PROTO :DWORD
wglGetProcAddress PROTO :DWORD
wglMakeCurrent PROTO :DWORD,:DWORD
wglRealizeLayerPalette PROTO :DWORD,:DWORD,:DWORD
wglSetLayerPaletteEntries PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
wglSetPixelFormat PROTO :DWORD,:DWORD,:DWORD
wglShareLists PROTO :DWORD,:DWORD
wglSwapBuffers PROTO :DWORD
wglSwapLayerBuffers PROTO :DWORD,:DWORD
wglUseFontBitmapsA PROTO :DWORD,:DWORD,:DWORD,:DWORD
wglUseFontBitmapsW PROTO :DWORD,:DWORD,:DWORD,:DWORD
IFDEF UNICODE
 wglUseFontBitmaps equ
ELSE
 wglUseFontBitmaps equ
ENDIF

wglUseFontOutlinesA PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
wglUseFontOutlinesW PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
IFDEF UNICODE
 wglUseFontOutlines equ
ELSE
 wglUseFontOutlines equ
ENDIF

;****************************************************************************
gl_movf MACRO dest,numb
 mov dest,12345678h
 ORG $-4
 real4 numb
ENDM

;====> Better close your eyes... ADULTS ONLY!

gl_fpush MACRO numb
LOCAL prmstr,prmlen,x,n
 prmstr EQU
 prmlen SIZESTR prmstr
IF (prmlen LE 7)
  ;; constant or varname
  x SUBSTR prmstr,prmlen,1
  IFIDNI x,
   x SUBSTR prmstr,1,prmlen-1
   push 12345678h
   ORG $-4
   real4 &x
  ELSE
   push GLfloat ptr prmstr
  ENDIF
ELSE
 x SUBSTR prmstr,1,7
 IFIDNI x,<(float)>
  x SUBSTR prmstr,8
  n=&x
  x TEXTEQU %n
  x CATSTR x,<.0>
  push 12345678h
  ORG $-4
  real4 &x
 ELSE
  x SUBSTR prmstr,prmlen,1
  IFIDNI x,
   x SUBSTR prmstr,1,prmlen-1
   push 12345678h
   ORG $-4
   real4 &x
  ELSE
   push GLfloat ptr prmstr
  ENDIF
 ENDIF
ENDIF
ENDM

; version without auto-convertion
_gl_fpush MACRO numb
LOCAL prmstr,prmlen,x
 prmstr EQU
 prmlen SIZESTR prmstr
 x SUBSTR prmstr,prmlen,1
 IFIDNI x,
  x SUBSTR prmstr,1,prmlen-1
  push 12345678h
  ORG $-4
  real4 &x
 ELSE
  push GLfloat ptr prmstr
 ENDIF
ENDM

; ugly, ugly, ugly... but yet works (from time to time)
IF 0 ; this version is more stable, but generated code much more close to insane
gl_dpush MACRO numb
LOCAL loc1,dat,prmstr,prmlen,x
 prmstr EQU
 prmlen SIZESTR prmstr
 x SUBSTR prmstr,prmlen,1
 IFIDNI x,
  x SUBSTR prmstr,1,prmlen-1
  jmp loc1
   ALIGN DWORD
   dat real8 &x
  loc1:
   push dword ptr dat[4]
   push dword ptr dat
 ELSE
  x CATSTR prmstr,<[4]>
  push dword ptr x
  push dword ptr prmstr
 ENDIF
ENDM

_GL_DPUSH_FIX MACRO
ENDM

ELSE ;----------------------------------------

gl_dpush MACRO numb
LOCAL prmstr,prmlen,x
 prmstr EQU
 prmlen SIZESTR prmstr
 x SUBSTR prmstr,prmlen,1
 IFIDNI x,
 x SUBSTR prmstr,1,prmlen-1
 real8 &x
 db 68h
 real8 &x
 ORG $-8-1-8
 db 8Dh,40h,0 ; lea eax,[eax+0]
 db 68h
 ORG $+4+1+4
 ELSE
 x CATSTR prmstr,<[4]>
 mov eax,eax
 mov ebx,ebx
 push dword ptr x
 push dword ptr prmstr
 ENDIF
ENDM

_GL_DPUSH_FIX MACRO
 mov eax,eax
 mov ebx,ebx
ENDM

ENDIF

;*****************************************************************      GL_PUSH_FLOAT_ARGS
; CALL macros
;*****************************************************************

;
GL_PUSH_FLOAT_ARGS MACRO a1,a2,a3,a4,a5,a6,a7,a8,a9,a10
    IFNB
        GL_PUSH_FLOAT_ARGS ,,,,,,,,
        gl_fpush a1
    ENDIF
ENDM

;
GL_PUSH_DOUBLE_ARGS MACRO a1,a2,a3,a4,a5,a6,a7,a8,a9,a10
    IFNB
        GL_PUSH_DOUBLE_ARGS ,,,,,,,,
        gl_dpush a1
    ENDIF
    _GL_DPUSH_FIX
ENDM

;
glCall_f MACRO glApiName:REQ,Args:VARARG
    GL_PUSH_FLOAT_ARGS Args
    call glApiName
ENDM

;
glCall_d MACRO glApiName:REQ,Args:VARARG
    GL_PUSH_DOUBLE_ARGS Args
    call glApiName
ENDM

;*****************************************************************      glCall_f     GLfloat   GL_PUSH_FLOAT_ARGS
include glMacros/gl.mac
;*****************************************************************

;***********These actually should be defined in windows.inc

IFNDEF HGLRC
 HGLRC typedef HANDLE
ENDIF

_safedef MACRO constname, constval
 IFNDEF constname
  constname EQU constval
 ENDIF
ENDM

;**** These flags may be needed when setting full-screen mode
;**** via ChangeDisplaySettings API. Not a good practice however.
;/* Flags for ChangeDisplaySettings */
_safedef CDS_UPDATEREGISTRY,1
_safedef CDS_TEST,2
_safedef CDS_FULLSCREEN,4
_safedef CDS_GLOBAL,8
_safedef CDS_SET_PRIMARY,10h
_safedef CDS_RESET,40000000h
_safedef CDS_SETRECT,20000000h
_safedef CDS_NORESET,10000000h

;/* Return values for ChangeDisplaySettings */
_safedef DISP_CHANGE_SUCCESSFUL,0
_safedef DISP_CHANGE_RESTART,1
_safedef DISP_CHANGE_FAILED,-1
_safedef DISP_CHANGE_BADMODE,-2
_safedef DISP_CHANGE_NOTUPDATED,-3
_safedef DISP_CHANGE_BADFLAGS,-4
_safedef DISP_CHANGE_BADPARAM,-5

;**** PixelFormat-related constants

;/* pixel types */
_safedef PFD_TYPE_RGBA,0
_safedef PFD_TYPE_COLORINDEX,1

;/* layer types */
_safedef PFD_MAIN_PLANE,0
_safedef PFD_OVERLAY_PLANE,1
_safedef PFD_UNDERLAY_PLANE,-1

;/* PIXELFORMATDESCRIPTOR flags */
_safedef PFD_DOUBLEBUFFER,1
_safedef PFD_STEREO,2
_safedef PFD_DRAW_TO_WINDOW,4
_safedef PFD_DRAW_TO_BITMAP,8
_safedef PFD_SUPPORT_GDI,10h
_safedef PFD_SUPPORT_OPENGL,20h
_safedef PFD_GENERIC_FORMAT,40h
_safedef PFD_NEED_PALETTE,80h
_safedef PFD_NEED_SYSTEM_PALETTE,100h
_safedef PFD_SWAP_EXCHANGE,200h
_safedef PFD_SWAP_COPY,400h
_safedef PFD_SWAP_LAYER_BUFFERS,800h
_safedef PFD_GENERIC_ACCELERATED,1000h

;/* PIXELFORMATDESCRIPTOR flags for use in ChoosePixelFormat only */
_safedef PFD_DEPTH_DONTCARE,20000000h
_safedef PFD_DOUBLEBUFFER_DONTCARE,40000000h
_safedef PFD_STEREO_DONTCARE,80000000h

ENDIF ;//_OPENGL_ASMDEF_


GLU.DEF

; Copyright 1992-1993 Silicon Graphics, Inc. All Rights Reserved.
; Transformed to this form by Sergey Chaban
; (C) hardCode 1999-2000
; http://bizarrecreations.webjump.com
; e-mail: soul_cat@mindless.com

IFNDEF _GLU_ASMDEF_
_GLU_ASMDEF_=1

include gl.def

;/* Extensions */
GLU_EXT_object_space_tess	EQU		1
GLU_EXT_nurbs_tessellator	EQU		1

;/* Boolean */
GLU_FALSE			EQU		0
GLU_TRUE			EQU		1

;/* Version */
GLU_VERSION_1_1			EQU		1
GLU_VERSION_1_2			EQU		1

;/* StringName */
GLU_VERSION			EQU		100800t
GLU_EXTENSIONS			EQU		100801t

;/* ErrorCode */
GLU_INVALID_ENUM		EQU		100900t
GLU_INVALID_VALUE		EQU		100901t
GLU_OUT_OF_MEMORY		EQU		100902t
GLU_INCOMPATIBLE_GL_VERSION	EQU		100903t
GLU_INVALID_OPERATION		EQU		100904t

;/* NurbsDisplay */
;GLU_FILL
GLU_OUTLINE_POLYGON		EQU		100240t
GLU_OUTLINE_PATCH		EQU		100241t

;/* NurbsCallback */
GLU_ERROR			EQU		100103t

;/* NurbsError */
GLU_NURBS_ERROR1		EQU		100251t
GLU_NURBS_ERROR2		EQU		100252t
GLU_NURBS_ERROR3		EQU		100253t
GLU_NURBS_ERROR4		EQU		100254t
GLU_NURBS_ERROR5		EQU		100255t
GLU_NURBS_ERROR6		EQU		100256t
GLU_NURBS_ERROR7		EQU		100257t
GLU_NURBS_ERROR8		EQU		100258t
GLU_NURBS_ERROR9		EQU		100259t
GLU_NURBS_ERROR10		EQU		100260t
GLU_NURBS_ERROR11		EQU		100261t
GLU_NURBS_ERROR12		EQU		100262t
GLU_NURBS_ERROR13		EQU		100263t
GLU_NURBS_ERROR14		EQU		100264t
GLU_NURBS_ERROR15		EQU		100265t
GLU_NURBS_ERROR16		EQU		100266t
GLU_NURBS_ERROR17		EQU		100267t
GLU_NURBS_ERROR18		EQU		100268t
GLU_NURBS_ERROR19		EQU		100269t
GLU_NURBS_ERROR20		EQU		100270t
GLU_NURBS_ERROR21		EQU		100271t
GLU_NURBS_ERROR22		EQU		100272t
GLU_NURBS_ERROR23		EQU		100273t
GLU_NURBS_ERROR24		EQU		100274t
GLU_NURBS_ERROR25		EQU		100275t
GLU_NURBS_ERROR26		EQU		100276t
GLU_NURBS_ERROR27		EQU		100277t
GLU_NURBS_ERROR28		EQU		100278t
GLU_NURBS_ERROR29		EQU		100279t
GLU_NURBS_ERROR30		EQU		100280t
GLU_NURBS_ERROR31		EQU		100281t
GLU_NURBS_ERROR32		EQU		100282t
GLU_NURBS_ERROR33		EQU		100283t
GLU_NURBS_ERROR34		EQU		100284t
GLU_NURBS_ERROR35		EQU		100285t
GLU_NURBS_ERROR36		EQU		100286t
GLU_NURBS_ERROR37		EQU		100287t

;/* NurbsProperty */
GLU_AUTO_LOAD_MATRIX		EQU		100200t
GLU_CULLING			EQU		100201t
GLU_SAMPLING_TOLERANCE		EQU		100203t
GLU_DISPLAY_MODE		EQU		100204t
GLU_PARAMETRIC_TOLERANCE	EQU		100202t
GLU_SAMPLING_METHOD		EQU		100205t
GLU_U_STEP			EQU		100206t
GLU_V_STEP			EQU		100207t

;/* NurbsSampling */
GLU_OBJECT_PARAMETRIC_ERROR_EXT	EQU		100208t
GLU_OBJECT_PATH_LENGTH_EXT	EQU		100209t
GLU_PATH_LENGTH			EQU		100215t
GLU_PARAMETRIC_ERROR		EQU		100216t
GLU_DOMAIN_DISTANCE		EQU		100217t

;/* NurbsTrim */
GLU_MAP1_TRIM_2			EQU		100210t
GLU_MAP1_TRIM_3			EQU		100211t

;/* QuadricDrawStyle */
GLU_POINT			EQU		100010t
GLU_LINE			EQU		100011t
GLU_FILL			EQU		100012t
GLU_SILHOUETTE			EQU		100013t

;/* QuadricCallback */
;GLU_ERROR

;/* QuadricNormal */
GLU_SMOOTH			EQU		100000t
GLU_FLAT			EQU		100001t
GLU_NONE			EQU		100002t

;/* QuadricOrientation */
GLU_OUTSIDE			EQU		100020t
GLU_INSIDE			EQU		100021t

;/* TessCallback */
GLU_TESS_BEGIN			EQU		100100t
GLU_BEGIN			EQU		100100t
GLU_TESS_VERTEX			EQU		100101t
GLU_VERTEX			EQU		100101t
GLU_TESS_END			EQU		100102t
GLU_END				EQU		100102t
GLU_TESS_ERROR			EQU		100103t
GLU_TESS_EDGE_FLAG		EQU		100104t
GLU_EDGE_FLAG			EQU		100104t
GLU_TESS_COMBINE		EQU		100105t
GLU_TESS_BEGIN_DATA		EQU		100106t
GLU_TESS_VERTEX_DATA		EQU		100107t
GLU_TESS_END_DATA		EQU		100108t
GLU_TESS_ERROR_DATA		EQU		100109t
GLU_TESS_EDGE_FLAG_DATA		EQU		100110t
GLU_TESS_COMBINE_DATA		EQU		100111t
GLU_NURBS_MODE_EXT		EQU		100160t
GLU_NURBS_TESSELLATOR_EXT	EQU		100161t
GLU_NURBS_RENDERER_EXT		EQU		100162t
GLU_NURBS_BEGIN_EXT		EQU		100164t
GLU_NURBS_VERTEX_EXT		EQU		100165t
GLU_NURBS_NORMAL_EXT		EQU		100166t
GLU_NURBS_COLOR_EXT		EQU		100167t
GLU_NURBS_TEX_COORD_EXT		EQU		100168t
GLU_NURBS_END_EXT		EQU		100169t
GLU_NURBS_BEGIN_DATA_EXT	EQU		100170t
GLU_NURBS_VERTEX_DATA_EXT	EQU		100171t
GLU_NURBS_NORMAL_DATA_EXT	EQU		100172t
GLU_NURBS_COLOR_DATA_EXT	EQU		100173t
GLU_NURBS_TEX_COORD_DATA_EXT	EQU		100174t
GLU_NURBS_END_DATA_EXT		EQU		100175t

;/* TessContour */
GLU_CW				EQU		100120t
GLU_CCW				EQU		100121t
GLU_INTERIOR			EQU		100122t
GLU_EXTERIOR			EQU		100123t
GLU_UNKNOWN			EQU		100124t

;/* TessProperty */
GLU_TESS_WINDING_RULE		EQU		100140t
GLU_TESS_BOUNDARY_ONLY		EQU		100141t
GLU_TESS_TOLERANCE		EQU		100142t

;/* TessError */
GLU_TESS_ERROR1			EQU		100151t
GLU_TESS_ERROR2			EQU		100152t
GLU_TESS_ERROR3			EQU		100153t
GLU_TESS_ERROR4			EQU		100154t
GLU_TESS_ERROR5			EQU		100155t
GLU_TESS_ERROR6			EQU		100156t
GLU_TESS_ERROR7			EQU		100157t
GLU_TESS_ERROR8			EQU		100158t
GLU_TESS_MISSING_BEGIN_POLYGON	EQU		100151t
GLU_TESS_MISSING_BEGIN_CONTOUR	EQU		100152t
GLU_TESS_MISSING_END_POLYGON	EQU		100153t
GLU_TESS_MISSING_END_CONTOUR	EQU		100154t
GLU_TESS_COORD_TOO_LARGE	EQU		100155t
GLU_TESS_NEED_COMBINE_CALLBACK	EQU		100156t

;/* TessWinding */
GLU_TESS_WINDING_ODD		EQU		100130t
GLU_TESS_WINDING_NONZERO	EQU		100131t
GLU_TESS_WINDING_POSITIVE	EQU		100132t
GLU_TESS_WINDING_NEGATIVE	EQU		100133t
GLU_TESS_WINDING_ABS_GEQ_TWO	EQU		100134t

;***********PROTOs for glu.lib

gluBeginCurve PROTO :DWORD
gluBeginPolygon PROTO :DWORD
gluBeginSurface PROTO :DWORD
gluBeginTrim PROTO :DWORD
gluBuild1DMipmaps PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
gluBuild2DMipmaps PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
gluCylinder PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
gluDeleteNurbsRenderer PROTO :DWORD
gluDeleteQuadric PROTO :DWORD
gluDeleteTess PROTO :DWORD
gluDisk PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
gluEndCurve PROTO :DWORD
gluEndPolygon PROTO :DWORD
gluEndSurface PROTO :DWORD
gluEndTrim PROTO :DWORD
gluErrorString PROTO :DWORD
gluErrorUnicodeStringEXT PROTO :DWORD
gluGetNurbsProperty PROTO :DWORD,:DWORD,:DWORD
gluGetString PROTO :DWORD
gluGetTessProperty PROTO :DWORD,:DWORD,:DWORD
gluLoadSamplingMatrices PROTO :DWORD,:DWORD,:DWORD,:DWORD
gluLookAt PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
gluNewNurbsRenderer PROTO
gluNewQuadric PROTO
gluNewTess PROTO
gluNextContour PROTO :DWORD,:DWORD
gluNurbsCallback PROTO :DWORD,:DWORD,:DWORD
gluNurbsCurve PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
gluNurbsProperty PROTO :DWORD,:DWORD,:DWORD
gluNurbsSurface PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
gluOrtho2D PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
gluPartialDisk PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
gluPerspective PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
gluPickMatrix PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
gluProject PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
gluPwlCurve PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
gluQuadricCallback PROTO :DWORD,:DWORD,:DWORD
gluQuadricDrawStyle PROTO :DWORD,:DWORD
gluQuadricNormals PROTO :DWORD,:DWORD
gluQuadricOrientation PROTO :DWORD,:DWORD
gluQuadricTexture PROTO :DWORD,:DWORD
gluScaleImage PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
gluSphere PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
gluTessBeginContour PROTO :DWORD
gluTessBeginPolygon PROTO :DWORD,:DWORD
gluTessCallback PROTO :DWORD,:DWORD,:DWORD
gluTessEndContour PROTO :DWORD
gluTessEndPolygon PROTO :DWORD
gluTessNormal PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
gluTessProperty PROTO :DWORD,:DWORD,:DWORD,:DWORD
gluTessVertex PROTO :DWORD,:DWORD,:DWORD
gluUnProject PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD

;*****************************************************************
include glMacros/glu.mac
;*****************************************************************

ENDIF ;//_GLU_ASMDEF_


Include.DEF

DM_BITSPERPEL       = 00040000h
DM_PELSWIDTH        = 00080000h
DM_PELSHEIGHT       = 00100000h

_glClearDepth MACRO t                   ;this is not defined in hardcode's include files
    gl_dpush t                          ;so here it is.
    mov eax, eax
    mov ecx, ecx
    call glClearDepth
ENDM

_glColor4f MACRO R,G,B,A
    gl_fpush A
    gl_fpush B
    gl_fpush G
    gl_fpush R
    mov eax, eax	 	 	; without this crap, it fucks up
    mov ecx, ecx		 	 ; don't ask me why...
    call glColor4f
ENDM

_glClearColor MACRO a,b,c,d
    gl_fpush d
    gl_fpush c
    gl_fpush b
    gl_fpush a
    mov eax, eax
    mov ecx, ecx
    call glClearColor
ENDM

_gluPerspective MACRO a,b,c,d
    gl_dpush d
    gl_dpush c
    gl_dpush b
    gl_dpush a
    mov eax, eax
    mov ecx, ecx
    call gluPerspective
ENDM


winextra.DEF

;* (C) hardCode | http://bizarrecreations.webjump.com

;->|Id: WinExtra\winextra.def ver 1.1 | 23.12.1999
;  | - added makeMemPtr, __push/popRegs, __save/restoreWorkingRegs macros
;  | - added SETBITFIELD macro
;  | - added _WINX_REGSAFETY flag
;  | - optimized ZeroMemory, memcpy
;  |   (ptrs to local vars should be prefixed with & )
;  | - fixed return macro

IFNDEF _WIN32_EXTRA_DEFS_
_WIN32_EXTRA_DEFS_=1

include unicode.def

IFNDEF SNDMSG
 SNDMSG MACRO hWnd, Umsg, wPrm, lPrm
  invoke SendMessage, hWnd, Umsg, wPrm, lPrm
 ENDM
ENDIF

IFNDEF _WINX_REGSAFETY
 _WINX_REGSAFETY=1
ENDIF

makeMemPtr MACRO reg,mem
LOCAL tmp
 IF ((OPATTR(reg)) AND 10h) EQ 0
  .err
 ENDIF

 tmp SUBSTR ,1,1
 IFIDNI tmp,<&>
   tmp SUBSTR ,2
   lea reg,&tmp
 ELSEIF ((OPATTR(mem)) AND 16h) ; register(10h), const(4) or [mem](2)
   mov reg,mem
 ELSE
   .err
 ENDIF
ENDM

__pushRegs MACRO regList:VARARG
LOCAL x
 IRP x,
  IF ((OPATTR(&x)) AND 10h)
   push x
  ELSE
  .err
  ENDIF
 ENDM
ENDM

__popRegs MACRO regList:VARARG
LOCAL x
 IRP x,
  IF ((OPATTR(&x)) AND 10h)
   pop x
  ELSE
  .err
  ENDIF
 ENDM
ENDM

__saveWorkingRegs MACRO regList:VARARG
 IF _WINX_REGSAFETY
  __pushRegs
 ENDIF
ENDM

__restoreWorkingRegs MACRO regList:VARARG
 IF _WINX_REGSAFETY
  __popRegs
 ENDIF
ENDM

ZeroMemory MACRO dest,count
 __saveWorkingRegs edi
 makeMemPtr edi,&dest
 sub eax,eax
 IF ((OPATTR(count)) AND 4) ; const
   IF ((count AND 3) EQ 0)
    mov ecx,count/4
    rep stosd
   ELSEIF ((count AND (NOT 3)) EQ 0)
    mov ecx,count
    rep stosb
   ELSE
    mov ecx,count/4
    rep stosd
    mov cl,count AND 3
    rep stosb
   ENDIF
 ELSEIF ((OPATTR(count)) AND 12h) ; register or [mem]
   mov ecx,count
   shr ecx,2
   db 0F2h
   stosd
   mov ecx,count
   and cl,3
   db 0F2h
   stosb
 ENDIF
 __restoreWorkingRegs edi
ENDM

memcpy MACRO dest,src,count
LOCAL tmp
 __saveWorkingRegs edi,esi

 makeMemPtr edi,&dest
 makeMemPtr esi,&src

 IF ((OPATTR(count)) AND 4) ; const
   IF ((count AND 3) EQ 0)
    mov ecx,count/4
    rep movsd
   ELSEIF ((count AND (NOT 3)) EQ 0)
    mov ecx,count
    rep movsb
   ELSE
    mov ecx,count/4
    rep stosd
    mov cl,count AND 3
    rep stosb
   ENDIF
 ELSEIF ((OPATTR(count)) AND 12h) ; register or [mem]
   mov ecx,count
   shr ecx,2
   db 0F2h
   movsd
   mov ecx,count
   and cl,3
   db 0F2h
   movsb
 ENDIF

 __restoreWorkingRegs esi,edi
ENDM

return MACRO param
 IF (param EQ 0)
  sub eax,eax
  ret
 ELSEIF (param EQ 1)
  sub eax,eax
  inc eax
  ret
 ELSEIF (param EQ -1)
  sub eax,eax
  dec eax
  ret
 ELSE
  mov eax,param
  ret
 ENDIF
ENDM

SETBITFIELD MACRO base,fieldID,value
local masque,x,nshifts,isReg,I,wordsize
 masque=MASK fieldID
 x=masque
 nshifts=0
 REPT 33
  IF (x AND 1)
   EXITM
  ENDIF
  nshifts=nshifts+1
  x=x SHR 1
 ENDM
 and base,NOT masque
 isReg=FALSE
  IRP I,
   IFIDNI ,
     isReg=TRUE
     wordsize TEXTEQU
     EXITM
   ENDIF
   IFIDNI ,
     isReg=TRUE
     wordsize TEXTEQU
     EXITM
   ENDIF
  ENDM
  IF NOT(isReg)
    IRP I,
     IFIDNI ,
       isReg=TRUE
       wordsize TEXTEQU
       EXITM
     ENDIF
    ENDM
  ENDIF
 IF (isReg)
   shl value,nshifts
   and value,masque
   or wordsize ptr base,value
 ELSE
  IF value
    or base,(value SHL nshifts) AND masque
  ENDIF
 ENDIF
ENDM

GetFPUFlags MACRO
 fnstsw ax
 sahf
ENDM

GenericErrMsg MACRO hWnd,ErrMsg
LOCAL loc,errtxt,boxhdr
 jmp loc
 defstring boxhdr,'Error'
 defstring errtxt,&ErrMsg
loc:
 invoke MessageBox,hWnd,offset errtxt,offset boxhdr,MB_OK
ENDM

GenericErrMsgW MACRO hWnd,ErrMsg
LOCAL loc,errtxt,boxhdr
 jmp loc
 defstringW boxhdr,'Error'
 defstringW errtxt,&ErrMsg
loc:
 invoke MessageBoxW,hWnd,offset errtxt,offset boxhdr,MB_OK
ENDM

GenericErrMsgA MACRO hWnd,ErrMsg
LOCAL loc,errtxt,boxhdr
 jmp loc
 defstringA boxhdr,'Error'
 defstringA errtxt,&ErrMsg
loc:
 invoke MessageBoxA,hWnd,offset errtxt,offset boxhdr,MB_OK
ENDM

DISPLAYMSG_TITLE textequ <'Error'>

DISPLAYMSG MACRO errMsg
LOCAL loc,errtxt,boxhdr
 jmp loc
 defstring boxhdr,%DISPLAYMSG_TITLE
 defstring errtxt,&errMsg
loc:
 invoke MessageBox,NULL,offset errtxt,offset boxhdr,MB_OK
ENDM

__makeNumLabel MACRO lbl,num
 lbl&num:
ENDM

__setNumVarVal MACRO name,id,val
 name&id = val
ENDM

__getNumVarVal MACRO name,id
 LOCAL tStr
 tStr CATSTR ,
EXITM %tStr
ENDM

__setTxtVarVal MACRO name,id,val
 name&id textequ
ENDM

__num2string MACRO num
 LOCAL tStr
 tStr textequ
EXITM %tStr
ENDM

_enum MACRO enumID,enumList:VARARG
LOCAL x,cnt
 enumID typedef DWORD
 cnt=0
 IRP x,
  x=cnt
  cnt=cnt+1
 ENDM
ENDM

define MACRO id,val
 id = val
ENDM

ENDIF ;// _WIN32_EXTRA_DEFS_


GL.MAC

;****************************************************************************
;* gl.mac
;* Copyright (C) hardCode, 1999-2000
;****************************************************************************

IFNDEF _OPENGL_WRAPPERS_MAC_
_OPENGL_WRAPPERS_MAC_=1

_glAccum MACRO op,val
 gl_fpush val
 push op
 call glAccum
ENDM

_glAlphaFunc MACRO func,ref
 gl_fpush ref
 push func
 call glAlphaFunc
ENDM

;(GLsizei n,const GLuint* textures,GLboolean* residences)
; OUT: GLboolean
_glAreTexturesResident MACRO n,textures,residences
 invoke glAreTexturesResident,n,textures,residences
ENDM

; (GLint i)
_glArrayElement MACRO i
 invoke glArrayElement,i
ENDM

; (GLenum mode)
_glBegin MACRO mode
 invoke glBegin,mode
ENDM

; (GLenum target,GLuint texture)
_glBindTexture MACRO target,texture
 invoke glBindTexture,target,texture
ENDM

; (GLsizei width,GLsizei height,GLfloat xorig,GLfloat yorig,
;  GLfloat xmove,GLfloat ymove,const GLubyte* bitmap)
_glBitmap MACRO width,height,xorig,yorig,xmove,ymove,bitmap
 push bitmap
 gl_fpush ymove
 gl_fpush xmove
 gl_fpush yorig
 gl_fpush xorig
 gl_fpush height
 gl_fpush width
 call glBitmap
ENDM

; (GLenum sfactor,GLenum dfactor)
_glBlendFunc MACRO sfactor,dfactor
 invoke glBlendFunc,sfactor,dfactor
ENDM

; (GLuint list)
_glCallList MACRO list
 invoke glCallList,list
ENDM

; (GLsizei n, GLenum type, const GLvoid* lists)
_glCallLists MACRO n,type,lists
 invoke glCallLists,n,type,lists
ENDM

; (GLbitfield mask)
_glClear MACRO mask
 invoke glClear,mask
ENDM

; (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
_glClearAccum MACRO r,g,b,a
 glCall_f ,r,g,b,a
ENDM

; (GLclampf red,GLclampf green,GLclampf blue,GLclampf alpha)
_glClearColor MACRO r,g,b,a
 glCall_f ,r,g,b,a
ENDM

; (GLclampd depth)
_glClearDepth MACRO depth
 glCall_d ,depth
ENDM

; (GLfloat c)
_glClearIndex MACRO c
 glCall_f ,c
ENDM

; (GLint s)
_glClearStencil MACRO s
 invoke glClearStencil,s
ENDM

; (GLenum plane, const GLdouble* equation)
_glClipPlane MACRO plane,equation
 invoke glClipPlane,plane,equation
ENDM

; (GLbyte red, GLbyte green, GLbyte blue)
_glColor3b MACRO r,g,b
 invoke glColor3b,r,g,b
ENDM

; (const GLbyte* v)
_glColor3bv MACRO v
 invoke glColor3bv,v
ENDM

; (GLdouble red, GLdouble green, GLdouble blue)
_glColor3d MACRO R,G,B
 glCall_d ,R,G,B
ENDM

; (const GLdouble* v)
_glColor3dv MACRO v
 invoke glColor3dv,v
ENDM

; (GLfloat red, GLfloat green, GLfloat blue)
_glColor3f MACRO R,G,B
 glCall_f ,R,G,B
ENDM

; (const GLfloat* v)
_glColor3fv MACRO v
 invoke glColor3fv,v
ENDM

; (GLint red, GLint green, GLint blue)
_glColor3i MACRO r,g,b
 invoke glColor3i,r,g,b
ENDM

; (const GLint* v)
_glColor3iv MACRO v
 invoke glColor3iv,v
ENDM

; (GLshort red, GLshort green, GLshort blue)
_glColor3s MACRO r,g,b
 invoke glColor3s,r,g,b
ENDM

; (const GLshort* v)
_glColor3sv MACRO v
 invoke glColor3sv,v
ENDM

; (GLubyte red, GLubyte green, GLubyte blue)
_glColor3ub MACRO r,g,b
 invoke glColor3ub,r,g,b
ENDM

; (const GLubyte* v)
_glColor3ubv MACRO v
 invoke glColor3ubv,v
ENDM

; (GLuint red, GLuint green, GLuint blue)
_glColor3ui MACRO r,g,b
 invoke glColor3ui,r,g,b
ENDM

; (const GLuint* v)
_glColor3uiv MACRO v
 invoke glColor3uiv,v
ENDM

; (GLushort red, GLushort green, GLushort blue)
_glColor3us MACRO r,g,b
 invoke glColor3us,r,g,b
ENDM

; (const GLushort* v)
_glColor3usv MACRO v
 invoke glColor3usv,v
ENDM

; (GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha)
_glColor4b MACRO r,g,b,a
 invoke glColor4b,r,g,b,a
ENDM

; (const GLbyte* v)
_glColor4bv MACRO v
 invoke glColor4bv,v
ENDM

; (GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha)
_glColor4d MACRO r,g,b,a
 glCall_d ,r,g,b,a
ENDM

; (const GLdouble* v)
_glColor4dv MACRO v
 invoke glColor4dv,v
ENDM

; (GLfloat red,GLfloat green,GLfloat blue,GLfloat alpha)
_glColor4f MACRO r,g,b,a
 glCall_f ,r,g,b,a
ENDM

; (const GLfloat* v)
_glColor4fv MACRO v
 invoke glColor4fv,v
ENDM

; (GLint red, GLint green, GLint blue, GLint alpha)
_glColor4i MACRO r,g,b,a
 invoke glColor4i,r,g,b,a
ENDM

; (const GLint* v)
_glColor4iv MACRO v
 invoke glColor4iv,v
ENDM

; (GLshort red, GLshort green, GLshort blue, GLshort alpha)
_glColor4s MACRO r,g,b,a
 invoke glColor4s,r,g,b,a
ENDM

; (const GLshort* v)
_glColor4sv MACRO v
 invoke glColor4sv,v
ENDM

; (GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha)
_glColor4ub MACRO r,g,b,a
 invoke glColor4ub,r,g,b,a
ENDM

; (const GLubyte* v)
_glColor4ubv MACRO v
 invoke glColor4ubv,v
ENDM

; (GLuint red, GLuint green, GLuint blue, GLuint alpha)
_glColor4ui MACRO r,g,b,a
 invoke glColor4ui,r,g,b,a
ENDM

; (const GLuint* v)
_glColor4uiv MACRO v
 invoke glColor4uiv,v
ENDM

; (GLushort red, GLushort green, GLushort blue, GLushort alpha)
_glColor4us MACRO r,g,b,a
 invoke glColor4us,r,g,b,a
ENDM

; (const GLushort* v)
_glColor4usv MACRO v
 invoke glColor4usv,v
ENDM

; (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha)
_glColorMask MACRO r,g,b,a
 invoke glColorMask,r,g,b,a
ENDM

; (GLenum face, GLenum mode)
_glColorMaterial MACRO face,mode
 invoke glColorMaterial,face,mode
ENDM

; (GLint size, GLenum type, GLsizei stride, const GLvoid* pointer)
_glColorPointer MACRO size,type,stride,ptr
 invoke glColorPointer,size,type,stride,ptr
ENDM

; (GLint x, GLint y, GLsizei width, GLsizei height, GLenum type)
_glCopyPixels MACRO x,y,w,h,type
 invoke glCopyPixels,x,y,w,h,type
ENDM

; (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border)
_glCopyTexImage1D MACRO target,level,internalFormat,x,y,w,border
 invoke glCopyTexImage1D,target,level,internalFormat,x,y,w,border
ENDM

; (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border)
_glCopyTexImage2D MACRO target,level,internalFormat,x,y,w,h,border
 invoke glCopyTexImage2D,target,level,internalFormat,x,y,w,h,border
ENDM

; (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width)
_glCopyTexSubImage1D MACRO target,level,xoffset,x,y,width
 invoke glCopyTexSubImage1D,target,level,xoffset,x,y,width
ENDM

; (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height)
_glCopyTexSubImage2D MACRO target,level,xoffset,yoffset,x,y,width,height
 invoke glCopyTexSubImage2D,target,level,xoffset,yoffset,x,y,width,height
ENDM

; (GLenum mode)
_glCullFace MACRO mode
 invoke glCullFace,mode
ENDM

; (GLuint list, GLsizei range)
_glDeleteLists MACRO list,range
 invoke glDeleteLists,list,range
ENDM

; (GLsizei n, const GLuint* textures)
_glDeleteTextures MACRO n,textures
 invoke glDeleteTextures,n,textures
ENDM

; (GLenum func)
_glDepthFunc MACRO func
 invoke glDepthFunc,func
ENDM

; (GLboolean flag)
_glDepthMask MACRO flag
 invoke glDepthMask,flag
ENDM

; (GLclampd zNear, GLclampd zFar)
_glDepthRange MACRO zNear,zFar
 glCall_d ,zNear,zFar
ENDM

; (GLenum cap)
_glDisable MACRO cap
 invoke glDisable,cap
ENDM

; (GLsizei range)
_glGenLists  MACRO range
 invoke glGenLists,range
ENDM

_glFrustum MACRO l,r,b,t,zNear,zFar
 glCall_d ,l,r,b,t,zNear,zFar
ENDM

_glOrtho MACRO l,r,b,t,zNear,zFar
 glCall_d ,l,r,b,t,zNear,zFar
ENDM

_glTranslatef MACRO x,y,z
 glCall_f ,x,y,z
ENDM

_glNormal3f MACRO nx,ny,nz
 glCall_f ,nx,ny,nz
ENDM

_glNormal3d MACRO nx,ny,nz
 glCall_d ,nx,ny,nz
ENDM

_glVertex3f MACRO x,y,z
 glCall_f ,x,y,z
ENDM

_glVertex2f MACRO x,y
 glCall_f ,x,y
ENDM

_glTexCoord2f MACRO u,v
 glCall_f ,u,v
ENDM

_glTexEnvfv MACRO target,pname,lpparam
 invoke glTexEnvfv,target,pname,lpparam
ENDM

_glTexEnvf MACRO target,pname,param
 gl_fpush param
 push pname
 push target
 call glTexEnvf
ENDM

_glTexEnvi MACRO target,pname,param
 push param
 push pname
 push target
 call glTexEnvf
ENDM

_glTexParameterf MACRO target,pname,param
 gl_fpush param
 push pname
 push target
 call glTexParameterf
ENDM

_glTexParameteri MACRO target,pname,param
 push param
 push pname
 push target
 call glTexParameteri
ENDM

_glEnd MACRO
 invoke glEnd
ENDM

_glEndList MACRO
 invoke glEndList
ENDM

_glEnable MACRO cap
 invoke glEnable,cap
ENDM

_glMatrixMode MACRO mode
 invoke glMatrixMode,mode
ENDM

_glShadeModel MACRO mode
 invoke glShadeModel,mode
ENDM

_glLoadIdentity MACRO
 invoke glLoadIdentity
ENDM

_glFrontFace MACRO mode
 invoke glFrontFace,mode
ENDM

_glViewport MACRO x,y,w,h
 push h
 push w
 push y
 push x
 call glViewport
ENDM

; (GLdouble u)
_glEvalCoord1d MACRO u
 glCall_d ,u
ENDM

; (GLfloat u)
_glEvalCoord1f MACRO u
 glCall_f ,u
ENDM

; (GLdouble u, GLdouble v)
_glEvalCoord2d MACRO u,v
 glCall_d ,u,v
ENDM

; (GLfloat u, GLfloat v)
_glEvalCoord2f MACRO u,v
 glCall_f u,v
ENDM

; (GLenum pname, GLfloat param)
_glFogf MACRO pname,param
 gl_fpush param
 push target
 call glFogf
ENDM

; (GLdouble c)
_glIndexd MACRO c
 glCall_d ,c
ENDM

; (GLfloat c)
_glIndexf MACRO c
 glCall_f ,c
ENDM

; (GLenum pname, GLfloat param)
_glLightModelf MACRO pname,param
 gl_fpush param
 push pname
 call glLightModelf
ENDM

; (GLenum light, GLenum pname, GLfloat param)
_glLightf MACRO light,pname,param
 gl_fpush param
 push pname
 push light
 call glLightf
ENDM

; (GLfloat width)
_glLineWidth MACRO width
 glCall_f ,width
ENDM

; (GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble* points)
_glMap1d MACRO target,u1,u2,stride,order,points
 push points
 push order
 push stride
 gl_dpush u2
 gl_dpush u1
 _GL_DPUSH_FIX
 push target
 call glMap1d
ENDM

; (GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat* points)
_glMap1f MACRO target,u1,u2,stride,order,points
 push points
 push order
 push stride
 gl_fpush u2
 gl_fpush u1
 push target
 call glMap1f
ENDM

; (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble* points)
_glMap2d MACRO target,u1,u2,ustride,uorder,v1,v2,vstride,vorder,points
 push points
 push vorder
 push vstride
 gl_dpush v2
 gl_dpush v1
 _GL_DPUSH_FIX
 push uorder
 push ustride
 gl_dpush u2
 gl_dpush u1
 _GL_DPUSH_FIX
 push target
 call glMap2d
ENDM

; (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat* points);
_glMap2f MACRO target,u1,u2,ustride,uorder,v1,v2,vstride,vorder,points
 push points
 push vorder
 push vstride
 gl_fpush v2
 gl_fpush v1
 push uorder
 push ustride
 gl_fpush u2
 gl_fpush u1
 push target
 call glMap2f
ENDM

; (GLint un, GLdouble u1, GLdouble u2)
_glMapGrid1d MACRO un,u1,u2
 gl_dpush u2
 gl_dpush u1
 _GL_DPUSH_FIX
 push un
 call glMapGrid1d
ENDM

; (GLint un, GLfloat u1, GLfloat u2)
_glMapGrid1f MACRO un,u1,u2
 gl_fpush u2
 gl_fpush u1
 push un
 call glMapGrid1f
ENDM

; (GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2)
_glMapGrid2d MACRO un,u1,u2,vn,v1,v2
 gl_dpush v2
 gl_dpush v1
 _GL_DPUSH_FIX
 push vn
 gl_dpush u2
 gl_dpush u1
 _GL_DPUSH_FIX
 push un
 call glMapGrid2d
ENDM

; (GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2)
_glMapGrid2f MACRO un,u1,u2,vn,v1,v2
 gl_fpush v2
 gl_fpush v1
 push vn
 gl_fpush u2
 gl_fpush u1
 push un
 call glMapGrid2f
ENDM

; (GLenum face, GLenum pname, GLfloat param)
_glMaterialf MACRO face,pname,param
 gl_fpush param
 push pname
 push face
 call glMaterialf
ENDM

; (GLfloat xfactor, GLfloat yfactor)
_glPixelZoom MACRO xfactor,yfactor
 glCall_f ,xfactor,yfactor
ENDM

; (GLfloat size)
_glPointSize MACRO size
 glCall_f ,size
ENDM

; (GLfloat factor, GLfloat units)
_glPolygonOffset MACRO factor,units
 glCall_f ,factor,units
ENDM

; (GLdouble x, GLdouble y)
_glRasterPos2d MACRO x,y
 glCall_d ,x,y
ENDM

; (GLfloat x, GLfloat y)
_glRasterPos2f MACRO x,y
 glCall_f ,x,y
ENDM

; (GLdouble x, GLdouble y, GLdouble z)
_glRasterPos3d MACRO x,y,z
 glCall_d ,x,y,z
ENDM

; (GLfloat x, GLfloat y, GLfloat z)
_glRasterPos3f MACRO x,y,z
 glCall_f ,x,y,z
ENDM

; (GLdouble x, GLdouble y, GLdouble z, GLdouble w)
_glRasterPos4d MACRO x,y,z,w
 glCall_d ,x,y,z,w
ENDM

; (GLfloat x, GLfloat y, GLfloat z, GLfloat w)
_glRasterPos4f MACRO x,y,z,w
 glCall_f ,x,y,z,w
ENDM

; (GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2)
_glRectd MACRO x1,y1,x2,y2
 glCall_d ,x1,y1,x2,y2
ENDM

; (GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2)
_glRectf MACRO x1,y1,x2,y2
 glCall_f ,x1,y1,x2,y2
ENDM

; (GLdouble angle, GLdouble x, GLdouble y, GLdouble z)
_glRotated MACRO angle,x,y,z
 glCall_d ,angle,x,y,z
ENDM

; (GLfloat angle, GLfloat x, GLfloat y, GLfloat z)
_glRotatef MACRO angle,x,y,z
 glCall_f ,angle,x,y,z
ENDM

; (GLdouble x, GLdouble y, GLdouble z)
_glScaled MACRO x,y,z
 glCall_d ,x,y,z
ENDM

; (GLfloat x, GLfloat y, GLfloat z)
_glScalef MACRO x,y,z
 glCall_f ,x,y,z
ENDM

ENDIF ;// _OPENGL_WRAPPERS_MAC_

comment $

glCall_f MACRO glApiName:REQ,Args:VARARG
    GL_PUSH_FLOAT_ARGS Args
    call glApiName
ENDM

;
glCall_d MACRO glApiName:REQ,Args:VARARG
    GL_PUSH_DOUBLE_ARGS Args
    call glApiName
ENDM

GL_PUSH_FLOAT_ARGS MACRO a1,a2,a3,a4,a5,a6,a7,a8,a9,a10
    IFNB
        GL_PUSH_FLOAT_ARGS ,,,,,,,,
        gl_fpush a1
    ENDIF
ENDM

;
GL_PUSH_DOUBLE_ARGS MACRO a1,a2,a3,a4,a5,a6,a7,a8,a9,a10
    IFNB
        GL_PUSH_DOUBLE_ARGS ,,,,,,,,
        gl_dpush a1
    ENDIF
    _GL_DPUSH_FIX
ENDM

       $


OpenGL Basics - Samples

OpenGL Code Samples

Vu dans ... https://www.opengl.org/archives/resources/code/samples/simple/

Code Samples released by SGI with the OpenGL 1.1 distribution in 1997.

These are very useful for beginning OpenGL coding and learning OpenGL program structure.

Advanced rendering and later extensions are not covered in these examples.

Simple program to test accumulation.

Source code: accum.c.

Simple program to test bitmap rendering.

Source code: bitmap1.c.

Simple program to test bitmap rendering.

Source code: bitmap2.c.

Program that demonstrates reading back the framebuffer and zooming the pixels.

Source code: copy.c.

Simple program to test depth buffering.

Source code: depth.c.

Not so simple test that does all sorts of stuff (see the source for more details).

Source code: logo.c.

Simple nurb (non-uniform rational b-spline) program.

Source code: nurb.c.

Program that uses packed pixel types for DrawPixels, ReadPixels, TexImage2D, and GetTexImage.

Use -h option to display a list of command line options. Use Key h in the created window for key options.

Source code: packedpix.c.

All the OpenGL primitives all in a row.

Source code: prim.c.

Another "megatest" with lots of options.

Source code: quad.c.

Test of auto texture coordinate generation.

Source code: sphere.c.

Neat little warping program. Click in the window to add control points, and press space bar to start warping.

Source code: stretch.c.

Ye 'ole classic (red in this case) teapot.

Source code: teapot.c.

Simple example of using two textures.

Source code: twotextures.c.

Neat wave program with some contouring options (press 'c').

Source code: wave.c.


accum.C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <GL/glut.h>

GLenum doubleBuffer;
GLint thing1, thing2;

static void Init(void)
{

    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClearAccum(0.0, 0.0, 0.0, 0.0);

    thing1 = glGenLists(1);
    glNewList(thing1, GL_COMPILE);
    glColor3f(1.0, 0.0, 0.0);
    glRectf(-1.0, -1.0, 1.0, 0.0);
    glEndList();

    thing2 = glGenLists(1);
    glNewList(thing2, GL_COMPILE);
    glColor3f(0.0, 1.0, 0.0);
    glRectf(0.0, -1.0, 1.0, 1.0);
    glEndList();
}

static void Reshape(int width, int height)
{

    glViewport(0, 0, width, height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

static void Key(unsigned char key, int x, int y)
{

    switch (key) {
      case '1':
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    glutPostRedisplay();
    break;
      case '2':
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    glutPostRedisplay();
    break;
      case 27:
    exit(0);
    }
}

static void Draw(void)
{

    glPushMatrix();

    glScalef(0.8, 0.8, 1.0);

    glClear(GL_COLOR_BUFFER_BIT);
    glCallList(thing1);
    glAccum(GL_LOAD, 0.5);

    glClear(GL_COLOR_BUFFER_BIT);
    glCallList(thing2);
    glAccum(GL_ACCUM, 0.5);

    glAccum(GL_RETURN, 1.0);

    glPopMatrix();

    if (doubleBuffer) {
    glutSwapBuffers();
    } else {
    glFlush();
    }
}

static void Args(int argc, char **argv)
{
    GLint i;

    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    }
    }
}

int main(int argc, char **argv)
{
    GLenum type;

    glutInit(&argc, argv);
    Args(argc, argv);

    type = GLUT_RGB | GLUT_ACCUM;
    type |= (doubleBuffer) ? GLUT_DOUBLE : GLUT_SINGLE;
    glutInitDisplayMode(type);
    glutInitWindowSize(300, 300);
    glutCreateWindow("Accum Test");

    Init();

    glutReshapeFunc(Reshape);
    glutKeyboardFunc(Key);
    glutDisplayFunc(Draw);
    glutMainLoop();
}


bitmap1.C

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <GL/glut.h>

#define OPENGL_WIDTH 24
#define OPENGL_HEIGHT 13

GLenum rgb, doubleBuffer;

float boxA[3] = {
    0, 0, 0
};
float boxB[3] = {
    -100, 0, 0
};
float boxC[3] = {
    100, 0, 0
};
float boxD[3] = {
    0, 95, 0
};
float boxE[3] = {
    0, -105, 0
};
GLubyte OpenGL_bits1[] = {
   0x00, 0x03, 0x00,
   0x7f, 0xfb, 0xff,
   0x7f, 0xfb, 0xff,
   0x00, 0x03, 0x00,
   0x3e, 0x8f, 0xb7,
   0x63, 0xdb, 0xb0,
   0x63, 0xdb, 0xb7,
   0x63, 0xdb, 0xb6,
   0x63, 0x8f, 0xf3,
   0x63, 0x00, 0x00,
   0x63, 0x00, 0x00,
   0x63, 0x00, 0x00,
   0x3e, 0x00, 0x00,
};
GLubyte OpenGL_bits2[] = {
   0x00, 0x00, 0x00,
   0xff, 0xff, 0x01,
   0xff, 0xff, 0x01,
   0x00, 0x00, 0x00,
   0xf9, 0xfc, 0x01,
   0x8d, 0x0d, 0x00,
   0x8d, 0x0d, 0x00,
   0x8d, 0x0d, 0x00,
   0xcc, 0x0d, 0x00,
   0x0c, 0x4c, 0x0a,
   0x0c, 0x4c, 0x0e,
   0x8c, 0xed, 0x0e,
   0xf8, 0x0c, 0x00,
};
GLubyte logo_bits[] = {
   0x00, 0x66, 0x66,
   0xff, 0x66, 0x66,
   0x00, 0x00, 0x00,
   0xff, 0x3c, 0x3c,
   0x00, 0x42, 0x40,
   0xff, 0x42, 0x40,
   0x00, 0x41, 0x40,
   0xff, 0x21, 0x20,
   0x00, 0x2f, 0x20,
   0xff, 0x20, 0x20,
   0x00, 0x10, 0x90,
   0xff, 0x10, 0x90,
   0x00, 0x0f, 0x10,
   0xff, 0x00, 0x00,
   0x00, 0x66, 0x66,
   0xff, 0x66, 0x66,
};

static void Init(void)
{

    if (!rgb) {
    glutSetColor(0, 0.0, 0.0, 0.0);
    glutSetColor(1, 1.0, 0.0, 0.0);
    glutSetColor(2, 0.0, 1.0, 0.0);
    glutSetColor(3, 1.0, 1.0, 0.0);
    glutSetColor(4, 0.0, 0.0, 1.0);
    glutSetColor(5, 1.0, 0.0, 1.0);
    glutSetColor(6, 0.0, 1.0, 1.0);
    glutSetColor(7, 1.0, 1.0, 1.0);
    }

    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClearIndex(0.0);
}

static void Reshape(int width, int height)
{

    glViewport(0, 0, width, height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(-175, 175, -175, 175);
    glMatrixMode(GL_MODELVIEW);
}

static void Key(unsigned char key, int x, int y)
{

    switch (key) {
      case 27:
    exit(0);
    }
}

static void Draw(void)
{

    glClear(GL_COLOR_BUFFER_BIT);

    glRasterPos3fv(boxA);
    glPixelStorei(GL_UNPACK_ROW_LENGTH, 24);
    glPixelStorei(GL_UNPACK_SKIP_PIXELS, 8);
    glPixelStorei(GL_UNPACK_SKIP_ROWS, 2);
    glPixelStorei(GL_UNPACK_LSB_FIRST, GL_FALSE);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glBitmap(16, 12, 8.0, 0.0, 0.0, 0.0, logo_bits);

    glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
    glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
    glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
    glPixelStorei(GL_UNPACK_LSB_FIRST, GL_TRUE);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    (rgb) ? glColor3f(1.0, 1.0, 1.0) : glIndexi(7);
    glRasterPos3fv(boxB);
    glBitmap(OPENGL_WIDTH, OPENGL_HEIGHT, OPENGL_WIDTH, 0.0, OPENGL_WIDTH, 0.0,
         OpenGL_bits1);
    glBitmap(OPENGL_WIDTH, OPENGL_HEIGHT, OPENGL_WIDTH, 0.0, OPENGL_WIDTH, 0.0,
         OpenGL_bits2);

    (rgb) ? glColor3f(0.0, 1.0, 1.0) : glIndexi(6);
    glRasterPos3fv(boxC);
    glBitmap(OPENGL_WIDTH, OPENGL_HEIGHT, OPENGL_WIDTH, 0.0, OPENGL_WIDTH, 0.0,
         OpenGL_bits1);
    glBitmap(OPENGL_WIDTH, OPENGL_HEIGHT, OPENGL_WIDTH, 0.0, OPENGL_WIDTH, 0.0,
         OpenGL_bits2);

    (rgb) ? glColor3f(1.0, 0.0, 1.0) : glIndexi(5);
    glRasterPos3fv(boxD);
    glBitmap(OPENGL_WIDTH, OPENGL_HEIGHT, OPENGL_WIDTH, 0.0, OPENGL_WIDTH, 0.0,
         OpenGL_bits1);
    glBitmap(OPENGL_WIDTH, OPENGL_HEIGHT, OPENGL_WIDTH, 0.0, OPENGL_WIDTH, 0.0,
         OpenGL_bits2);

    (rgb) ? glColor3f(1.0, 1.0, 0.0) : glIndexi(3);
    glRasterPos3fv(boxE);
    glBitmap(OPENGL_WIDTH, OPENGL_HEIGHT, OPENGL_WIDTH, 0.0, OPENGL_WIDTH, 0.0,
         OpenGL_bits1);
    glBitmap(OPENGL_WIDTH, OPENGL_HEIGHT, OPENGL_WIDTH, 0.0, OPENGL_WIDTH, 0.0,
         OpenGL_bits2);

    if (doubleBuffer) {
    glutSwapBuffers();
    } else {
    glFlush();
    }
}

static void Args(int argc, char **argv)
{
    GLint i;

    rgb = GL_TRUE;
    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-ci") == 0) {
        rgb = GL_FALSE;
    } else if (strcmp(argv[i], "-rgb") == 0) {
        rgb = GL_TRUE;
    } else if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    }
    }
}

int main(int argc, char **argv)
{
    GLenum type;

    glutInit(&argc, argv);
    Args(argc, argv);

    type = (rgb) ? GLUT_RGB : GLUT_INDEX;
    type |= (doubleBuffer) ? GLUT_DOUBLE : GLUT_SINGLE;
    glutInitDisplayMode(type);
    glutInitWindowSize(300, 300);
    glutCreateWindow("Bitmap Test");

    Init();

    glutReshapeFunc(Reshape);
    glutKeyboardFunc(Key);
    glutDisplayFunc(Draw);
    glutMainLoop();
}


copy.C

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <GL/glut.h>
#include "rgb.h"

GLenum doubleBuffer;
GLint windW = 300, windH = 300;

RGBImageRec *image = NULL;
float point[3];
float zoom;
GLint x, y;

static void Init(void)
{

    glClearColor(0.0, 0.0, 0.0, 0.0);

    x = 0;
    y = windH;
    zoom = 1.8;
}

static void Reshape(int width, int height)
{

    windW = width;
    windH = height;

    glViewport(0, 0, windW, windH);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0, windW, 0, windH);
    glMatrixMode(GL_MODELVIEW);
}

static void Key(unsigned char key, int x, int y)
{

    switch (key) {
      case 27:
        exit(0);
    }
}

static void SpecialKey(int key, int x, int y)
{

    switch (key) {
      case GLUT_KEY_UP:
    zoom += 0.2;
    glutPostRedisplay();
    break;
      case GLUT_KEY_DOWN:
    zoom -= 0.2;
    if (zoom < 0.2) {
        zoom = 0.2;
    }
    glutPostRedisplay();
    break;
    }
}

static void Mouse(int button, int state, int mouseX, int mouseY)
{

    if (state == GLUT_DOWN) {
    x = mouseX;
    y = mouseY;
    glutPostRedisplay();
    }
}

static void Draw(void)
{

    glClear(GL_COLOR_BUFFER_BIT);

    point[0] = (windW / 2) - (image->sizeX / 2);
    point[1] = (windH / 2) - (image->sizeY / 2);
    point[2] = 0;
    glRasterPos3fv(point);

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glPixelZoom(1.0, 1.0);
    glDrawPixels(image->sizeX, image->sizeY, GL_RGB, GL_UNSIGNED_BYTE,
         image->data);

    point[0] = (float)x;
    point[1] = windH - (float)y;
    point[2] = 0.0;
    glRasterPos3fv(point);

    glPixelZoom(zoom, zoom);
    glCopyPixels((windW/2)-(image->sizeX/2), (windH/2)-(image->sizeY/2),
         image->sizeX, image->sizeY, GL_COLOR);

    if (doubleBuffer) {
    glutSwapBuffers();
    } else {
    glFlush();
    }
}

static void Args(int argc, char **argv)
{
    GLint i;

    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    } else if (strcmp(argv[i], "-f") == 0) {
        if (i+1 >= argc || argv[i+1][0] == '-') {
        printf("-f (No file name).\n");
        exit(1);
        } else {
        image = rgbImageLoad(argv[++i]);
        if (image == NULL) {
            printf("-f (bad file name).\n");
            exit(1);
        }
        }
    }
    }
}

int main(int argc, char **argv)
{
    GLenum type;

    glutInit(&argc, argv);
    Args(argc, argv);

    if (image == NULL) {
    printf("No texture file.\n");
    exit(1);
    }

    type = GLUT_RGB;
    type |= (doubleBuffer) ? GLUT_DOUBLE : GLUT_SINGLE;
    glutInitDisplayMode(type);
    glutInitWindowSize(windW, windH);
    glutCreateWindow("Copy Test");

    Init();

    glutReshapeFunc(Reshape);
    glutKeyboardFunc(Key);
    glutSpecialFunc(SpecialKey);
    glutMouseFunc(Mouse);
    glutDisplayFunc(Draw);
    glutMainLoop();
}


depth.C

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <GL/glut.h>

#define CI_OFFSET_1 16
#define CI_OFFSET_2 32

GLenum rgb, doubleBuffer;

GLenum antiAlias, stipple;
GLubyte stippleBits[32*4] = {
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
    0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55,
};

static void Init(void)
{
    GLint i;

    if (!rgb) {
    for (i = 0; i < 16; i++) {
        glutSetColor(i+CI_OFFSET_1, 0.0, 0.0, i/15.0);
        glutSetColor(i+CI_OFFSET_2, 0.0, i/15.0, 0.0);
    }
    }

    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClearIndex(0.0);

    glPolygonStipple(stippleBits);

    antiAlias = GL_FALSE;
    stipple = GL_FALSE;
}

static void Reshape(int width, int height)
{

    glViewport(0, 0, width, height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-1.0, 1.0, -1.0, 1.0, -0.5, 1000.0);
    glMatrixMode(GL_MODELVIEW);
}

static void Key(unsigned char key, int x, int y)
{

    switch (key) {
      case '1':
    antiAlias = !antiAlias;
    glutPostRedisplay();
    break;
      case '2':
    stipple = !stipple;
    glutPostRedisplay();
    break;
      case 27:
    exit(0);
    }
}

static void Draw(void)
{
    GLint ci1, ci2;

    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    if (antiAlias) {
    ci1 = CI_OFFSET_1;
    ci2 = CI_OFFSET_2;
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    glEnable(GL_BLEND);
    glEnable(GL_POLYGON_SMOOTH);
    glDisable(GL_DEPTH_TEST);
    } else {
    ci1 = 4;
    ci2 = 2;
    glDisable(GL_BLEND);
    glDisable(GL_POLYGON_SMOOTH);
    glEnable(GL_DEPTH_TEST);
    }

    if (stipple) {
    glEnable(GL_POLYGON_STIPPLE);
    } else {
    glDisable(GL_POLYGON_STIPPLE);
    }

    glBegin(GL_TRIANGLES);
    (rgb) ? glColor3f(0.0, 0.0, 1.0) : glIndexi(ci1);
    glVertex3f( 0.9, -0.9, -30.0);
    glVertex3f( 0.9,  0.9, -30.0);
    glVertex3f(-0.9,  0.0, -30.0);
    (rgb) ? glColor3f(0.0, 1.0, 0.0) : glIndexi(ci2);
    glVertex3f(-0.9, -0.9, -40.0);
    glVertex3f(-0.9,  0.9, -40.0);
    glVertex3f( 0.9,  0.0, -25.0);
    glEnd();

    if (doubleBuffer) {
    glutSwapBuffers();
    } else {
    glFlush();
    }
}

static void Args(int argc, char **argv)
{
    GLint i;

    rgb = GL_TRUE;
    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-ci") == 0) {
        rgb = GL_FALSE;
    } else if (strcmp(argv[i], "-rgb") == 0) {
        rgb = GL_TRUE;
    } else if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    }
    }
}

int main(int argc, char **argv)
{
    GLenum type;

    glutInit(&argc, argv);
    Args(argc, argv);

    type = GLUT_DEPTH;
    type |= (rgb) ? GLUT_RGB : GLUT_INDEX;
    type |= (doubleBuffer) ? GLUT_DOUBLE : GLUT_SINGLE;
    glutInitDisplayMode(type);
    glutInitWindowSize(300, 300);
    glutCreateWindow("Depth Test");

    Init();

    glutReshapeFunc(Reshape);
    glutKeyboardFunc(Key);
    glutDisplayFunc(Draw);
    glutMainLoop();
}


logo.C

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <GL/glut.h>

#define PI 3.141592654

#define	BLACK 0
#define	GRAY 128
#define	WHITE 255
#define BL 0x00
#define WH 0xFF
#define RD 0xA4,0x00,0x00,0xFF
#define WT 0xFF,0xFF,0xFF,0xFF

#define	CHECKIMAGEWIDTH 8
#define	CHECKIMAGEHEIGHT 8
#define	BRICKIMAGEWIDTH 16
#define	BRICKIMAGEHEIGHT 16

GLenum doubleBuffer;

float black[3] = {0.0, 0.0, 0.0};
float white[3] = {1.0, 1.0, 1.0};
float gray[3] = {0.5, 0.5, 0.5};
float blue[3] = {0.0, 0.0, 1.0};
GLint colorIndexes[3] = {0, 200, 255};

GLenum polyMode;
GLenum dithering;
GLenum shade;
GLenum doStipple;

double plane[4] = {1.0, 0.0, -1.0, 0.0};
float xRotation = 30.0, yRotation = 30.0;
float zTranslation = -15.0;

GLint singleCylinder;
GLint doubleCylinder;
GLint elbow, logo;

GLubyte checkImage[3*CHECKIMAGEWIDTH*CHECKIMAGEHEIGHT] = {
    BL, BL, BL, WH, WH, WH, BL, BL, BL, WH, WH, WH, BL, BL, BL, WH,
    WH, WH, BL, BL, BL, WH, WH, WH, WH, WH, WH, BL, BL, BL, WH, WH,
    WH, BL, BL, BL, WH, WH, WH, BL, BL, BL, WH, WH, WH, BL, BL, BL,
    BL, BL, BL, WH, WH, WH, BL, BL, BL, WH, WH, WH, BL, BL, BL, WH,
    WH, WH, BL, BL, BL, WH, WH, WH, WH, WH, WH, BL, BL, BL, WH, WH,
    WH, BL, BL, BL, WH, WH, WH, BL, BL, BL, WH, WH, WH, BL, BL, BL,
    BL, BL, BL, WH, WH, WH, BL, BL, BL, WH, WH, WH, BL, BL, BL, WH,
    WH, WH, BL, BL, BL, WH, WH, WH, WH, WH, WH, BL, BL, BL, WH, WH,
    WH, BL, BL, BL, WH, WH, WH, BL, BL, BL, WH, WH, WH, BL, BL, BL,
    BL, BL, BL, WH, WH, WH, BL, BL, BL, WH, WH, WH, BL, BL, BL, WH,
    WH, WH, BL, BL, BL, WH, WH, WH, WH, WH, WH, BL, BL, BL, WH, WH,
    WH, BL, BL, BL, WH, WH, WH, BL, BL, BL, WH, WH, WH, BL, BL, BL,
};
GLubyte brickImage[4*BRICKIMAGEWIDTH*BRICKIMAGEHEIGHT] = {
    RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD,
    WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT,
    RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD,
    RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD,
    RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD,
    RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD,
    WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT,
    RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD,
    WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT,
    RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD
};

GLubyte *image = checkImage;
GLint imageHeight = CHECKIMAGEHEIGHT;
GLint imageWidth = CHECKIMAGEWIDTH;

float decal[] = {
    GL_DECAL,
};
float modulate[] = {
    GL_MODULATE,
};
float repeat[] = {
    GL_REPEAT,
};
float nearest[] = {
    GL_NEAREST,
};

GLubyte stipple[4*32] = {
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,

    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,

    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,
    0x00, 0x0F, 0xF0, 0x00,

    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
};

float tscp[18][2] = {
    {
    0.0, 0.0
    },
    {
    1.0, 0.0
    },
    {
    0.0, 0.125
    },
    {
    1.0, 0.125
    },
    {
    0.0, 0.250
    },
    {
    1.0, 0.25
    },
    {
    0.0, 0.375
    },
    {
    1.0, 0.375
    },
    {
    0.0, 0.50
    },
    {
    1.0, 0.50
    },
    {
    0.0, 0.625
    },
    {
    1.0, 0.625
    },
    {
    0.0, 0.75
    },
    {
    1.0, 0.75
    },
    {
    0.0, 0.875
    },
    {
    1.0, 0.875
    },
    {
    0.0, 1.0
    },
    {
    1.0, 1.0
    }
};
float scp[18][3] = {
    {
    1.000000, 0.000000, 0.000000
    },
    {
    1.000000, 0.000000, 5.000000
    },
    {
    0.707107, 0.707107, 0.000000
    },
    {
    0.707107, 0.707107, 5.000000
    },
    {
    0.000000, 1.000000, 0.000000
    },
    {
    0.000000, 1.000000, 5.000000
    },
    {
    -0.707107, 0.707107, 0.000000
    },
    {
    -0.707107, 0.707107, 5.000000
    },
    {
    -1.000000, 0.000000, 0.000000
    },
    {
    -1.000000, 0.000000, 5.000000
    },
    {
    -0.707107, -0.707107, 0.000000
    },
    {
    -0.707107, -0.707107, 5.000000
    },
    {
    0.000000, -1.000000, 0.000000
    },
    {
    0.000000, -1.000000, 5.000000
    },
    {
    0.707107, -0.707107, 0.000000
    },
    {
    0.707107, -0.707107, 5.000000
    },
    {
    1.000000, 0.000000, 0.000000
    },
    {
    1.000000, 0.000000, 5.000000
    }
};
float dcp[18][3] = {
    {
    1.000000, 0.000000, 0.000000
    },
    {
    1.000000, 0.000000, 7.000000
    },
    {
    0.707107, 0.707107, 0.000000
    },
    {
    0.707107, 0.707107, 7.000000
    },
    {
    0.000000, 1.000000, 0.000000
    },
    {
    0.000000, 1.000000, 7.000000
    },
    {
    -0.707107, 0.707107, 0.000000
    },
    {
    -0.707107, 0.707107, 7.000000
    },
    {
    -1.000000, 0.000000, 0.000000
    },
    {
    -1.000000, 0.000000, 7.000000
    },
    {
    -0.707107, -0.707107, 0.000000
    },
    {
    -0.707107, -0.707107, 7.000000
    },
    {
    0.000000, -1.000000, 0.000000
    },
    {
    0.000000, -1.000000, 7.000000
    },
    {
    0.707107, -0.707107, 0.000000
    },
    {
    0.707107, -0.707107, 7.000000
    },
    {
    1.000000, 0.000000, 0.000000
    },
    {
    1.000000, 0.000000, 7.000000
    }
};
float ep[7][9][3] = {
    {
    {
        1.000000, 0.000000, 0.000000
    },
    {
        0.707107, 0.707107, 0.000000
    },
    {
        0.000000, 1.000000, 0.000000
    },
    {
        -0.707107, 0.707107, 0.000000
    },
    {
        -1.000000, 0.000000, 0.000000
    },
    {
        -0.707107, -0.707107, 0.000000
    },
    {
        0.000000, -1.000000, 0.000000
    },
    {
        0.707107, -0.707107, 0.000000
    },
    {
        1.000000, 0.000000, 0.000000
    }
    },
    {
    {
        1.000000, 0.034074, 0.258819
    },
    {
        0.707107, 0.717087, 0.075806
    },
    {
        0.000000, 1.000000, 0.000000
    },
    {
        -0.707107, 0.717087, 0.075806
    },
    {
        -1.000000, 0.034074, 0.258819
    },
    {
        -0.707107, -0.648939, 0.441832
    },
    {
        0.000000, -0.931852, 0.517638
    },
    {
        0.707107, -0.648939, 0.441832
    },
    {
        1.000000, 0.034074, 0.258819
    }
    },
    {
    {
        1.000000, 0.133975, 0.500000
    },
    {
        0.707107, 0.746347, 0.146447
    },
    {
        0.000000, 1.000000, 0.000000
    },
    {
        -0.707107, 0.746347, 0.146447
    },
    {
        -1.000000, 0.133975, 0.500000
    },
    {
        -0.707107, -0.478398, 0.853553
    },
    {
        0.000000, -0.732051, 1.000000
    },
    {
        0.707107, -0.478398, 0.853553
    },
    {
        1.000000, 0.133975, 0.500000
    }
    },
    {
    {
        1.000000, 0.292893, 0.707107
    },
    {
        0.707107, 0.792893, 0.207107
    },
    {
        0.000000, 1.000000, 0.000000
    },
    {
        -0.707107, 0.792893, 0.207107
    },
    {
        -1.000000, 0.292893, 0.707107
    },
    {
        -0.707107, -0.207107, 1.207107
    },
    {
        0.000000, -0.414214, 1.414214
    },
    {
        0.707107, -0.207107, 1.207107
    },
    {
        1.000000, 0.292893, 0.707107
    }
    },
    {
    {
        1.000000, 0.500000, 0.866025
    },
    {
        0.707107, 0.853553, 0.253653
    },
    {
        0.000000, 1.000000, 0.000000
    },
    {
        -0.707107, 0.853553, 0.253653
    },
    {
        -1.000000, 0.500000, 0.866025
    },
    {
        -0.707107, 0.146447, 1.478398
    },
    {
        0.000000, 0.000000, 1.732051
    },
    {
        0.707107, 0.146447, 1.478398
    },
    {
        1.000000, 0.500000, 0.866025
    }
    },
    {
    {
        1.000000, 0.741181, 0.965926
    },
    {
        0.707107, 0.924194, 0.282913
    },
    {
        0.000000, 1.000000, 0.000000
    },
    {
        -0.707107, 0.924194, 0.282913
    },
    {
        -1.000000, 0.741181, 0.965926
    },
    {
        -0.707107, 0.558168, 1.648939
    },
    {
        0.000000, 0.482362, 1.931852
    },
    {
        0.707107, 0.558168, 1.648939
    },
    {
        1.000000, 0.741181, 0.965926
    }
    },
    {
    {
        1.000000, 1.000000, 1.000000
    },
    {
        0.707107, 1.000000, 0.292893
    },
    {
        0.000000, 1.000000, 0.000000
    },
    {
        -0.707107, 1.000000, 0.292893
    },
    {
        -1.000000, 1.000000, 1.000000
    },
    {
        -0.707107, 1.000000, 1.707107
    },
    {
        0.000000, 1.000000, 2.000000
    },
    {
        0.707107, 1.000000, 1.707107
    },
    {
        1.000000, 1.000000, 1.000000
    }
    }
};
float en[7][9][3] = {
    {
    {
        1.000000, 0.000000, 0.000000
    },
    {
        0.707107, 0.707107, 0.000000
    },
    {
        0.000000, 1.000000, 0.000000
    },
    {
        -0.707107, 0.707107, 0.000000
    },
    {
        -1.000000, 0.000000, 0.000000
    },
    {
        -0.707107, -0.707107, 0.000000
    },
    {
        0.000000, -1.000000, 0.000000
    },
    {
        0.707107, -0.707107, 0.000000
    },
    {
        1.000000, 0.000000, 0.000000
    }
    },
    {
    {
        1.000000, 0.000000, 0.000000
    },
    {
        0.707107, 0.683013, -0.183013
    },
    {
        0.000000, 0.965926, -0.258819
    },
    {
        -0.707107, 0.683013, -0.183013
    },
    {
        -1.000000, 0.000000, 0.000000
    },
    {
        -0.707107, -0.683013, 0.183013
    },
    {
        0.000000, -0.965926, 0.258819
    },
    {
        0.707107, -0.683013, 0.183013
    },
    {
        1.000000, 0.000000, 0.000000
    }
    },
    {
    {
        1.000000, 0.000000, 0.000000
    },
    {
        0.707107, 0.612372, -0.353553
    },
    {
        0.000000, 0.866025, -0.500000
    },
    {
        -0.707107, 0.612372, -0.353553
    },
    {
        -1.000000, 0.000000, 0.000000
    },
    {
        -0.707107, -0.612372, 0.353553
    },
    {
        0.000000, -0.866025, 0.500000
    },
    {
        0.707107, -0.612372, 0.353553
    },
    {
        1.000000, 0.000000, 0.000000
    }
    },
    {
    {
        1.000000, 0.000000, 0.000000
    },
    {
        0.000000, 0.707107, -0.707107
    },
    {
        -0.707107, 0.500000, -0.500000
    },
    {
        -1.000000, 0.000000, 0.000000
    },
    {
        -0.707107, -0.500000, 0.500000
    },
    {
        0.000000, -0.707107, 0.707107
    },
    {
        0.707107, -0.500000, 0.500000
    },
    {
        1.000000, 0.000000, 0.000000
    }
    },
    {
    {
        1.000000, 0.000000, 0.000000
    },
    {
        0.707107, 0.353553, -0.612372
    },
    {
        0.000000, 0.500000, -0.866025
    },
    {
        -0.707107, 0.353553, -0.612372
    },
    {
        -1.000000, 0.000000, 0.000000
    },
    {
        -0.707107, -0.353553, 0.612372
    },
    {
        0.000000, -0.500000, 0.866025
    },
    {
        0.707107, -0.353553, 0.612372
    },
    {
        1.000000, 0.000000, 0.000000
    }
    },
    {
    {
        1.000000, 0.000000, 0.000000
    },
    {
        0.707107, 0.183013, -0.683013
    },
    {
        0.000000, 0.258819, -0.965926
    },
    {
        -0.707107, 0.183013, -0.683013
    },
    {

        -1.000000, 0.000000, 0.000000
    },
    {
        -0.707107, -0.183013, 0.683013
    },
    {
        0.000000, -0.258819, 0.965926
    },
    {
        0.707107, -0.183013, 0.683013
    },
    {
        1.000000, 0.000000, 0.000000
    }
    },
    {
    {
        1.000000, 0.000000, 0.000000
    },
    {
        0.707107, 0.000000, -0.707107
    },
    {
        0.000000, 0.000000, -1.000000
    },
    {
        -0.707107, 0.000000, -0.707107
    },
    {
        -1.000000, 0.000000, 0.000000
    },
    {
        -0.707107, 0.000000, 0.707107
    },
    {
        0.000000, 0.000000, 1.000000
    },
    {
        0.707107, 0.000000, 0.707107
    },
    {
        1.000000, 0.000000, 0.000000
    }
    }
};
float tep[7][9][2] = {
    {
    {
        0,     0.0
    },
    {
        0.125, 0.0
    },
    {
        0.25,  0.0
    },
    {
        0.375, 0.0
    },
    {
        0.5,   0.0
    },
    {
        0.625, 0.0
    },
    {
        0.75,  0.0
    },
    {
        0.875, 0.0
    },
    {
        1.0,   0.0
    }
    },
    {
    {
        0,     0.16667
    },
    {
        0.125, 0.16667
    },
    {
        0.25,  0.16667
    },
    {
        0.375, 0.16667
    },
    {
        0.5,   0.16667
    },
    {
        0.625, 0.16667
    },
    {
        0.75,  0.16667
    },
    {
        0.875, 0.16667
    },
    {
        1.0,   0.16667
    }
    },
    {
    {
        0,     0.33333
    },
    {
        0.125, 0.33333
    },
    {
        0.25,  0.33333
    },
    {
        0.375, 0.33333
    },
    {
        0.5,   0.33333
    },
    {
        0.625, 0.33333
    },
    {
        0.75,  0.33333
    },
    {
        0.875, 0.33333
    },
    {
        1.0,   0.33333
    }
    },
    {
    {
        0,     0.5
    },
    {
        0.125, 0.5
    },
    {
        0.25,  0.5
    },
    {
        0.375, 0.5
    },
    {
        0.5,   0.5
    },
    {
        0.625, 0.5
    },
    {
        0.75,  0.5
    },
    {
        0.875, 0.5
    },
    {
        1.0,   0.5
    }
    },
    {
    {
        0,     0.6667
    },
    {
        0.125, 0.6667
    },
    {
        0.25,  0.6667
    },
    {
        0.375, 0.6667
    },
    {
        0.5,   0.6667
    },
    {
        0.625, 0.6667
    },
    {
        0.75,  0.6667
    },
    {
        0.875, 0.6667
    },
    {
        1.0,   0.6667
    }
    },
    {
    {
        0,     0.83333
    },
    {
        0.125, 0.83333
    },
    {
        0.25,  0.83333
    },
    {
        0.375, 0.83333
    },
    {
        0.5,   0.83333
    },
    {
        0.625, 0.83333
    },
    {
        0.75,  0.83333
    },
    {
        0.875, 0.83333
    },
    {
        1.0,   0.83333
    }
    },
    {
    {
        0,     1.0
    },
    {
        0.125, 1.0
    },
    {
        0.25,  1.0
    },
    {
        0.375, 1.0
    },
    {
        0.5,   1.0
    },
    {
        0.625, 1.0
    },
    {
        0.75,  1.0
    },
    {
        0.875, 1.0
    },
    {
        1.0,   1.0
    }
    }
};

static void BendForward(void)
{

    glTranslatef(0.0, 1.0, 0.0);
    glRotatef(90.0, 1, 0, 0);
    glTranslatef(0.0, -1.0, 0.0);
}

static void BendLeft(void)
{

    glRotatef(-90.0, 0, 0, 1);
    glTranslatef(0.0, 1.0, 0.0);
    glRotatef(90.0, 1, 0, 0);
    glTranslatef(0.0, -1.0, 0.0);
}

static void BendRight(void)
{

    glRotatef(90.0, 0, 0, 1);
    glTranslatef(0.0, 1.0, 0.0);
    glRotatef(90.0, 1, 0, 0);
    glTranslatef(0.0, -1.0, 0.0);
}

static void BuildSingleCylinder(void)
{

    glNewList(singleCylinder, GL_COMPILE);

    glBegin(GL_TRIANGLE_STRIP);
       glNormal3fv(scp[0]); glTexCoord2fv(tscp[0]); glVertex3fv(scp[0]);
       glNormal3fv(scp[0]); glTexCoord2fv(tscp[1]); glVertex3fv(scp[1]);
       glNormal3fv(scp[2]); glTexCoord2fv(tscp[2]); glVertex3fv(scp[2]);
       glNormal3fv(scp[2]); glTexCoord2fv(tscp[3]); glVertex3fv(scp[3]);
       glNormal3fv(scp[4]); glTexCoord2fv(tscp[4]); glVertex3fv(scp[4]);
       glNormal3fv(scp[4]); glTexCoord2fv(tscp[5]); glVertex3fv(scp[5]);
       glNormal3fv(scp[6]); glTexCoord2fv(tscp[6]); glVertex3fv(scp[6]);
       glNormal3fv(scp[6]); glTexCoord2fv(tscp[7]); glVertex3fv(scp[7]);
       glNormal3fv(scp[8]); glTexCoord2fv(tscp[8]); glVertex3fv(scp[8]);
       glNormal3fv(scp[8]); glTexCoord2fv(tscp[9]); glVertex3fv(scp[9]);
       glNormal3fv(scp[10]); glTexCoord2fv(tscp[10]); glVertex3fv(scp[10]);
       glNormal3fv(scp[10]); glTexCoord2fv(tscp[11]); glVertex3fv(scp[11]);
       glNormal3fv(scp[12]); glTexCoord2fv(tscp[12]); glVertex3fv(scp[12]);
       glNormal3fv(scp[12]); glTexCoord2fv(tscp[13]); glVertex3fv(scp[13]);
       glNormal3fv(scp[14]); glTexCoord2fv(tscp[14]); glVertex3fv(scp[14]);
       glNormal3fv(scp[14]); glTexCoord2fv(tscp[15]); glVertex3fv(scp[15]);
       glNormal3fv(scp[16]); glTexCoord2fv(tscp[16]); glVertex3fv(scp[16]);
       glNormal3fv(scp[16]); glTexCoord2fv(tscp[17]); glVertex3fv(scp[17]);
    glEnd();

    glEndList();
}

static void BuildDoubleCylinder(void)
{

    glNewList(doubleCylinder, GL_COMPILE);

    glBegin(GL_TRIANGLE_STRIP);
    glNormal3fv(dcp[0]); glTexCoord2fv(tscp[0]); glVertex3fv(dcp[0]);
    glNormal3fv(dcp[0]); glTexCoord2fv(tscp[1]); glVertex3fv(dcp[1]);
    glNormal3fv(dcp[2]); glTexCoord2fv(tscp[2]); glVertex3fv(dcp[2]);
    glNormal3fv(dcp[2]); glTexCoord2fv(tscp[3]); glVertex3fv(dcp[3]);
    glNormal3fv(dcp[4]); glTexCoord2fv(tscp[4]); glVertex3fv(dcp[4]);
    glNormal3fv(dcp[4]); glTexCoord2fv(tscp[5]); glVertex3fv(dcp[5]);
    glNormal3fv(dcp[6]); glTexCoord2fv(tscp[6]); glVertex3fv(dcp[6]);
    glNormal3fv(dcp[6]); glTexCoord2fv(tscp[7]); glVertex3fv(dcp[7]);
    glNormal3fv(dcp[8]); glTexCoord2fv(tscp[8]); glVertex3fv(dcp[8]);
    glNormal3fv(dcp[8]); glTexCoord2fv(tscp[9]); glVertex3fv(dcp[9]);
    glNormal3fv(dcp[10]); glTexCoord2fv(tscp[10]); glVertex3fv(dcp[10]);
    glNormal3fv(dcp[10]); glTexCoord2fv(tscp[11]); glVertex3fv(dcp[11]);
    glNormal3fv(dcp[12]); glTexCoord2fv(tscp[12]); glVertex3fv(dcp[12]);
    glNormal3fv(dcp[12]); glTexCoord2fv(tscp[13]); glVertex3fv(dcp[13]);
    glNormal3fv(dcp[14]); glTexCoord2fv(tscp[14]); glVertex3fv(dcp[14]);
    glNormal3fv(dcp[14]); glTexCoord2fv(tscp[15]); glVertex3fv(dcp[15]);
    glNormal3fv(dcp[16]); glTexCoord2fv(tscp[16]); glVertex3fv(dcp[16]);
    glNormal3fv(dcp[16]); glTexCoord2fv(tscp[17]); glVertex3fv(dcp[17]);
    glEnd();

    glEndList();
}

static void BuildElbow(void)
{

    glNewList(elbow, GL_COMPILE);

    glBegin(GL_TRIANGLE_STRIP);
    glNormal3fv(en[0][0]); glTexCoord2fv(tep[0][0]); glVertex3fv(ep[0][0]);
    glNormal3fv(en[1][0]); glTexCoord2fv(tep[1][0]); glVertex3fv(ep[1][0]);
    glNormal3fv(en[0][1]); glTexCoord2fv(tep[0][1]); glVertex3fv(ep[0][1]);
    glNormal3fv(en[1][1]); glTexCoord2fv(tep[1][1]); glVertex3fv(ep[1][1]);
    glNormal3fv(en[0][2]); glTexCoord2fv(tep[0][2]); glVertex3fv(ep[0][2]);
    glNormal3fv(en[1][2]); glTexCoord2fv(tep[1][2]); glVertex3fv(ep[1][2]);
    glNormal3fv(en[0][3]); glTexCoord2fv(tep[0][3]); glVertex3fv(ep[0][3]);
    glNormal3fv(en[1][3]); glTexCoord2fv(tep[1][3]); glVertex3fv(ep[1][3]);
    glNormal3fv(en[0][4]); glTexCoord2fv(tep[0][4]); glVertex3fv(ep[0][4]);
    glNormal3fv(en[1][4]); glTexCoord2fv(tep[1][4]); glVertex3fv(ep[1][4]);
    glNormal3fv(en[0][5]); glTexCoord2fv(tep[0][5]); glVertex3fv(ep[0][5]);
    glNormal3fv(en[1][5]); glTexCoord2fv(tep[1][5]); glVertex3fv(ep[1][5]);
    glNormal3fv(en[0][6]); glTexCoord2fv(tep[0][6]); glVertex3fv(ep[0][6]);
    glNormal3fv(en[1][6]); glTexCoord2fv(tep[1][6]); glVertex3fv(ep[1][6]);
    glNormal3fv(en[0][7]); glTexCoord2fv(tep[0][7]); glVertex3fv(ep[0][7]);
    glNormal3fv(en[1][7]); glTexCoord2fv(tep[1][7]); glVertex3fv(ep[1][7]);
    glNormal3fv(en[0][8]); glTexCoord2fv(tep[0][8]); glVertex3fv(ep[0][8]);
    glNormal3fv(en[1][8]); glTexCoord2fv(tep[1][8]); glVertex3fv(ep[1][8]);
    glEnd();
    glBegin(GL_TRIANGLE_STRIP);
    glNormal3fv(en[1][0]); glTexCoord2fv(tep[1][0]); glVertex3fv(ep[1][0]);
    glNormal3fv(en[2][0]); glTexCoord2fv(tep[2][0]); glVertex3fv(ep[2][0]);
    glNormal3fv(en[1][1]); glTexCoord2fv(tep[1][1]); glVertex3fv(ep[1][1]);
    glNormal3fv(en[2][1]); glTexCoord2fv(tep[2][1]); glVertex3fv(ep[2][1]);
    glNormal3fv(en[1][2]); glTexCoord2fv(tep[1][2]); glVertex3fv(ep[1][2]);
    glNormal3fv(en[2][2]); glTexCoord2fv(tep[2][2]); glVertex3fv(ep[2][2]);
    glNormal3fv(en[1][3]); glTexCoord2fv(tep[1][3]); glVertex3fv(ep[1][3]);
    glNormal3fv(en[2][3]); glTexCoord2fv(tep[2][3]); glVertex3fv(ep[2][3]);
    glNormal3fv(en[1][4]); glTexCoord2fv(tep[1][4]); glVertex3fv(ep[1][4]);
    glNormal3fv(en[2][4]); glTexCoord2fv(tep[2][4]); glVertex3fv(ep[2][4]);
    glNormal3fv(en[1][5]); glTexCoord2fv(tep[1][5]); glVertex3fv(ep[1][5]);
    glNormal3fv(en[2][5]); glTexCoord2fv(tep[2][5]); glVertex3fv(ep[2][5]);
    glNormal3fv(en[1][6]); glTexCoord2fv(tep[1][6]); glVertex3fv(ep[1][6]);
    glNormal3fv(en[2][6]); glTexCoord2fv(tep[2][6]); glVertex3fv(ep[2][6]);
    glNormal3fv(en[1][7]); glTexCoord2fv(tep[1][7]); glVertex3fv(ep[1][7]);
    glNormal3fv(en[2][7]); glTexCoord2fv(tep[2][7]); glVertex3fv(ep[2][7]);
    glNormal3fv(en[1][8]); glTexCoord2fv(tep[1][8]); glVertex3fv(ep[1][8]);
    glNormal3fv(en[2][8]); glTexCoord2fv(tep[2][8]); glVertex3fv(ep[2][8]);
    glEnd();
    glBegin(GL_TRIANGLE_STRIP);
    glNormal3fv(en[2][0]); glTexCoord2fv(tep[2][0]); glVertex3fv(ep[2][0]);
    glNormal3fv(en[3][0]); glTexCoord2fv(tep[3][0]); glVertex3fv(ep[3][0]);
    glNormal3fv(en[2][1]); glTexCoord2fv(tep[2][1]); glVertex3fv(ep[2][1]);
    glNormal3fv(en[3][1]); glTexCoord2fv(tep[3][1]); glVertex3fv(ep[3][1]);
    glNormal3fv(en[2][2]); glTexCoord2fv(tep[2][2]); glVertex3fv(ep[2][2]);
    glNormal3fv(en[3][2]); glTexCoord2fv(tep[3][2]); glVertex3fv(ep[3][2]);
    glNormal3fv(en[2][3]); glTexCoord2fv(tep[2][3]); glVertex3fv(ep[2][3]);
    glNormal3fv(en[3][3]); glTexCoord2fv(tep[3][3]); glVertex3fv(ep[3][3]);
    glNormal3fv(en[2][4]); glTexCoord2fv(tep[2][4]); glVertex3fv(ep[2][4]);
    glNormal3fv(en[3][4]); glTexCoord2fv(tep[3][4]); glVertex3fv(ep[3][4]);
    glNormal3fv(en[2][5]); glTexCoord2fv(tep[2][5]); glVertex3fv(ep[2][5]);
    glNormal3fv(en[3][5]); glTexCoord2fv(tep[3][5]); glVertex3fv(ep[3][5]);
    glNormal3fv(en[2][6]); glTexCoord2fv(tep[2][6]); glVertex3fv(ep[2][6]);
    glNormal3fv(en[3][6]); glTexCoord2fv(tep[3][6]); glVertex3fv(ep[3][6]);
    glNormal3fv(en[2][7]); glTexCoord2fv(tep[2][7]); glVertex3fv(ep[2][7]);
    glNormal3fv(en[3][7]); glTexCoord2fv(tep[3][7]); glVertex3fv(ep[3][7]);
    glNormal3fv(en[2][8]); glTexCoord2fv(tep[2][8]); glVertex3fv(ep[2][8]);
    glNormal3fv(en[3][8]); glTexCoord2fv(tep[3][8]); glVertex3fv(ep[3][8]);
    glEnd();
    glBegin(GL_TRIANGLE_STRIP);
    glNormal3fv(en[3][0]); glTexCoord2fv(tep[3][0]); glVertex3fv(ep[3][0]);
    glNormal3fv(en[4][0]); glTexCoord2fv(tep[4][0]); glVertex3fv(ep[4][0]);
    glNormal3fv(en[3][1]); glTexCoord2fv(tep[3][1]); glVertex3fv(ep[3][1]);
    glNormal3fv(en[4][1]); glTexCoord2fv(tep[4][1]); glVertex3fv(ep[4][1]);
    glNormal3fv(en[3][2]); glTexCoord2fv(tep[3][2]); glVertex3fv(ep[3][2]);
    glNormal3fv(en[4][2]); glTexCoord2fv(tep[4][2]); glVertex3fv(ep[4][2]);
    glNormal3fv(en[3][3]); glTexCoord2fv(tep[3][3]); glVertex3fv(ep[3][3]);
    glNormal3fv(en[4][3]); glTexCoord2fv(tep[4][3]); glVertex3fv(ep[4][3]);
    glNormal3fv(en[3][4]); glTexCoord2fv(tep[3][4]); glVertex3fv(ep[3][4]);
    glNormal3fv(en[4][4]); glTexCoord2fv(tep[4][4]); glVertex3fv(ep[4][4]);
    glNormal3fv(en[3][5]); glTexCoord2fv(tep[3][5]); glVertex3fv(ep[3][5]);
    glNormal3fv(en[4][5]); glTexCoord2fv(tep[4][5]); glVertex3fv(ep[4][5]);
    glNormal3fv(en[3][6]); glTexCoord2fv(tep[3][6]); glVertex3fv(ep[3][6]);
    glNormal3fv(en[4][6]); glTexCoord2fv(tep[4][6]); glVertex3fv(ep[4][6]);
    glNormal3fv(en[3][7]); glTexCoord2fv(tep[3][7]); glVertex3fv(ep[3][7]);
    glNormal3fv(en[4][7]); glTexCoord2fv(tep[4][7]); glVertex3fv(ep[4][7]);
    glNormal3fv(en[3][8]); glTexCoord2fv(tep[3][8]); glVertex3fv(ep[3][8]);
    glNormal3fv(en[4][8]); glTexCoord2fv(tep[4][8]); glVertex3fv(ep[4][8]);
    glEnd();
    glBegin(GL_TRIANGLE_STRIP);
    glNormal3fv(en[4][0]); glTexCoord2fv(tep[4][0]); glVertex3fv(ep[4][0]);
    glNormal3fv(en[5][0]); glTexCoord2fv(tep[5][0]); glVertex3fv(ep[5][0]);
    glNormal3fv(en[4][1]); glTexCoord2fv(tep[4][1]); glVertex3fv(ep[4][1]);
    glNormal3fv(en[5][1]); glTexCoord2fv(tep[5][1]); glVertex3fv(ep[5][1]);
    glNormal3fv(en[4][2]); glTexCoord2fv(tep[4][2]); glVertex3fv(ep[4][2]);
    glNormal3fv(en[5][2]); glTexCoord2fv(tep[5][2]); glVertex3fv(ep[5][2]);
    glNormal3fv(en[4][3]); glTexCoord2fv(tep[4][3]); glVertex3fv(ep[4][3]);
    glNormal3fv(en[5][3]); glTexCoord2fv(tep[5][3]); glVertex3fv(ep[5][3]);
    glNormal3fv(en[4][4]); glTexCoord2fv(tep[4][4]); glVertex3fv(ep[4][4]);
    glNormal3fv(en[5][4]); glTexCoord2fv(tep[5][4]); glVertex3fv(ep[5][4]);
    glNormal3fv(en[4][5]); glTexCoord2fv(tep[4][5]); glVertex3fv(ep[4][5]);
    glNormal3fv(en[5][5]); glTexCoord2fv(tep[5][5]); glVertex3fv(ep[5][5]);
    glNormal3fv(en[4][6]); glTexCoord2fv(tep[4][6]); glVertex3fv(ep[4][6]);
    glNormal3fv(en[5][6]); glTexCoord2fv(tep[5][6]); glVertex3fv(ep[5][6]);
    glNormal3fv(en[4][7]); glTexCoord2fv(tep[4][7]); glVertex3fv(ep[4][7]);
    glNormal3fv(en[5][7]); glTexCoord2fv(tep[5][7]); glVertex3fv(ep[5][7]);
    glNormal3fv(en[4][8]); glTexCoord2fv(tep[4][8]); glVertex3fv(ep[4][8]);
    glNormal3fv(en[5][8]); glTexCoord2fv(tep[5][8]); glVertex3fv(ep[5][8]);
    glEnd();
    glBegin(GL_TRIANGLE_STRIP);
    glNormal3fv(en[5][0]); glTexCoord2fv(tep[5][0]); glVertex3fv(ep[5][0]);
    glNormal3fv(en[6][0]); glTexCoord2fv(tep[6][0]); glVertex3fv(ep[6][0]);
    glNormal3fv(en[5][1]); glTexCoord2fv(tep[5][1]); glVertex3fv(ep[5][1]);
    glNormal3fv(en[6][1]); glTexCoord2fv(tep[6][1]); glVertex3fv(ep[6][1]);
    glNormal3fv(en[5][2]); glTexCoord2fv(tep[5][2]); glVertex3fv(ep[5][2]);
    glNormal3fv(en[6][2]); glTexCoord2fv(tep[6][2]); glVertex3fv(ep[6][2]);
    glNormal3fv(en[5][3]); glTexCoord2fv(tep[5][3]); glVertex3fv(ep[5][3]);
    glNormal3fv(en[6][3]); glTexCoord2fv(tep[6][3]); glVertex3fv(ep[6][3]);
    glNormal3fv(en[5][4]); glTexCoord2fv(tep[5][4]); glVertex3fv(ep[5][4]);
    glNormal3fv(en[6][4]); glTexCoord2fv(tep[6][4]); glVertex3fv(ep[6][4]);
    glNormal3fv(en[5][5]); glTexCoord2fv(tep[5][5]); glVertex3fv(ep[5][5]);
    glNormal3fv(en[6][5]); glTexCoord2fv(tep[6][5]); glVertex3fv(ep[6][5]);
    glNormal3fv(en[5][6]); glTexCoord2fv(tep[5][6]); glVertex3fv(ep[5][6]);
    glNormal3fv(en[6][6]); glTexCoord2fv(tep[6][6]); glVertex3fv(ep[6][6]);
    glNormal3fv(en[5][7]); glTexCoord2fv(tep[5][7]); glVertex3fv(ep[5][7]);
    glNormal3fv(en[6][7]); glTexCoord2fv(tep[6][7]); glVertex3fv(ep[6][7]);
    glNormal3fv(en[5][8]); glTexCoord2fv(tep[5][8]); glVertex3fv(ep[5][8]);
    glNormal3fv(en[6][8]); glTexCoord2fv(tep[6][8]); glVertex3fv(ep[6][8]);
    glEnd();

    glEndList();
}

static void BuildLogo(void)
{

    glNewList(logo, GL_COMPILE);

    glTranslatef(5.5, -3.5, 4.5);
    glTranslatef(0.0, 0.0, -7.0);
    glCallList(doubleCylinder);
    BendForward();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -7.0);
    glCallList(doubleCylinder);
    BendForward();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -5.0);
    glCallList(singleCylinder);
    BendRight();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -7.0);
    glCallList(doubleCylinder);
    BendForward();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -7.0);
    glCallList(doubleCylinder);
    BendForward();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -5.0);
    glCallList(singleCylinder);
    BendLeft();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -7.0);
    glCallList(doubleCylinder);
    BendForward();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -7.0);
    glCallList(doubleCylinder);
    BendForward();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -5.0);
    glCallList(singleCylinder);
    BendRight();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -7.0);
    glCallList(doubleCylinder);
    BendForward();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -7.0);
    glCallList(doubleCylinder);
    BendForward();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -5.0);
    glCallList(singleCylinder);
    BendLeft();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -7.0);
    glCallList(doubleCylinder);
    BendForward();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -7.0);
    glCallList(doubleCylinder);
    BendForward();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -5.0);
    glCallList(singleCylinder);
    BendRight();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -7.0);
    glCallList(doubleCylinder);
    BendForward();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -7.0);
    glCallList(doubleCylinder);
    BendForward();
    glCallList(elbow);
    glTranslatef(0.0, 0.0, -5.0);
    glCallList(singleCylinder);
    BendLeft();
    glCallList(elbow);

    glEndList();
}

static void BuildLists(void)
{

    singleCylinder = glGenLists(1);
    doubleCylinder = glGenLists(1);
    elbow = glGenLists(1);
    logo = glGenLists(1);

    BuildSingleCylinder();
    BuildDoubleCylinder();
    BuildElbow();
    BuildLogo();
}

static void Init(void)
{
    static float ambient[] = {0.1, 0.1, 0.1, 1.0};
    static float diffuse[] = {0.5, 1.0, 1.0, 1.0};
    static float position[] = {90.0, 90.0, 150.0, 0.0};
    static float front_mat_shininess[] = {30.0};
    static float front_mat_specular[] = {0.2, 0.2, 0.2, 1.0};
    static float front_mat_diffuse[] = {0.5, 0.28, 0.38, 1.0};
    static float back_mat_shininess[] = {50.0};
    static float back_mat_specular[] = {0.5, 0.5, 0.2, 1.0};
    static float back_mat_diffuse[] = {1.0, 1.0, 0.2, 1.0};
    static float lmodel_ambient[] = {1.0, 1.0, 1.0, 1.0};
    static float lmodel_twoside[] = {GL_TRUE};

    glClearColor(0.0, 0.0, 0.0, 0.0);

    glFrontFace(GL_CW);

    glEnable(GL_DEPTH_TEST);

    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
    glLightModelfv(GL_LIGHT_MODEL_TWO_SIDE, lmodel_twoside);
    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
    glLightfv(GL_LIGHT0, GL_POSITION, position);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    glMaterialfv(GL_FRONT, GL_SHININESS, front_mat_shininess);
    glMaterialfv(GL_FRONT, GL_SPECULAR, front_mat_specular);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, front_mat_diffuse);
    glMaterialfv(GL_BACK, GL_SHININESS, back_mat_shininess);
    glMaterialfv(GL_BACK, GL_SPECULAR, back_mat_specular);
    glMaterialfv(GL_BACK, GL_DIFFUSE, back_mat_diffuse);

    glEnable(GL_CLIP_PLANE0);

    glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, decal);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, repeat);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, repeat);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, nearest);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, nearest);
    glTexImage2D(GL_TEXTURE_2D, 0, 3, CHECKIMAGEWIDTH, CHECKIMAGEHEIGHT, 0,
         GL_RGB, GL_UNSIGNED_BYTE, (GLvoid *)checkImage);
    glEnable(GL_TEXTURE_2D);

    glCullFace(GL_BACK);
    glEnable(GL_CULL_FACE);

    BuildLists();

    dithering = GL_TRUE;
    shade = GL_TRUE;
    doStipple = GL_FALSE;
    polyMode = GL_BACK;
}

static void Reshape(int width, int height)
{

    glViewport(0, 0, width, height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(90, 1.0, 1.0, 200.0);
    glMatrixMode(GL_MODELVIEW);
}

static void Key(unsigned char key, int x, int y)
{

    switch (key) {
      case 'z':
    zTranslation -= 1.0;
    glutPostRedisplay();
    break;
      case 'Z':
    zTranslation += 1.0;
    glutPostRedisplay();
    break;
      case '1':
    glPolygonMode(polyMode, GL_POINT);
    glutPostRedisplay();
    break;
      case '2':
    glPolygonMode(polyMode, GL_LINE);
    glutPostRedisplay();
    break;
      case '3':
    glPolygonMode(polyMode, GL_FILL);
    glutPostRedisplay();
    break;
      case '4':
    switch (polyMode) {
      case GL_BACK:
        polyMode = GL_FRONT;
        break;
      case GL_FRONT:
        polyMode = GL_FRONT_AND_BACK;
        break;
      case GL_FRONT_AND_BACK:
        polyMode = GL_BACK;
        break;
    }
    glutPostRedisplay();
    break;
      case '5':
    glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
    glutPostRedisplay();
    break;
      case '6':
    glEnable(GL_POLYGON_SMOOTH);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    glEnable(GL_BLEND);
    glDisable(GL_DEPTH_TEST);
    glutPostRedisplay();
    break;
      case '7':
    glDisable(GL_POLYGON_SMOOTH);
    glBlendFunc(GL_ONE, GL_ZERO);
    glDisable(GL_BLEND);
    glEnable(GL_DEPTH_TEST);
    glutPostRedisplay();
    break;
      case '8':
    dithering = !dithering;
    (dithering) ? glEnable(GL_DITHER) : glDisable(GL_DITHER);
    glutPostRedisplay();
    break;
      case '9':
    doStipple = !doStipple;
    if (doStipple) {
        glPolygonStipple(stipple);
        glEnable(GL_POLYGON_STIPPLE);
    } else {
        glDisable(GL_POLYGON_STIPPLE);
    }
    glutPostRedisplay();
    break;
      case '0':
    shade = !shade;
    (shade) ? glShadeModel(GL_SMOOTH) : glShadeModel(GL_FLAT);
    glutPostRedisplay();
    break;
      case 'q':
    glDisable(GL_CULL_FACE);
    glutPostRedisplay();
    break;
      case 'w':
    glEnable(GL_CULL_FACE);
    glCullFace(GL_FRONT);
    glutPostRedisplay();
    break;
      case 'e':
    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK);
    glutPostRedisplay();
    break;
      case 'r':
    glFrontFace(GL_CW);
    glutPostRedisplay();
    break;
      case 't':
    glFrontFace(GL_CCW);
    glutPostRedisplay();
    break;
      case 'y':
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glPixelStorei(GL_UNPACK_LSB_FIRST, 0);
    glPolygonStipple(stipple);
    glutPostRedisplay();
    break;
      case 'u':
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glPixelStorei(GL_UNPACK_LSB_FIRST, 1);
    glPolygonStipple(stipple);
    glutPostRedisplay();
    break;
      case 'a':
    glEnable(GL_TEXTURE_2D);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, repeat);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, repeat);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, nearest);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, nearest);
    glTexImage2D(GL_TEXTURE_2D, 0, 4, BRICKIMAGEWIDTH,
             BRICKIMAGEHEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE,
             (GLvoid *)brickImage);
    glutPostRedisplay();
    break;
      case 's':
    glEnable(GL_TEXTURE_2D);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, repeat);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, repeat);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, nearest);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, nearest);
    glTexImage2D(GL_TEXTURE_2D, 0, 3, CHECKIMAGEWIDTH,
             CHECKIMAGEHEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE,
             (GLvoid *)checkImage);
    glutPostRedisplay();
    break;
      case 'd':
    glDisable(GL_TEXTURE_2D);
    glutPostRedisplay();
    break;
      case 'f':
    glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, decal);
    glutPostRedisplay();
    break;
      case 'g':
    glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, modulate);
    glutPostRedisplay();
    break;
      case 27:
    exit(0);
    }
}

static void SpecialKey(int key, int x, int y)
{

    switch (key) {
      case GLUT_KEY_LEFT:
    yRotation += 0.5;
    glutPostRedisplay();
    break;
      case GLUT_KEY_RIGHT:
    yRotation -= 0.5;
    glutPostRedisplay();
    break;
      case GLUT_KEY_UP:
    plane[3] += 2.0;
    glutPostRedisplay();
    break;
      case GLUT_KEY_DOWN:
    plane[3] -= 2.0;
    glutPostRedisplay();
    break;
    }
}

static void Draw(void)
{

    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    glPushMatrix();

    glTranslatef(0, 0, zTranslation);
    glRotatef(30.0, 1, 0, 0);
    glRotatef(yRotation, 0, 1, 0);
    glClipPlane(GL_CLIP_PLANE0, plane);
    glCallList(logo);

    glPopMatrix();

    if (doubleBuffer) {
    glutSwapBuffers();
    } else {
    glFlush();
    }
}

static void Args(int argc, char **argv)
{
    GLint i;

    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    }
    }
}

int main(int argc, char **argv)
{
    GLenum type;

    glutInit(&argc, argv);
    Args(argc, argv);

    type = GLUT_RGB | GLUT_DEPTH;
    type |= (doubleBuffer) ? GLUT_DOUBLE : GLUT_SINGLE;
    glutInitDisplayMode(type);
    glutInitWindowSize(300, 300);
    glutCreateWindow("Logo Test");

    Init();

    glutReshapeFunc(Reshape);
    glutKeyboardFunc(Key);
    glutSpecialFunc(SpecialKey);
    glutDisplayFunc(Draw);
    glutMainLoop();
}


nurb.C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <GL/glut.h>

#ifndef WIN32
#define CALLBACK
#endif

#define INREAL float

#define S_NUMPOINTS 13
#define S_ORDER     3
#define S_NUMKNOTS  (S_NUMPOINTS + S_ORDER)
#define T_NUMPOINTS 3
#define T_ORDER     3
#define T_NUMKNOTS  (T_NUMPOINTS + T_ORDER)
#define SQRT_TWO    1.41421356237309504880

typedef INREAL Point[4];

GLenum doubleBuffer;

GLenum expectedError;
GLint rotX = 40, rotY = 40;
INREAL sknots[S_NUMKNOTS] = {
    -1.0, -1.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0,
    4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 9.0, 9.0
};
INREAL glutnots[T_NUMKNOTS] = {
    1.0, 1.0, 1.0, 2.0, 2.0, 2.0
};
Point ctlpoints[S_NUMPOINTS][T_NUMPOINTS] = {
    {
    {
        4.0, 2.0, 2.0, 1.0
    },
    {
        4.0, 1.6, 2.5, 1.0
    },
    {
        4.0, 2.0, 3.0, 1.0
    }
    },
    {
    {
        5.0, 4.0, 2.0, 1.0
    },
    {
        5.0, 4.0, 2.5, 1.0
    },
    {
        5.0, 4.0, 3.0, 1.0
    }
    },
    {
    {
        6.0, 5.0, 2.0, 1.0
    },
    {
        6.0, 5.0, 2.5, 1.0
    },
    {
        6.0, 5.0, 3.0, 1.0
    }
    },
    {
    {
        SQRT_TWO*6.0, SQRT_TWO*6.0, SQRT_TWO*2.0, SQRT_TWO
    },
    {
        SQRT_TWO*6.0, SQRT_TWO*6.0, SQRT_TWO*2.5, SQRT_TWO
    },
    {
        SQRT_TWO*6.0, SQRT_TWO*6.0, SQRT_TWO*3.0, SQRT_TWO
    }
    },
    {
    {
        5.2, 6.7, 2.0, 1.0
    },
    {
        5.2, 6.7, 2.5, 1.0
    },
    {
        5.2, 6.7, 3.0, 1.0
    }
    },
    {
    {
        SQRT_TWO*4.0, SQRT_TWO*6.0, SQRT_TWO*2.0, SQRT_TWO
    },
    {
        SQRT_TWO*4.0, SQRT_TWO*6.0, SQRT_TWO*2.5, SQRT_TWO
    },
    {
        SQRT_TWO*4.0, SQRT_TWO*6.0, SQRT_TWO*3.0, SQRT_TWO
    }
    },
    {
    {
        4.0, 5.2, 2.0, 1.0
    },
    {
        4.0, 4.6, 2.5, 1.0
    },
    {
        4.0, 5.2, 3.0, 1.0
    }
    },
    {
    {
        SQRT_TWO*4.0, SQRT_TWO*6.0, SQRT_TWO*2.0, SQRT_TWO
    },
    {
        SQRT_TWO*4.0, SQRT_TWO*6.0, SQRT_TWO*2.5, SQRT_TWO
    },
    {
        SQRT_TWO*4.0, SQRT_TWO*6.0, SQRT_TWO*3.0, SQRT_TWO
    }
    },
    {
    {
        2.8, 6.7, 2.0, 1.0
    },
    {
        2.8, 6.7, 2.5, 1.0
    },
    {
        2.8, 6.7, 3.0, 1.0
    }
    },
    {
    {
        SQRT_TWO*2.0, SQRT_TWO*6.0, SQRT_TWO*2.0, SQRT_TWO
    },
    {
        SQRT_TWO*2.0, SQRT_TWO*6.0, SQRT_TWO*2.5, SQRT_TWO
    },
    {
        SQRT_TWO*2.0, SQRT_TWO*6.0, SQRT_TWO*3.0, SQRT_TWO
    }
    },
    {
    {
        2.0, 5.0, 2.0, 1.0
    },
    {
        2.0, 5.0, 2.5, 1.0
    },
    {
        2.0, 5.0, 3.0, 1.0
    }
    },
    {
    {
        3.0, 4.0, 2.0, 1.0
    },
    {
        3.0, 4.0, 2.5, 1.0
    },
    {
        3.0, 4.0, 3.0, 1.0
    }
    },
    {
    {
        4.0, 2.0, 2.0, 1.0
    },
    {
        4.0, 1.6, 2.5, 1.0
    },
    {
        4.0, 2.0, 3.0, 1.0
    }
    }
};
GLUnurbsObj *theNurbs;

static void CALLBACK ErrorCallback(GLenum which)
{

    if (which != expectedError) {
    fprintf(stderr, "Unexpected error occured (%d):\n", which);
    fprintf(stderr, "    %s\n", gluErrorString(which));
    }
}

static void Init(void)
{

    theNurbs = gluNewNurbsRenderer();
    gluNurbsCallback(theNurbs, GLU_ERROR, ErrorCallback);

    gluNurbsProperty(theNurbs, GLU_SAMPLING_TOLERANCE, 15.0);
    gluNurbsProperty(theNurbs, GLU_DISPLAY_MODE, GLU_OUTLINE_PATCH);

    expectedError = GLU_INVALID_ENUM;
    gluNurbsProperty(theNurbs, ~0, 15.0);
    expectedError = GLU_NURBS_ERROR13;
    gluEndSurface(theNurbs);
    expectedError = 0;

    glColor3f(1.0, 1.0, 1.0);
}

static void Reshape(int width, int height)
{

    glViewport(0, 0, width, height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum(-2.0, 2.0, -2.0, 2.0, 0.8, 10.0);
    gluLookAt(7.0, 4.5, 4.0, 4.5, 4.5, 2.5, 6.0, -3.0, 2.0);
    glMatrixMode(GL_MODELVIEW);
}

static void Key(unsigned char key, int x, int y)
{

    switch (key) {
      case 27:
    exit(0);
    }
}

static void SpecialKey(int key, int x, int y)
{

    switch (key) {
      case GLUT_KEY_DOWN:
    rotX -= 5;
    glutPostRedisplay();
    break;
      case GLUT_KEY_UP:
    rotX += 5;
    glutPostRedisplay();
    break;
      case GLUT_KEY_LEFT:
    rotY -= 5;
    glutPostRedisplay();
    break;
      case GLUT_KEY_RIGHT:
    rotY += 5;
    glutPostRedisplay();
    break;
    }
}

static void Draw(void)
{

    glClear(GL_COLOR_BUFFER_BIT);

    glPushMatrix();

    glTranslatef(4.0, 4.5, 2.5);
    glRotatef(rotY, 1, 0, 0);
    glRotatef(rotX, 0, 1, 0);
    glTranslatef(-4.0, -4.5, -2.5);

    gluBeginSurface(theNurbs);
    gluNurbsSurface(theNurbs, S_NUMKNOTS, sknots, T_NUMKNOTS, glutnots,
            4*T_NUMPOINTS, 4, &ctlpoints[0][0][0], S_ORDER,
            T_ORDER, GL_MAP2_VERTEX_4);
    gluEndSurface(theNurbs);

    glPopMatrix();

    if (doubleBuffer) {
    glutSwapBuffers();
    } else {
    glFlush();
    }
}

static void Args(int argc, char **argv)
{
    GLint i;

    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    }
    }
}

int main(int argc, char **argv)
{
    GLenum type;

    Args(argc, argv);

    type = GLUT_RGB;
    type |= (doubleBuffer) ? GLUT_DOUBLE : GLUT_SINGLE;
    glutInitDisplayMode(type);
    glutInitWindowSize(300, 300);
    glutCreateWindow("NURBS Test");

    Init();

    glutReshapeFunc(Reshape);
    glutKeyboardFunc(Key);
    glutSpecialFunc(SpecialKey);
    glutDisplayFunc(Draw);
    glutMainLoop();
}


packedpix.C

/*
** packedpix.c: Uses packed pixel types for DrawPixels, ReadPixels,
**              TexImage2D, and GetTexImage.
**
** Use -h option to display a list of command line options. Use Key h
** in the created window for key options.
*/

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <math.h>
#include <stdlib.h>
#include <assert.h>
#include <GL/glut.h>
#include "rgb.h"

/*----------------------------------------------------------------------*/

#define NUM_IMAGES 4
char *rgbFile = NULL;

GLboolean doubleBuffer = GL_FALSE;
GLboolean queryExts = GL_TRUE;

int winWidth = 300;
int winHeight = 300;

RGBImageRec *fileImage;

GLboolean isUseDisplayLists= GL_FALSE;

/*----------------------------------------------------------------------*/

/*
** This code is used to create an image of specified type and format
** for use with DrawPixels and TexImage.  Instead of writing
** code for any possible source image, a fixed type/format image is
** generated and then used to produce the requested type/format images.
** The fixed image is float/rgba.
*/

typedef struct _ConvertImageRec {
    GLfloat *fltImage;		/* original float RGBA image */
    void *newImage;             /* converted image */
    GLint imageWidth;		/* number of active pixels in a row */
    GLint imageHeight;		/* number of pixels in a column */
    GLint imageDataWidth;	/* original number of pixels in a row */
    GLint imageDataHeight;	/* original number of pixels in a column */
    GLenum type;		/* type of converted image */
    GLenum format;		/* format of converted image */
    GLint imageSize;		/* number of bytes in the converted image */
    int components;		/* number of components per pixel */
    int bytesPerComp;		/* number of bytes per component */
} ConvertImageRec;

/*
** Creates the float RGBA image from the unsigned byte RGB one that typically
** comes from a file.
*/
static void CreateFloatImage(RGBImageRec *imageIn, ConvertImageRec *imageOut)
{
    GLubyte *rawData;
    GLint x, y;
    GLfloat *fltImage, *ptr;
    GLfloat alpha;
    GLint imageWidth, imageHeight;
    int imageSize;

    imageWidth = imageIn->sizeX;
    imageHeight = imageIn->sizeY;
    rawData = imageIn->data;

    imageSize = imageWidth * imageHeight * sizeof(GLfloat) * 4;
    fltImage = (float *)malloc(imageSize);
    if (NULL == fltImage) {
    fprintf(stderr, "Out of memory!\n");
    exit(1);
    }

    ptr = fltImage;
    for (y = 0; y < imageHeight; y++) {
        for (x = 0; x < imageWidth; x++) {
            *ptr++ = *rawData++ / 255.0;
        /* make an alpha value by copying the green channel */
            alpha = *ptr++ = *rawData++ / 255.0;
            *ptr++ = *rawData++ / 255.0;
            *ptr++ = alpha;
        }
    }
    imageOut->fltImage = fltImage;
    imageOut->imageWidth = imageWidth;
    imageOut->imageHeight = imageHeight;
    imageOut->imageDataWidth = imageOut->imageWidth;
    imageOut->imageDataHeight = imageOut->imageHeight;
}

/*
** Converts the float RGBA image pointed to by imageData->fltImage
** into an image of imageData->type and imageData->format pointed
** to by imageData->newImage.
*/
static void ConvertImage(ConvertImageRec *imageData)
{
    GLint imageWidth = imageData->imageDataWidth;
    GLint imageHeight = imageData->imageDataHeight;
    GLenum type = imageData->type;
    GLenum format = imageData->format;
    void *newImage;
    GLfloat *myImage = imageData->fltImage;
    int i, j, k;
    int imageSize;
    int components = imageData->components;

    /* Free the old image if there is one. */
    if (NULL != imageData->newImage) {
    free(imageData->newImage);
    }

    imageSize = imageWidth*imageHeight * components*imageData->bytesPerComp;

    newImage = (void *)malloc(imageSize);
    if (NULL == newImage) {
    fprintf(stderr, "Out of memory!\n");
    exit(1);
    }

    switch (type) {
    case GL_BITMAP:
    case GL_UNSIGNED_BYTE:
    case GL_BYTE:
    case GL_UNSIGNED_SHORT:
    case GL_SHORT:
    case GL_INT:
    case GL_UNSIGNED_INT:
        break;

    case GL_FLOAT:
        {
        GLfloat *data = newImage;

        for (i = 0; i < imageSize/4; i++) {
            *data++ = *myImage++;
        }
        }
        break;

    case GL_UNSIGNED_BYTE_3_3_2_EXT:
        {
        GLubyte *data = newImage;
        GLfloat *invalue;

        for (i = 0; i < imageHeight; i++) {
            for (j = 0; j < imageWidth; j++) {

            /* first bitfield */
            invalue = myImage++;
            *data = ((GLubyte)(*invalue * 7) << 5) & 0xE0;

            /* second bitfield */
            invalue = myImage++;
            *data |= ((GLubyte)(*invalue * 7) << 2) & 0x1C;

            /* third bitfield */
            invalue = myImage++;
            *data |= (GLubyte)(*invalue * 3) & 0x03;

            data++;
            myImage += 1; /* skip the final component */
            }
        }
        }
        break;

    case GL_UNSIGNED_SHORT_4_4_4_4_EXT:
        {
        GLushort *data = newImage;
        GLfloat *invalue;

        for (i = 0; i < imageHeight; i++) {
            for (j = 0; j < imageWidth; j++) {

            /* first bitfield */
            invalue = myImage++;
            *data = ((GLushort)(*invalue * 15) << 12) & 0xF000;

            /* second bitfield */
            invalue = myImage++;
            *data |= ((GLushort)(*invalue * 15) << 8) & 0x0F00;

            /* third bitfield */
            invalue = myImage++;
            *data |= ((GLushort)(*invalue * 15) << 4) & 0x00F0;

            /* fourth bitfield */
            invalue = myImage++;
            *data |= (GLushort)(*invalue * 15) & 0x000F;

            data++;
            }
        }
        }
        break;

    case GL_UNSIGNED_SHORT_5_5_5_1_EXT:
        {
        GLushort *data = newImage;
        GLfloat *invalue;

        for (i = 0; i < imageHeight; i++) {
            for (j = 0; j < imageWidth; j++) {

            /* first bitfield */
            invalue = myImage++;
            *data = ((GLushort)(*invalue * 31) << 11) & 0xF800;

            /* second bitfield */
            invalue = myImage++;
            *data |= ((GLushort)(*invalue * 31) << 6) & 0x07C0;

            /* third bitfield */
            invalue = myImage++;
            *data |= ((GLushort)(*invalue * 31) << 1) & 0x003E;

            /* fourth bitfield */
            invalue = myImage++;
            *data |= (GLushort)(*invalue) & 0x0001;

            data++;
            }
        }
        }
        break;

    case GL_UNSIGNED_INT_8_8_8_8_EXT:
        {
        GLuint *data = newImage;
        GLfloat *invalue;

        for (i = 0; i < imageHeight; i++) {
            for (j = 0; j < imageWidth; j++) {

            /* first bitfield */
            invalue = myImage++;
            *data = ((GLuint)(*invalue * 255) << 24) & 0xFF000000;

            /* second bitfield */
            invalue = myImage++;
            *data |= ((GLushort)(*invalue * 255) << 16) &
                 0x00FF0000;

            /* third bitfield */
            invalue = myImage++;
            *data |= ((GLuint)(*invalue * 255) << 8) & 0x0000FF00;

            /* fourth bitfield */
            invalue = myImage++;
            *data |= (GLuint)(*invalue * 255) & 0x000000FF;

            data++;
            }
        }
        }
        break;

    case GL_UNSIGNED_INT_10_10_10_2_EXT:
        {
        GLuint *data = newImage;
        GLfloat *invalue;

        for (i = 0; i < imageHeight; i++) {
            for (j = 0; j < imageWidth; j++) {

            /* first bitfield */
            invalue = myImage++;
            *data = ((GLuint)(*invalue * 1023) << 22) & 0xFFC00000;

            /* second bitfield */
            invalue = myImage++;

            *data |= ((GLuint)(*invalue * 1023) << 12) & 0x003FF000;

            /* third bitfield */
            invalue = myImage++;
            *data |= ((GLuint)(*invalue * 1023) << 2) & 0x00000FFC;

            /* fourth bitfield */
            invalue = myImage++;
            *data |= (GLuint)(*invalue * 3) & 0x00000003;

            data++;
            }
        }
        }
        break;
    }
    imageData->newImage = newImage;
    imageData->imageSize = imageSize;
}
/*----------------------------------------------------------------------*/

ConvertImageRec imageData;

static int numTypes = 6;
static GLint components = 4;

void *tempbuf;

GLuint texSizeX, texSizeY;

/*----------------------------------------------------------------------*/

typedef struct cellStruct {
    int numRows;
    int numCols;
    int cellSizeX;
    int cellSizeY;
    int row;
    int col;
    int x;
    int y;
} cellStruct;

static cellStruct cell = {4,6, 0,0, 0,0, 0,0};

static void updateXY(cellStruct *cell)
{
    cell->x = cell->col * cell->cellSizeX;
    cell->y = cell->row * cell->cellSizeY;
}

static void setCell(cellStruct *cell, int col, int row)
{
    cell->col = col; cell->row = row;
    updateXY(cell);
}

static void nextCell(cellStruct *cell)
{
    cell->col ++;
    if (cell->col == cell->numCols) {
    cell->col = 0;
    cell->row ++;
    if (cell->row == cell->numRows) {
        cell->row = 0;
    }
    }
    updateXY(cell);
}

/*----------------------------------------------------------------------*/

/*
** Calculates the largest power of two that's less than the value.
*/
static unsigned int maxTexSize(unsigned int value)
{
    int power;

    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &power);
    if (value < power) {
    while(!(power & value)) power >>= 1;
    }
    return power;
}

/*
** Makes a ramped unsigned byte RGB image.
*/
static RGBImageRec *InitRampedImage(void)
{
    int i,j;
    int sizeX = 128;
    int sizeY = 128;
    int imageSize = sizeX*sizeY*3;
    GLubyte *byteImage, *pImg;
    RGBImageRec *rgbImage;

    rgbImage = (RGBImageRec *)malloc(sizeof(RGBImageRec));
    byteImage = (GLubyte *)malloc(imageSize);
    pImg = byteImage;

    for (i=0; i < sizeX; i++) {
    for (j=0; j < sizeY; j++) {
        *pImg++ = 255 * (float)i/sizeX;
        *pImg++ = 255 * (1-(float)j/sizeY);
        *pImg++ = 255 * (1-(float)i/sizeX);
    }
    }
    rgbImage->data = byteImage;
    rgbImage->sizeX = sizeX;
    rgbImage->sizeY = sizeY;
    return rgbImage;
}

static void Init(void)
{
    int i;
    GLfloat quant4[4];

    /*
    ** Create a float RGBA image from the file data.
    */
    CreateFloatImage(fileImage, &imageData);
    free(fileImage->data);
    free(fileImage);
    fileImage = NULL;

    /*
    ** Now create a working image with the float image.
    */
    imageData.type = GL_FLOAT;
    imageData.format = GL_RGBA;
    imageData.newImage = NULL;
    imageData.components = components = 4;
    imageData.bytesPerComp = 4;
    ConvertImage(&imageData);

    /*
    ** Allocate a buffer to read images into.
    */
    tempbuf = malloc(imageData.imageSize+3);
    if (NULL == tempbuf) {
    fprintf(stderr, "Out of memory!\n");
    exit(1);
    }

    /*
    ** Find the largest texture size that will work with this image.
    */
    texSizeX = maxTexSize((unsigned int) imageData.imageWidth);
    texSizeY = maxTexSize((unsigned int) imageData.imageHeight);

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glPixelStorei(GL_PACK_ALIGNMENT, 1);
    glPixelStorei(GL_UNPACK_ROW_LENGTH, imageData.imageDataWidth);
    glPixelStorei(GL_PACK_ROW_LENGTH, imageData.imageDataWidth);

    glScissor(0,0,winWidth,winHeight);
    glEnable(GL_SCISSOR_TEST);
    glColor3f(1.0, 1.0, 1.0);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

}

/*----------------------------------------------------------------------*/

/*
** Allows the caller to run through a list of types,
** setting the format, components, and bytesPerComp accordingly.
*/
static void nextType(ConvertImageRec *imageData)
{
    /*
    ** Note that packed pixel types are designed so that
    ** the image size can be computed by making the number
    ** of components = 1 and the bytesPerComponent = bytes
    ** per group.  This allows the arithmatic to be performed
    ** uniformly without introducing floating point values.
    ** imageData->components is used to compute the image size.
    ** components is passed to TexImage.
    */
    switch(imageData->type) {
      case GL_FLOAT:
    imageData->type = GL_UNSIGNED_BYTE_3_3_2_EXT;
    imageData->format = GL_RGB;
    imageData->components = 1;
    imageData->bytesPerComp = 1;
    components = 3;
    break;
      case GL_UNSIGNED_BYTE_3_3_2_EXT:
    imageData->type = GL_UNSIGNED_SHORT_4_4_4_4_EXT;
    imageData->format = GL_RGBA;
    imageData->components = 1;
    imageData->bytesPerComp = 2;
    components = 4;
    break;
      case GL_UNSIGNED_SHORT_4_4_4_4_EXT:
    imageData->type = GL_UNSIGNED_SHORT_5_5_5_1_EXT;
    imageData->format = GL_RGBA;
    imageData->components = 1;
    imageData->bytesPerComp = 2;
    components = 4;
    break;
      case GL_UNSIGNED_SHORT_5_5_5_1_EXT:
    imageData->type = GL_UNSIGNED_INT_8_8_8_8_EXT;
    imageData->format = GL_RGBA;
    imageData->components = 1;
    imageData->bytesPerComp = 4;
    components = 4;
    break;
      case GL_UNSIGNED_INT_8_8_8_8_EXT:
    imageData->type = GL_UNSIGNED_INT_10_10_10_2_EXT;
    imageData->format = GL_RGBA;
    imageData->components = 1;
    imageData->bytesPerComp = 4;
    components = 4;
    break;
      case GL_UNSIGNED_INT_10_10_10_2_EXT:
    imageData->type = GL_FLOAT;
    imageData->format = GL_RGBA;
    imageData->components = 4;
    imageData->bytesPerComp = 4;
    components = 4;
    break;
    }
}

/*----------------------------------------------------------------------*/

static void Reshape(int width, int height)
{
    winWidth = width; winHeight = height;
    glViewport(0, 0, width, height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, width, 0, height, -1, 1);
    glMatrixMode(GL_MODELVIEW);
    glScissor(0,0,width,height);

    /* Update the cell sizes to match the new window size. */
    cell.cellSizeX = width/cell.numCols;
    cell.cellSizeY = height/cell.numRows;
    imageData.imageWidth = cell.cellSizeX-4;
    imageData.imageHeight = cell.cellSizeY-4;

    /* Make sure we don't go stomping off the edges of our image data. */
    if (imageData.imageWidth > imageData.imageDataWidth) {
    imageData.imageWidth = imageData.imageDataWidth;
    }
    if (imageData.imageHeight > imageData.imageDataHeight) {
    imageData.imageHeight = imageData.imageDataHeight;
    }
}

/*
** Runs the packed pixel types through DrawPixels/ReadPixels
** and TexImage/GetTexImage.
*/
static void Draw(void)
{
    int i;

    glClear(GL_COLOR_BUFFER_BIT);

    setCell(&cell, 0,0);

    /* DrawPixels */

    for (i=0; i < numTypes; i++) {
    glRasterPos3f(0,0,-1);
    glBitmap(0,0,0,0, cell.x, cell.y, NULL);
    ConvertImage(&imageData);

    if (isUseDisplayLists) {
       glNewList(1,GL_COMPILE);
       assert(glGetError() == GL_NO_ERROR);
       glDrawPixels(imageData.imageWidth, imageData.imageHeight,
            imageData.format, imageData.type, imageData.newImage);
       assert(glGetError() == GL_NO_ERROR);
       glEndList();
       assert(glGetError() == GL_NO_ERROR);
       glCallList(1);
       assert(glGetError() == GL_NO_ERROR);

    }
    else {
       glDrawPixels(imageData.imageWidth, imageData.imageHeight,
            imageData.format, imageData.type, imageData.newImage);
        }

    nextType(&imageData);
    nextCell(&cell);
    }

    /* ReadPixels */

    for (i=0; i < numTypes; i++) {
    glReadPixels(0,0, imageData.imageWidth, imageData.imageHeight,
        imageData.format, imageData.type, tempbuf);
    glRasterPos3f(0,0,-1);
    glBitmap(0,0,0,0, cell.x, cell.y, NULL);
    glDrawPixels(imageData.imageWidth, imageData.imageHeight,
        imageData.format, imageData.type, tempbuf);

    nextType(&imageData);
    nextCell(&cell);
    }

    /* TexImage */

    glEnable(GL_TEXTURE_2D);

    for (i=0; i < numTypes; i++) {
    ConvertImage(&imageData);

    if (isUseDisplayLists) {
       glNewList(2,GL_COMPILE);
       assert(glGetError() == GL_NO_ERROR);
       glTexImage2D(GL_TEXTURE_2D, 0, components, texSizeX, texSizeY,
            0, imageData.format, imageData.type,
            imageData.newImage);

       assert(glGetError() == GL_NO_ERROR);
       glEndList();
       assert(glGetError() == GL_NO_ERROR);

       glCallList(2);
    }
    else {
       glTexImage2D(GL_TEXTURE_2D, 0, components, texSizeX, texSizeY,
            0, imageData.format, imageData.type,
            imageData.newImage);
    }

    glBegin(GL_QUADS);
        glTexCoord2f(0.0, 0.0);
        glVertex2i(cell.x, cell.y);
        glTexCoord2f(1.0, 0.0);
        glVertex2i(cell.x+imageData.imageWidth-1, cell.y);
        glTexCoord2f(1.0, 1.0);
        glVertex2i(cell.x+imageData.imageWidth-1,
            cell.y+imageData.imageHeight-1);
        glTexCoord2f(0.0, 1.0);
        glVertex2i(cell.x, cell.y+imageData.imageHeight-1);
    glEnd();

    nextType(&imageData);
    nextCell(&cell);
    }

    /* GetTexImage */

    for (i=0; i < numTypes; i++) {
    glTexImage2D(GL_TEXTURE_2D, 0, 4, texSizeX, texSizeY,
            0, GL_RGBA, GL_FLOAT, imageData.fltImage);
    glGetTexImage(GL_TEXTURE_2D, 0, imageData.format,
            imageData.type, tempbuf);
    glTexImage2D(GL_TEXTURE_2D, 0, components, texSizeX, texSizeY,
            0, imageData.format, imageData.type, tempbuf);
    glBegin(GL_QUADS);
        glTexCoord2f(0.0, 0.0);
        glVertex2i(cell.x, cell.y);
        glTexCoord2f(1.0, 0.0);
        glVertex2i(cell.x+imageData.imageWidth-1, cell.y);
        glTexCoord2f(1.0, 1.0);
        glVertex2i(cell.x+imageData.imageWidth-1,
            cell.y+imageData.imageHeight-1);
        glTexCoord2f(0.0, 1.0);
        glVertex2i(cell.x, cell.y+imageData.imageHeight-1);
    glEnd();

    nextType(&imageData);
    nextCell(&cell);
    }
    glDisable(GL_TEXTURE_2D);

    if (doubleBuffer) {
    glutSwapBuffers();
    } else {
    glFlush();
    }
}

/*----------------------------------------------------------------------*/

static char *keyList[][2] = {
    {"R", "Reset draw routine"},
    {"d", "toggle use of display lists"},
    {"",""},
    {"h", "print this list"},
    {"ESC", "exit"},
    {NULL, NULL}
};

static void Key(unsigned char key, int x, int y)
{
    int i;

    switch (key) {
      case 'h':
    for (i = 0; keyList[i][0] != NULL; i++) {
        printf("%-20s %s\n", keyList[i][0], keyList[i][1]);
    }
    break;
      case 'd':
    if (isUseDisplayLists) {
       printf("Not using display lists.\n");
       isUseDisplayLists= GL_FALSE;
    }
    else {
       printf("Using display lists in bottom row & 2nd row from top.\n");
       isUseDisplayLists= GL_TRUE;
    }
    glutPostRedisplay();
    break;
      case 'R':
    glutPostRedisplay();
    break;

      case 27:
    exit(0);
    }
}

/*----------------------------------------------------------------------*/

static char *argList[][2] = {
    {"-sb", "single buffered"},
    {"-db", "double buffered"},
    {"-f filename", "filename for texture"},
    {"-nq", "skip querying extensions"},
    {"-h", "print this list"},
    {"Press H in app window to get key interpretations", ""},
    {NULL, NULL}
};

static void Args(int argc, char **argv)
{
    GLint i;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    } else if (strcmp(argv[i], "-f") == 0) {
        if (i+1 >= argc || argv[i+1][0] == '-') {
        printf("-f (No file name).\n");
        exit(1);
        } else {
        rgbFile = argv[++i];
        }
    } else if (strcmp(argv[i], "-nq") == 0) {
        queryExts = GL_FALSE;
    } else if (strcmp(argv[i], "-h") == 0) {
        for (i = 0; argList[i][0] != NULL; i++) {
        printf("%-30s %s\n", argList[i][0], argList[i][1]);
        }
        exit(0);
    }
    }
}

/*----------------------------------------------------------------------*/

static GLboolean QueryExtension(char *extName)
{
    /*
    ** Search for extName in the extensions string. Use of strstr()
    ** is not sufficient because extension names can be prefixes of
    ** other extension names. Could use strtok() but the constant
    ** string returned by glGetString can be in read-only memory.
    */
    char *p;
    char *end;
    int extNameLen;

    extNameLen = strlen(extName);

    p = (char *)glGetString(GL_EXTENSIONS);
    if (NULL == p) {
    return GL_FALSE;
    }

    end = p + strlen(p);

    while (p < end) {
    int n = strcspn(p, " ");
    if ((extNameLen == n) && (strncmp(extName, p, n) == 0)) {
        return GL_TRUE;
    }
    p += (n + 1);
    }
    return GL_FALSE;
}

/*----------------------------------------------------------------------*/

int main(int argc, char **argv)
{
    GLenum type;

    glutInit(&argc, argv);
    Args(argc, argv);

    /*
    ** The image is loaded before the window is opened to
    ** allow sizing the window based on the image size.
    */
    if (NULL == rgbFile) {
        fileImage = InitRampedImage();
    }
    else {
    fileImage = rgbImageLoad(rgbFile);
    if (NULL == fileImage) {
        fprintf(stdout, "Warning: file %s not found.\n", rgbFile);
        fileImage = InitRampedImage();
    }
    }
    cell.cellSizeX = fileImage->sizeX + 4;
    cell.cellSizeY = fileImage->sizeY + 4;
    winWidth = cell.numCols*cell.cellSizeX - 4;
    winHeight = cell.numRows*cell.cellSizeY - 4;

    type = GLUT_RGB;
    type |= (doubleBuffer) ? GLUT_DOUBLE : GLUT_SINGLE;
    glutInitDisplayMode(type);
    glutInitWindowSize(winWidth, winHeight);
    glutCreateWindow("Packed Pixel Test");

    if (queryExts && !QueryExtension("GL_EXT_packed_pixels")) {
    fprintf(stdout, "Warning: packed pixel extension not found.\n");
    exit(0);
    }

    Init();

    glutReshapeFunc(Reshape);
    glutKeyboardFunc(Key);
    glutDisplayFunc(Draw);
    glutMainLoop();
}


prim.C

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <GL/glut.h>

#define PIXEL_CENTER(x) ((long)(x) + 0.5)

#define GAP 10
#define ROWS 3
#define COLS 4

#define OPENGL_WIDTH 48
#define OPENGL_HEIGHT 13

GLenum rgb, doubleBuffer;
GLint windW = 600, windH = 300;

GLenum mode1, mode2;
GLint boxW, boxH;
GLubyte OpenGL_bits[] = {
   0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
   0x7f, 0xfb, 0xff, 0xff, 0xff, 0x01,
   0x7f, 0xfb, 0xff, 0xff, 0xff, 0x01,
   0x00, 0x03, 0x00, 0x00, 0x00, 0x00,
   0x3e, 0x8f, 0xb7, 0xf9, 0xfc, 0x01,
   0x63, 0xdb, 0xb0, 0x8d, 0x0d, 0x00,
   0x63, 0xdb, 0xb7, 0x8d, 0x0d, 0x00,
   0x63, 0xdb, 0xb6, 0x8d, 0x0d, 0x00,
   0x63, 0x8f, 0xf3, 0xcc, 0x0d, 0x00,
   0x63, 0x00, 0x00, 0x0c, 0x4c, 0x0a,
   0x63, 0x00, 0x00, 0x0c, 0x4c, 0x0e,
   0x63, 0x00, 0x00, 0x8c, 0xed, 0x0e,
   0x3e, 0x00, 0x00, 0xf8, 0x0c, 0x00,
};

static void Init(void)
{

    mode1 = GL_TRUE;
    mode2 = GL_TRUE;
}

static void Reshape(int width, int height)
{

    windW = width;
    windH = height;
}

static void RotateColorMask(void)
{
    static GLint rotation = 0;

    rotation = (rotation + 1) & 0x3;
    switch (rotation) {
      case 0:
    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    glIndexMask(0xff);
    break;
      case 1:
    glColorMask(GL_FALSE, GL_TRUE, GL_TRUE, GL_TRUE);
    glIndexMask(0xFE);
    break;
      case 2:
    glColorMask(GL_TRUE, GL_FALSE, GL_TRUE, GL_TRUE);
    glIndexMask(0xFD);
    break;
      case 3:
    glColorMask(GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE);
    glIndexMask(0xFB);
    break;
    }
}

static void SetColor(int index)
{

    if (rgb) {
    switch (index) {
      case 0:
        glColor3f(0.0, 0.0, 0.0);
        break;
      case 1:
        glColor3f(1.0, 0.0, 0.0);
        break;
      case 2:
        glColor3f(0.0, 1.0, 0.0);
        break;
      case 3:
        glColor3f(1.0, 1.0, 0.0);
        break;
      case 4:
        glColor3f(0.0, 0.0, 1.0);
        break;
      case 5:
        glColor3f(1.0, 0.0, 1.0);
        break;
      case 6:
        glColor3f(0.0, 1.0, 1.0);
        break;
      case 7:
        glColor3f(1.0, 1.0, 1.0);
        break;
    }
    } else {
    glIndexi(index);
    }
}

static void Key(unsigned char key, int x, int y)
{

    switch (key) {
      case '1':
    mode1 = !mode1;
    glutPostRedisplay();
    break;
      case '2':
    mode2 = !mode2;
    glutPostRedisplay();
    break;
      case '3':
    RotateColorMask();
    glutPostRedisplay();
    break;
      case 27:
    exit(0);
    }
}

static void Viewport(GLint row, GLint column)
{
    GLint x, y;

    boxW = (windW - (COLS + 1) * GAP) / COLS;
    boxH = (windH - (ROWS + 1) * GAP) / ROWS;

    x = GAP + column * (boxW + GAP);
    y = GAP + row * (boxH + GAP);

    glViewport(x, y, boxW, boxH);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-boxW/2, boxW/2, -boxH/2, boxH/2, 0.0, 1.0);
    glMatrixMode(GL_MODELVIEW);

    glEnable(GL_SCISSOR_TEST);
    glScissor(x, y, boxW, boxH);
}

static void Point(void)
{
    GLint i;

    glBegin(GL_POINTS);
    for (i = 1; i < 8; i++) {
        GLint j = i * 2;
        SetColor(i);
        glVertex2i(-j, -j);
        glVertex2i(-j, 0);
        glVertex2i(-j, j);
        glVertex2i(0, j);
        glVertex2i(j, j);
        glVertex2i(j, 0);
        glVertex2i(j, -j);
        glVertex2i(0, -j);
    }
    glEnd();
}

static void Lines(void)
{
    GLint i;

    glPushMatrix();

    glTranslatef(-12, 0, 0);
    for (i = 1; i < 8; i++) {
    SetColor(i);
    glBegin(GL_LINES);
        glVertex2i(-boxW/4, -boxH/4);
        glVertex2i(boxW/4, boxH/4);
    glEnd();
    glTranslatef(4, 0, 0);
    }

    glPopMatrix();

    (rgb) ? glColor3f(1.0, 1.0, 1.0) : glIndexi(7);
    glBegin(GL_LINES);
    glVertex2i(0, 0);
    glEnd();
}

static void LineStrip(void)
{

    glBegin(GL_LINE_STRIP);
    SetColor(1);
    glVertex2f(PIXEL_CENTER(-boxW/4), PIXEL_CENTER(-boxH/4));
    SetColor(2);
    glVertex2f(PIXEL_CENTER(-boxW/4), PIXEL_CENTER(boxH/4));
    SetColor(3);
    glVertex2f(PIXEL_CENTER(boxW/4), PIXEL_CENTER(boxH/4));
    SetColor(4);
    glVertex2f(PIXEL_CENTER(boxW/4), PIXEL_CENTER(-boxH/4));
    glEnd();

    (rgb) ? glColor3f(1.0, 1.0, 1.0) : glIndexi(7);
    glBegin(GL_LINE_STRIP);
    glVertex2i(0, 0);
    glEnd();
}

static void LineLoop(void)
{

    glBegin(GL_LINE_LOOP);
    SetColor(1);
    glVertex2f(PIXEL_CENTER(-boxW/4), PIXEL_CENTER(-boxH/4));
    SetColor(2);
    glVertex2f(PIXEL_CENTER(-boxW/4), PIXEL_CENTER(boxH/4));
    SetColor(3);
    glVertex2f(PIXEL_CENTER(boxW/4), PIXEL_CENTER(boxH/4));
    SetColor(4);
    glVertex2f(PIXEL_CENTER(boxW/4), PIXEL_CENTER(-boxH/4));
    glEnd();

    glEnable(GL_LOGIC_OP);
    glLogicOp(GL_XOR);

    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);

    SetColor(5);
    glBegin(GL_LINE_LOOP);
    glVertex2f(PIXEL_CENTER(-boxW/8), PIXEL_CENTER(-boxH/8));
    glVertex2f(PIXEL_CENTER(-boxW/8), PIXEL_CENTER(boxH/8));
    glEnd();
    glBegin(GL_LINE_LOOP);
    glVertex2f(PIXEL_CENTER(-boxW/8), PIXEL_CENTER(boxH/8+5));
    glVertex2f(PIXEL_CENTER(boxW/8), PIXEL_CENTER(boxH/8+5));
    glEnd();
    glDisable(GL_LOGIC_OP);
    glDisable(GL_BLEND);

    SetColor(6);
    glBegin(GL_POINTS);
    glVertex2i(0, 0);
    glEnd();

    (rgb) ? glColor3f(1.0, 1.0, 1.0) : glIndexi(7);
    glBegin(GL_LINE_LOOP);
    glVertex2i(0, 0);
    glEnd();
}

static void Bitmap(void)
{

    glBegin(GL_LINES);
    SetColor(1);
    glVertex2i(-boxW/2, 0);
    glVertex2i(boxW/2, 0);
    glVertex2i(0, -boxH/2);
    glVertex2i(0, boxH/2);
    SetColor(2);
    glVertex2i(0, -3);
    glVertex2i(0, -3+OPENGL_HEIGHT);
    SetColor(3);
    glVertex2i(0, -3);
    glVertex2i(OPENGL_WIDTH, -3);
    glEnd();

    SetColor(4);

    glPixelStorei(GL_UNPACK_LSB_FIRST, GL_TRUE);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    glRasterPos2i(0, 0);
    glBitmap(OPENGL_WIDTH, OPENGL_HEIGHT, 0, 3, 0.0, 0.0, OpenGL_bits);
}

static void Triangles(void)
{

    glBegin(GL_TRIANGLES);
    SetColor(1);
    glVertex2i(-boxW/4, -boxH/4);
    SetColor(2);
    glVertex2i(-boxW/8, -boxH/16);
    SetColor(3);
    glVertex2i(boxW/8, -boxH/16);

    SetColor(4);
    glVertex2i(-boxW/4, boxH/4);
    SetColor(5);
    glVertex2i(-boxW/8, boxH/16);
    SetColor(6);
    glVertex2i(boxW/8, boxH/16);
    glEnd();

    (rgb) ? glColor3f(1.0, 1.0, 1.0) : glIndexi(7);
    glBegin(GL_TRIANGLES);
    glVertex2i(0, 0);
    glVertex2i(-100, 100);
    glEnd();
}

static void TriangleStrip(void)
{

    glBegin(GL_TRIANGLE_STRIP);
    SetColor(1);
    glVertex2i(-boxW/4, -boxH/4);
    SetColor(2);
    glVertex2i(-boxW/4, boxH/4);
    SetColor(3);
    glVertex2i(0, -boxH/4);
    SetColor(4);
    glVertex2i(0, boxH/4);
    SetColor(5);
    glVertex2i(boxW/4, -boxH/4);
    SetColor(6);
    glVertex2i(boxW/4, boxH/4);
    glEnd();

    (rgb) ? glColor3f(1.0, 1.0, 1.0) : glIndexi(7);
    glBegin(GL_TRIANGLE_STRIP);
    glVertex2i(0, 0);
    glVertex2i(-100, 100);
    glEnd();
}

static void TriangleFan(void)
{
    GLint vx[8][2];
    GLint x0, y0, x1, y1, x2, y2, x3, y3;
    GLint i;

    y0 = -boxH/4;
    y1 = y0 + boxH/2/3;
    y2 = y1 + boxH/2/3;
    y3 = boxH/4;
    x0 = -boxW/4;
    x1 = x0 + boxW/2/3;
    x2 = x1 + boxW/2/3;
    x3 = boxW/4;

    vx[0][0] = x0; vx[0][1] = y1;
    vx[1][0] = x0; vx[1][1] = y2;
    vx[2][0] = x1; vx[2][1] = y3;
    vx[3][0] = x2; vx[3][1] = y3;
    vx[4][0] = x3; vx[4][1] = y2;
    vx[5][0] = x3; vx[5][1] = y1;
    vx[6][0] = x2; vx[6][1] = y0;
    vx[7][0] = x1; vx[7][1] = y0;

    glBegin(GL_TRIANGLE_FAN);
    SetColor(7);
    glVertex2i(0, 0);
    for (i = 0; i < 8; i++) {
        SetColor(7-i);
        glVertex2iv(vx[i]);
    }
    glEnd();

    (rgb) ? glColor3f(1.0, 1.0, 1.0) : glIndexi(7);
    glBegin(GL_TRIANGLE_FAN);
    glVertex2i(0, 0);
    glVertex2i(-100, 100);
    glEnd();
}

static void Rect(void)
{

    (rgb) ? glColor3f(1.0, 0.0, 1.0) : glIndexi(5);
    glRecti(-boxW/4, -boxH/4, boxW/4, boxH/4);
}

static void Polygons(void)
{
    GLint vx[8][2];
    GLint x0, y0, x1, y1, x2, y2, x3, y3;
    GLint i;

    y0 = -boxH/4;
    y1 = y0 + boxH/2/3;
    y2 = y1 + boxH/2/3;
    y3 = boxH/4;
    x0 = -boxW/4;
    x1 = x0 + boxW/2/3;
    x2 = x1 + boxW/2/3;
    x3 = boxW/4;

    vx[0][0] = x0; vx[0][1] = y1;
    vx[1][0] = x0; vx[1][1] = y2;
    vx[2][0] = x1; vx[2][1] = y3;
    vx[3][0] = x2; vx[3][1] = y3;
    vx[4][0] = x3; vx[4][1] = y2;
    vx[5][0] = x3; vx[5][1] = y1;
    vx[6][0] = x2; vx[6][1] = y0;
    vx[7][0] = x1; vx[7][1] = y0;

    glBegin(GL_POLYGON);
    for (i = 0; i < 8; i++) {
        SetColor(7-i);
        glVertex2iv(vx[i]);
    }
    glEnd();

    (rgb) ? glColor3f(1.0, 1.0, 1.0) : glIndexi(7);
    glBegin(GL_POLYGON);
    glVertex2i(0, 0);
    glVertex2i(100, 100);
    glEnd();
}

static void Quads(void)
{

    glBegin(GL_QUADS);
    SetColor(1);
    glVertex2i(-boxW/4, -boxH/4);
    SetColor(2);
    glVertex2i(-boxW/8, -boxH/16);
    SetColor(3);
    glVertex2i(boxW/8, -boxH/16);
    SetColor(4);
    glVertex2i(boxW/4, -boxH/4);

    SetColor(5);
    glVertex2i(-boxW/4, boxH/4);
    SetColor(6);
    glVertex2i(-boxW/8, boxH/16);
    SetColor(7);
    glVertex2i(boxW/8, boxH/16);
    SetColor(0);
    glVertex2i(boxW/4, boxH/4);
    glEnd();

    (rgb) ? glColor3f(1.0, 1.0, 1.0) : glIndexi(7);
    glBegin(GL_QUADS);
    glVertex2i(0, 0);
    glVertex2i(100, 100);
    glVertex2i(-100, 100);
    glEnd();
}

static void QuadStrip(void)
{

    glBegin(GL_QUAD_STRIP);
    SetColor(1);
    glVertex2i(-boxW/4, -boxH/4);
    SetColor(2);
    glVertex2i(-boxW/4, boxH/4);
    SetColor(3);
    glVertex2i(0, -boxH/4);
    SetColor(4);
    glVertex2i(0, boxH/4);
    SetColor(5);
    glVertex2i(boxW/4, -boxH/4);
    SetColor(6);
    glVertex2i(boxW/4, boxH/4);
    glEnd();

    (rgb) ? glColor3f(1.0, 1.0, 1.0) : glIndexi(7);
    glBegin(GL_QUAD_STRIP);
    glVertex2i(0, 0);
    glVertex2i(100, 100);
    glVertex2i(-100, 100);
    glEnd();
}

static void Draw(void)
{

    glViewport(0, 0, windW, windH);
    glDisable(GL_SCISSOR_TEST);

    glPushAttrib(GL_COLOR_BUFFER_BIT);

    glColorMask(1, 1, 1, 1);
    glIndexMask(~0);

    glClearColor(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);

    glPopAttrib();

    if (mode1) {
    glShadeModel(GL_SMOOTH);
    } else {
    glShadeModel(GL_FLAT);
    }

    if (mode2) {
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    } else {
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    }

    Viewport(0, 0); Point();
    Viewport(0, 1); Lines();
    Viewport(0, 2); LineStrip();
    Viewport(0, 3); LineLoop();

    Viewport(1, 0); Bitmap();

    Viewport(1, 1); TriangleFan();
    Viewport(1, 2); Triangles();
    Viewport(1, 3); TriangleStrip();

    Viewport(2, 0); Rect();
    Viewport(2, 1); Polygons();
    Viewport(2, 2); Quads();
    Viewport(2, 3); QuadStrip();

    if (doubleBuffer) {
    glutSwapBuffers();
    } else {
    glFlush();
    }
}

static void Args(int argc, char **argv)
{
    GLint i;

    rgb = GL_TRUE;
    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-ci") == 0) {
        rgb = GL_FALSE;
    } else if (strcmp(argv[i], "-rgb") == 0) {
        rgb = GL_TRUE;
    } else if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    }
    }
}

int main(int argc, char **argv)
{
    GLenum type;

    glutInit(&argc, argv);
    Args(argc, argv);

    type = (rgb) ? GLUT_RGB : GLUT_INDEX;
    type |= (doubleBuffer) ? GLUT_DOUBLE : GLUT_SINGLE;
    glutInitDisplayMode(type);
    glutInitWindowSize(windW, windH);
    glutCreateWindow("Primitive Test");

    Init();

    glutReshapeFunc(Reshape);
    glutKeyboardFunc(Key);
    glutDisplayFunc(Draw);
    glutMainLoop();
}


quad.C

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <GL/glut.h>
#include "rgb.h"

#ifndef WIN32
#define CALLBACK
#endif

#define PI 3.141592654
#define	BLACK 0
#define	GRAY 128
#define	WHITE 255
#define RD 0xA4,0x00,0x00,0xFF
#define WT 0xFF,0xFF,0xFF,0xFF
#define	brickImageWidth 16
#define	brickImageHeight 16

GLenum doubleBuffer;

float black[3] = {
    0.0, 0.0, 0.0
};
float blue[3] =  {
    0.0, 0.0, 1.0
};
float gray[3] =  {
    0.5, 0.5, 0.5
};
float white[3] = {
    1.0, 1.0, 1.0
};

GLenum doDither = GL_TRUE;
GLenum shade = GL_TRUE;
GLenum texture = GL_TRUE;

float xRotation = 30.0, yRotation = 30.0, zRotation = 0.0;
GLint radius1, radius2;
GLdouble angle1, angle2;
GLint slices, stacks;
GLint height;
GLint orientation = GLU_OUTSIDE;
GLint whichQuadric;
GLUquadricObj *quadObj;

GLubyte brickImage[4*brickImageWidth*brickImageHeight] = {
    RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD,
    WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT,
    RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD,
    RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD,
    RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD,
    RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD, RD,
    WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT,
    RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD,
    RD, RD, RD, RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD,
    WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT, WT,
    RD, RD, RD, RD, WT, RD, RD, RD, RD, RD, RD, RD, RD, RD, WT, RD
};
RGBImageRec *image = NULL;

static void CALLBACK ErrorHandler(GLenum which)
{

    fprintf(stderr, "Quad Error: %s\n", gluErrorString(which));
}

static void Init(void)
{
    static GLint colorIndexes[3] = {0, 200, 255};
    static float ambient[] = {0.1, 0.1, 0.1, 1.0};
    static float diffuse[] = {0.5, 1.0, 1.0, 1.0};
    static float position[] = {90.0, 90.0, 150.0, 0.0};
    static float front_mat_shininess[] = {30.0};
    static float front_mat_specular[] = {0.2, 0.2, 0.2, 1.0};
    static float front_mat_diffuse[] = {0.5, 0.28, 0.38, 1.0};
    static float back_mat_shininess[] = {50.0};
    static float back_mat_specular[] = {0.5, 0.5, 0.2, 1.0};
    static float back_mat_diffuse[] = {1.0, 1.0, 0.2, 1.0};
    static float lmodel_ambient[] = {1.0, 1.0, 1.0, 1.0};
    static float lmodel_twoside[] = {GL_TRUE};
    static float decal[] = {GL_DECAL};
    static float modulate[] = {GL_MODULATE};
    static float repeat[] = {GL_REPEAT};
    static float nearest[] = {GL_NEAREST};

    glClearColor(0.0, 0.0, 0.0, 0.0);

    glEnable(GL_DEPTH_TEST);

    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
    glLightfv(GL_LIGHT0, GL_POSITION, position);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
    glLightModelfv(GL_LIGHT_MODEL_TWO_SIDE, lmodel_twoside);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    glMaterialfv(GL_FRONT, GL_SHININESS, front_mat_shininess);
    glMaterialfv(GL_FRONT, GL_SPECULAR, front_mat_specular);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, front_mat_diffuse);
    glMaterialfv(GL_BACK, GL_SHININESS, back_mat_shininess);
    glMaterialfv(GL_BACK, GL_SPECULAR, back_mat_specular);
    glMaterialfv(GL_BACK, GL_DIFFUSE, back_mat_diffuse);

    glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, decal);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, repeat);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, repeat);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, nearest);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, nearest);
    if (image) {
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    gluBuild2DMipmaps(GL_TEXTURE_2D, 3, image->sizeX, image->sizeY,
              GL_RGB, GL_UNSIGNED_BYTE, image->data);
    } else {
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glTexImage2D(GL_TEXTURE_2D, 0, 4, brickImageWidth, brickImageHeight,
             0, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid *)brickImage);
    }

    quadObj = gluNewQuadric();
    gluQuadricCallback(quadObj, GLU_ERROR, ErrorHandler);

    radius1 = 10;
    radius2 = 5;
    angle1 = 90;
    angle2 = 180;
    slices = 16;
    stacks = 10;
    height = 20;
}

static void Reshape(int width, int height)
{

    glViewport(0, 0, width, height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum(-1, 1, -1, 1, 1, 10);
    gluLookAt(2, 2, 2, 0, 0, 0, 0, 0, 1);
    glMatrixMode(GL_MODELVIEW);
}

static void Key(unsigned char key, int x, int y)
{

    switch (key) {
      case 'X':
    zRotation += 5;
    glutPostRedisplay();
    break;
      case 'x':
    zRotation -= 5;
    glutPostRedisplay();
    break;

      case '1':
    gluQuadricDrawStyle(quadObj, GLU_FILL);
    glutPostRedisplay();
    break;
      case '2':
    gluQuadricDrawStyle(quadObj, GLU_POINT);
    glutPostRedisplay();
    break;
      case '3':
    gluQuadricDrawStyle(quadObj, GLU_LINE);
    glutPostRedisplay();
    break;
      case '4':
    gluQuadricDrawStyle(quadObj, GLU_SILHOUETTE);
    glutPostRedisplay();
    break;

      case '0':
    shade = !shade;
    if (shade) {
        glShadeModel(GL_SMOOTH);
        gluQuadricNormals(quadObj, GLU_SMOOTH);
    } else {
        glShadeModel(GL_FLAT);
        gluQuadricNormals(quadObj, GLU_FLAT);
    }
    glutPostRedisplay();
    break;

      case 'A':
    stacks++;
    glutPostRedisplay();
    break;
      case 'a':
    stacks--;
    glutPostRedisplay();
    break;

      case 'S':
    slices++;
    glutPostRedisplay();
    break;
      case 's':
    slices--;
    glutPostRedisplay();
    break;

      case 'd':
    switch(orientation) {
      case GLU_OUTSIDE:
        orientation = GLU_INSIDE;
        break;
      case GLU_INSIDE:
      default:
        orientation = GLU_OUTSIDE;
        break;
    }
    gluQuadricOrientation(quadObj, orientation);
    glutPostRedisplay();
    break;

      case 'f':
    whichQuadric = (whichQuadric + 1) & 0x03;
    glutPostRedisplay();
    break;

      case 'G':
    radius1 += 1;
    glutPostRedisplay();
    break;
      case 'g':
    radius1 -= 1;
    glutPostRedisplay();
    break;

      case 'J':
    radius2 += 1;
    glutPostRedisplay();
    break;
      case 'j':
    radius2 -= 1;
    glutPostRedisplay();
    break;

      case 'H':
    height += 2;
    glutPostRedisplay();
    break;
      case 'h':
    height -= 2;
    glutPostRedisplay();
    break;

      case 'K':
    angle1 += 5;
    glutPostRedisplay();
    break;
      case 'k':
    angle1 -= 5;
    glutPostRedisplay();
    break;

      case 'L':
    angle2 += 5;
    glutPostRedisplay();
    break;
      case 'l':
    angle2 -= 5;
    glutPostRedisplay();
    break;

      case 'z':
        texture = !texture;
    if (texture) {
        gluQuadricTexture(quadObj, GL_TRUE);
        glEnable(GL_TEXTURE_2D);
    } else {
        gluQuadricTexture(quadObj, GL_FALSE);
        glDisable(GL_TEXTURE_2D);
    }
    glutPostRedisplay();
    break;

      case 'q':
    glDisable(GL_CULL_FACE);
    glutPostRedisplay();
    break;
      case 'w':
    glEnable(GL_CULL_FACE);
    glCullFace(GL_FRONT);
    glutPostRedisplay();
    break;
      case 'e':
    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK);
    glutPostRedisplay();
    break;

      case 'r':
    glFrontFace(GL_CW);
    glutPostRedisplay();
    break;
      case 't':
    glFrontFace(GL_CCW);
    glutPostRedisplay();
    break;

      case 'y':
    doDither = !doDither;
    (doDither) ? glEnable(GL_DITHER) : glDisable(GL_DITHER);
    glutPostRedisplay();
    break;

      case 27:
    exit(0);
    }
}

static void SpecialKey(int key, int x, int y)
{

    switch (key) {
      case GLUT_KEY_LEFT:
    yRotation += 5;
    glutPostRedisplay();
    break;
      case GLUT_KEY_RIGHT:
    yRotation -= 5;
    glutPostRedisplay();
    break;
      case GLUT_KEY_UP:
    xRotation += 5;
    glutPostRedisplay();
    break;
      case GLUT_KEY_DOWN:
    xRotation -= 5;
    glutPostRedisplay();
    break;
    }
}

static void Draw(void)
{

    glLoadIdentity();
    glRotatef(xRotation, 1, 0, 0);
    glRotatef(yRotation, 0, 1, 0);
    glRotatef(zRotation, 0, 0, 1);

    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    glColor3f(1.0, 1.0, 1.0);
    switch (whichQuadric) {
      case 0:
    glTranslatef(0, 0, -height/20.0);
    gluCylinder(quadObj, radius1/10.0, radius2/10.0, height/10.0,
            slices, stacks);
    break;
      case 1:
    gluSphere(quadObj, radius1/10.0, slices, stacks);
    break;
      case 2:
    gluPartialDisk(quadObj, radius2/10.0, radius1/10.0, slices,
               stacks, angle1, angle2);
    break;
      case 3:
    gluDisk(quadObj, radius2/10.0, radius1/10.0, slices, stacks);
    break;
    }

    if (doubleBuffer) {
    glutSwapBuffers();
    } else {
    glFlush();
    }
}

static void Args(int argc, char **argv)
{
    GLint i;

    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    } else if (strcmp(argv[i], "-f") == 0) {
        if (i+1 >= argc || argv[i+1][0] == '-') {
        printf("-f (No file name).\n");
        exit(1);
        } else {
        image = rgbImageLoad(argv[++i]);
        if (image == NULL) {
            printf("-f (Bad file name).\n");
            exit(1);
        }
        }
    }
    }
}

int main(int argc, char **argv)
{
    GLenum type;

    glutInit(&argc, argv);
    Args(argc, argv);

    if (image == NULL) {
    printf("No texture file.\n");
    exit(1);
    }

    type = GLUT_RGB | GLUT_DEPTH;
    type |= (doubleBuffer) ? GLUT_DOUBLE : GLUT_SINGLE;
    glutInitDisplayMode(type);
    glutInitWindowSize(300, 300);
    glutCreateWindow("Quad Test");

    Init();

    glutReshapeFunc(Reshape);
    glutKeyboardFunc(Key);
    glutSpecialFunc(SpecialKey);
    glutDisplayFunc(Draw);
    glutMainLoop();
}


sphere.C

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <GL/glut.h>
#include "rgb.h"

#ifdef WIN32
#define sinf sin
#define cosf cos
#endif

#define FALSE 0
#define TRUE  1
#define PI    3.14159265358979323846

GLenum doubleBuffer;
int W = 400, H = 400;

RGBImageRec *image = NULL;

int numComponents;

float *minFilter, *magFilter, *sWrapMode, *tWrapMode;
float decal[] = {GL_DECAL};
float modulate[] = {GL_MODULATE};
float repeat[] = {GL_REPEAT};
float clamp[] = {GL_CLAMP};
float nearest[] = {GL_NEAREST};
float linear[] = {GL_LINEAR};
float nearest_mipmap_nearest[] = {GL_NEAREST_MIPMAP_NEAREST};
float nearest_mipmap_linear[] = {GL_NEAREST_MIPMAP_LINEAR};
float linear_mipmap_nearest[] = {GL_LINEAR_MIPMAP_NEAREST};
float linear_mipmap_linear[] = {GL_LINEAR_MIPMAP_LINEAR};
GLint sphereMap[] = {GL_SPHERE_MAP};

float xRotation = 0.0, yRotation = 0.0;
float zTranslate = -4.0;
GLenum autoRotate = TRUE;
GLenum deepestColor = GLUT_GREEN;
GLenum isLit = TRUE;
GLenum isFogged = FALSE;
float *textureEnvironment = modulate;

struct MipMap {
    int width, height;
    unsigned char *data;
};

int cube, cage, cylinder, torus, genericObject;

float c[6][4][4][3] = {
    {
    {
        {
        1.0, 1.0, -1.0
        },
        {
        0.0, 1.0, -1.0
        },
        {
        0.0, 0.0, -1.0
        },
        {
        1.0, 0.0, -1.0
        },
    },
    {
        {
        0.0, 1.0, -1.0
        },
        {
        -1.0, 1.0, -1.0
        },
        {
        -1.0, 0.0, -1.0
        },
        {
        0.0, 0.0, -1.0
        },
    },
    {
        {
        0.0,  0.0, -1.0
        },
        {
        -1.0, 0.0, -1.0
        },
        {
        -1.0, -1.0, -1.0
        },
        {
        0.0, -1.0, -1.0
        },
    },
    {
        {
        1.0, 0.0, -1.0
        },
        {
        0.0, 0.0, -1.0
        },
        {
        0.0, -1.0, -1.0
        },
        {
        1.0, -1.0, -1.0
        },
    },
    },
    {
    {
        {
        1.0, 1.0, 1.0
        },
        {
        1.0, 1.0, 0.0
        },
        {
        1.0, 0.0, 0.0
        },
        {
        1.0, 0.0, 1.0
        },
    },
    {
        {
        1.0, 1.0, 0.0
        },
        {
        1.0, 1.0, -1.0
        },
        {
        1.0, 0.0, -1.0
        },
        {
        1.0, 0.0, 0.0
        },
    },
    {
        {
        1.0, 0.0, -1.0
        },
        {
        1.0, -1.0, -1.0
        },
        {
        1.0, -1.0, 0.0
        },
        {
        1.0, 0.0, 0.0
        },
    },
    {
        {
        1.0, 0.0, 0.0
        },
        {
        1.0, -1.0, 0.0
        },
        {
        1.0, -1.0, 1.0
        },
        {
        1.0, 0.0, 1.0
        },
    },
    },
    {
    {
        {
        -1.0, 1.0, 1.0
        },
        {
        0.0, 1.0, 1.0
        },
        {
        0.0, 0.0, 1.0
        },
        {
        -1.0, 0.0, 1.0
        },
    },
    {
        {
        0.0, 1.0, 1.0
        },
        {
        1.0, 1.0, 1.0
        },
        {
        1.0, 0.0, 1.0
        },
        {
        0.0, 0.0, 1.0
        },
    },
    {
        {
        1.0, 0.0, 1.0
        },
        {
        1.0, -1.0, 1.0
        },
        {
        0.0, -1.0, 1.0
        },
        {
        0.0, 0.0, 1.0
        },
    },
    {
        {
        0.0, -1.0, 1.0
        },
        {
        -1.0, -1.0, 1.0
        },
        {
        -1.0, 0.0, 1.0
        },
        {
        0.0, 0.0, 1.0
        },
    },
    },
    {
    {
        {
        -1.0, 1.0, -1.0
        },
        {
        -1.0, 1.0, 0.0
        },
        {
        -1.0, 0.0, 0.0
        },
        {
        -1.0, 0.0, -1.0
        },
    },
    {
        {
        -1.0, 1.0, 0.0
        },
        {
        -1.0, 1.0, 1.0
        },
        {
        -1.0, 0.0, 1.0
        },
        {
        -1.0, 0.0, 0.0
        },
    },
    {
        {
        -1.0, 0.0, 1.0
        },
        {
        -1.0, -1.0, 1.0
        },
        {
        -1.0, -1.0, 0.0
        },
        {
        -1.0, 0.0, 0.0
        },
    },
    {
        {
        -1.0, -1.0, 0.0
        },
        {
        -1.0, -1.0, -1.0
        },
        {
        -1.0, 0.0, -1.0
        },
        {
        -1.0, 0.0, 0.0
        },
    },
    },
    {
    {
        {
        -1.0, 1.0, 1.0
        },
        {
        -1.0, 1.0, 0.0
        },
        {
        0.0, 1.0, 0.0
        },
        {
        0.0, 1.0, 1.0
        },
    },
    {
        {
        -1.0, 1.0, 0.0
        },
        {
        -1.0, 1.0, -1.0
        },
        {
        0.0, 1.0, -1.0
        },
        {
        0.0, 1.0, 0.0
        },
    },
    {
        {
        0.0, 1.0, -1.0
        },
        {
        1.0, 1.0, -1.0
        },
        {
        1.0, 1.0, 0.0
        },
        {
        0.0, 1.0, 0.0
        },
    },
    {
        {
        1.0, 1.0, 0.0
        },
        {
        1.0, 1.0, 1.0
        },
        {
        0.0, 1.0, 1.0
        },
        {
        0.0, 1.0, 0.0
        },
    },
    },
    {
    {
        {
        -1.0, -1.0, -1.0
        },
        {
        -1.0, -1.0, 0.0
        },
        {
        0.0, -1.0, 0.0
        },
        {
        0.0, -1.0, -1.0
        },
    },
    {
        {
        -1.0, -1.0, 0.0
        },
        {
        -1.0, -1.0, 1.0
        },
        {
        0.0, -1.0, 1.0
        },
        {
        0.0, -1.0, 0.0
        },
    },
    {
        {
        0.0, -1.0, 1.0
        },
        {
        1.0, -1.0, 1.0
        },
        {
        1.0, -1.0, 0.0
        },
        {
        0.0, -1.0, 0.0
        },
    },
    {
        {
        1.0, -1.0, 0.0
        },
        {
        1.0, -1.0, -1.0
        },
        {
        0.0, -1.0, -1.0
        },
        {
        0.0, -1.0, 0.0
        },
    },
    }
};

float n[6][3] = {
    {
    0.0, 0.0, -1.0
    },
    {
    1.0, 0.0, 0.0
    },
    {
    0.0, 0.0, 1.0
    },
    {
    -1.0, 0.0, 0.0
    },
    {
    0.0, 1.0, 0.0
    },
    {
    0.0, -1.0, 0.0
    }
};

GLfloat identity[16] = {
    1, 0, 0, 0,
    0, 1, 0, 0,
    0, 0, 1, 0,
    0, 0, 0, 1,
};

void BuildCylinder(int numEdges)
{
    int i, top = 1.0, bottom = -1.0;
    float x[100], y[100], angle;

    for (i = 0; i <= numEdges; i++) {
    angle = i * 2.0 * PI / numEdges;
    x[i] = cosf(angle);
    y[i] = sinf(angle);
    }

    glNewList(cylinder, GL_COMPILE);
    glBegin(GL_TRIANGLE_STRIP);
    for (i = 0; i <= numEdges; i++) {
        glNormal3f(x[i], y[i], 0.0);
        glVertex3f(x[i], y[i], bottom);
        glVertex3f(x[i], y[i], top);
    }
    glEnd();
    glBegin(GL_TRIANGLE_FAN);
    glNormal3f(0.0, 0.0, 1.0);
    glVertex3f(0.0, 0.0, top);
    for (i = 0; i <= numEdges; i++) {
        glVertex3f(x[i], -y[i], top);
    }
    glEnd();
    glBegin(GL_TRIANGLE_FAN);
    glNormal3f(0.0, 0.0, -1.0);
    glVertex3f(0.0, 0.0, bottom);
    for (i = 0; i <= numEdges; i++) {
        glVertex3f(x[i], y[i], bottom);
    }
    glEnd();
    glEndList();
}

void BuildTorus(float rc, int numc, float rt, int numt)
{
    int i, j, k;
    double s, t;
    double x, y, z;
    double pi, twopi;

    pi = 3.14159265358979323846;
    twopi = 2.0 * pi;

    glNewList(torus, GL_COMPILE);
    for (i = 0; i < numc; i++) {
    glBegin(GL_QUAD_STRIP);
        for (j = 0; j <= numt; j++) {
        for (k = 0; k <= 1; k++) {
        s = (i + k) % numc + 0.5;
        t = j % numt;

        x = cos(t*twopi/numt) * cos(s*twopi/numc);
        y = sin(t*twopi/numt) * cos(s*twopi/numc);
        z = sin(s*twopi/numc);
        glNormal3f(x, y, z);

        x = (rt + rc * cos(s*twopi/numc)) * cos(t*twopi/numt);
        y = (rt + rc * cos(s*twopi/numc)) * sin(t*twopi/numt);
        z = rc * sin(s*twopi/numc);
        glVertex3f(x, y, z);
        }
        }
    glEnd();
    }
    glEndList();
}

void BuildCage(void)
{
    int i, j;
    float inc;
    float right, left, top, bottom, front, back;

    front  = 0.0;
    back   = -8.0;

    left   = -4.0;
    bottom = -4.0;
    right  = 4.0;
    top    = 4.0;

    inc = 2.0 * 4.0 * 0.1;

    glNewList(cage, GL_COMPILE);
    for (i = 0; i < 10; i++) {

    /*
    ** Back
    */
    glBegin(GL_LINES);
        glVertex3f(left+i*inc, top,    back);
        glVertex3f(left+i*inc, bottom, back);
    glEnd();
    glBegin(GL_LINES);
        glVertex3f(right, bottom+i*inc, back);
        glVertex3f(left,  bottom+i*inc, back);
    glEnd();

    /*
    ** Front
    */
    glBegin(GL_LINES);
        glVertex3f(left+i*inc, top,    front);
        glVertex3f(left+i*inc, bottom, front);
    glEnd();
    glBegin(GL_LINES);
        glVertex3f(right, bottom+i*inc, front);
        glVertex3f(left,  bottom+i*inc, front);
    glEnd();

    /*
    ** Left
    */
    glBegin(GL_LINES);
        glVertex3f(left, bottom+i*inc, front);
        glVertex3f(left, bottom+i*inc, back);
    glEnd();
    glBegin(GL_LINES);
        glVertex3f(left, top,    back+i*inc);
        glVertex3f(left, bottom, back+i*inc);
    glEnd();

    /*
    ** Right
    */
    glBegin(GL_LINES);
        glVertex3f(right, top-i*inc, front);
        glVertex3f(right, top-i*inc, back);
    glEnd();
    glBegin(GL_LINES);
        glVertex3f(right, top,    back+i*inc);
        glVertex3f(right, bottom, back+i*inc);
    glEnd();

    /*
    ** Top
    */
    glBegin(GL_LINES);
        glVertex3f(left+i*inc, top, front);
        glVertex3f(left+i*inc, top, back);
    glEnd();
    glBegin(GL_LINES);
        glVertex3f(right, top, back+i*inc);
        glVertex3f(left,  top, back+i*inc);
    glEnd();

    /*
    ** Bottom
    */
    glBegin(GL_LINES);
        glVertex3f(right-i*inc, bottom, front);
        glVertex3f(right-i*inc, bottom, back);
    glEnd();
    glBegin(GL_LINES);
        glVertex3f(right, bottom, back+i*inc);
        glVertex3f(left,  bottom, back+i*inc);
    glEnd();
    }
    glEndList();
}

void BuildCube(void)
{
    int i, j;

    glNewList(cube, GL_COMPILE);
    for (i = 0; i < 6; i++) {
    for (j = 0; j < 4; j++) {
        glNormal3fv(n[i]);
        glBegin(GL_POLYGON);
        glVertex3fv(c[i][j][0]);
        glVertex3fv(c[i][j][1]);
        glVertex3fv(c[i][j][2]);
        glVertex3fv(c[i][j][3]);
        glEnd();
    }
    }
    glEndList();
}

void BuildLists(void)
{

    cube = glGenLists(1);
    BuildCube();

    cage = glGenLists(2);
    BuildCage();

    cylinder = glGenLists(3);
    BuildCylinder(60);

    torus = glGenLists(4);
    BuildTorus(0.65, 20, .85, 65);

    genericObject = torus;
}

void SetDeepestColor(void)
{
    GLint redBits, greenBits, blueBits;

    glGetIntegerv(GL_RED_BITS, &redBits);
    glGetIntegerv(GL_GREEN_BITS, &greenBits);
    glGetIntegerv(GL_BLUE_BITS, &blueBits);

    deepestColor = (redBits >= greenBits) ? GLUT_RED : GLUT_GREEN;
    deepestColor = (deepestColor >= blueBits) ? deepestColor : GLUT_BLUE;
}

void SetDefaultSettings(void)
{

    magFilter = nearest;
    minFilter = nearest;
    sWrapMode = repeat;
    tWrapMode = repeat;
    textureEnvironment = modulate;
    autoRotate = TRUE;
}

unsigned char *AlphaPadImage(int bufSize, unsigned char *inData, int alpha)
{
    unsigned char *outData, *out_ptr, *in_ptr;
    int i;

    outData = malloc(bufSize * 4);
    out_ptr = outData;
    in_ptr = inData;

    for (i = 0; i < bufSize; i++) {
    *out_ptr++ = *in_ptr++;
    *out_ptr++ = *in_ptr++;
    *out_ptr++ = *in_ptr++;
    *out_ptr++ = alpha;
    }

    free (inData);
    return outData;
}

void Init(void)
{
    float ambient[] = {0.0, 0.0, 0.0, 1.0};
    float diffuse[] = {0.0, 1.0, 0.0, 1.0};
    float specular[] = {1.0, 1.0, 1.0, 1.0};
    float position[] = {2.0, 2.0,  0.0, 1.0};
    float fog_color[] = {0.0, 0.0, 0.0, 1.0};
    float mat_ambient[] = {0.0, 0.0, 0.0, 1.0};
    float mat_shininess[] = {90.0};
    float mat_specular[] = {1.0, 1.0, 1.0, 1.0};
    float mat_diffuse[] = {1.0, 1.0, 1.0, 1.0};
    float lmodel_ambient[] = {0.0, 0.0, 0.0, 1.0};
    float lmodel_twoside[] = {GL_TRUE};
    float lmodel_local_viewer[] = {GL_FALSE};

    SetDeepestColor();
    SetDefaultSettings();

    if (numComponents == 4) {
    image->data = AlphaPadImage(image->sizeX*image->sizeY,
                                    image->data, 128);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    gluBuild2DMipmaps(GL_TEXTURE_2D, numComponents,
              image->sizeX, image->sizeY,
              GL_RGBA, GL_UNSIGNED_BYTE, image->data);
    } else {
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    gluBuild2DMipmaps(GL_TEXTURE_2D, numComponents,
              image->sizeX, image->sizeY,
              GL_RGB, GL_UNSIGNED_BYTE, image->data);
    }

    glFogf(GL_FOG_DENSITY, 0.125);
    glFogi(GL_FOG_MODE, GL_LINEAR);
    glFogf(GL_FOG_START, 4.0);
    glFogf(GL_FOG_END, 9.0);
    glFogfv(GL_FOG_COLOR, fog_color);

    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
    glLightfv(GL_LIGHT0, GL_SPECULAR, specular);
    glLightfv(GL_LIGHT0, GL_POSITION, position);

    glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, mat_shininess);
    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mat_specular);
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, mat_diffuse);
    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, mat_ambient);

    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
    glLightModelfv(GL_LIGHT_MODEL_TWO_SIDE, lmodel_twoside);
    glShadeModel(GL_SMOOTH);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    glClearColor(0.0, 0.0, 0.0, 0.0);
    glViewport(0, 0, W, H);
    glEnable(GL_DEPTH_TEST);

    glFrontFace(GL_CW);
    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK);

    glEnable(GL_TEXTURE_2D);
    glTexGeniv(GL_S, GL_TEXTURE_GEN_MODE, sphereMap);
    glTexGeniv(GL_T, GL_TEXTURE_GEN_MODE, sphereMap);
    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_GEN_T);

    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, sWrapMode);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, tWrapMode);

    glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, textureEnvironment);

    BuildLists();
}

void ReInit(void)
{

    if (genericObject == torus) {
    glEnable(GL_DEPTH_TEST);
    } else  {
    glDisable(GL_DEPTH_TEST);
    }
    if (isFogged) {
    textureEnvironment = modulate;
    }

    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
    glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, textureEnvironment);
}

void Draw(void)
{

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum(-0.2, 0.2, -0.2, 0.2, 0.15, 9.0);
    glMatrixMode(GL_MODELVIEW);

    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    if (isFogged) {
    glEnable(GL_FOG);
    }
    glColor3f(1.0, 1.0, 1.0);
    glDisable(GL_LIGHTING);
    glDisable(GL_LIGHT0);
    glDisable(GL_TEXTURE_2D);
    glCallList(cage);

    glPushMatrix();
    glTranslatef(0.0, 0.0, zTranslate);
    glRotatef(xRotation, 1, 0, 0);
    glRotatef(yRotation, 0, 1, 0);

    if (isLit == TRUE) {
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    }

    glEnable(GL_TEXTURE_2D);
    if (isFogged) {
    glDisable(GL_FOG);
    }
    glPolygonMode(GL_FRONT, GL_FILL);
    glColor3f(1.0, 1.0, 1.0);
    glCallList(genericObject);

    glPopMatrix();

    if (autoRotate) {
    xRotation += .75;
    yRotation += .375;
    }

    if (doubleBuffer) {
    glutSwapBuffers();
    } else {
    glFlush();
    }
}

void Reshape(int width, int height)
{

    W = width;
    H = height;
    ReInit();
}

void Key(unsigned char key, int x, int y)
{

    switch (key) {
      case 'a':
    autoRotate = !autoRotate;
    ReInit();
    glutPostRedisplay();
    break;
      case 'c':
    genericObject = (genericObject == cube) ? cylinder : cube;
    ReInit();
    glutPostRedisplay();
    break;
      case 'd':
    textureEnvironment = decal;
    ReInit();
    glutPostRedisplay();
    break;
      case 'm':
    textureEnvironment = modulate;
    ReInit();
    glutPostRedisplay();
    break;
      case 'l':
    isLit = !isLit;
    ReInit();
    glutPostRedisplay();
    break;
      case 'f':
    isFogged = !isFogged;
    ReInit();
    glutPostRedisplay();
    break;
      case 't':
    genericObject = torus;
    ReInit();
    glutPostRedisplay();
    break;
      case '0':
    magFilter = nearest;
    ReInit();
    glutPostRedisplay();
    break;
      case '1':
    magFilter = linear;
    ReInit();
    glutPostRedisplay();
    break;
      case '2':
    minFilter = nearest;
    ReInit();
    glutPostRedisplay();
    break;
      case '3':
    minFilter = linear;
    ReInit();
    glutPostRedisplay();
    break;
      case '4':
    minFilter = nearest_mipmap_nearest;
    ReInit();
    glutPostRedisplay();
    break;
      case '5':
    minFilter = nearest_mipmap_linear;
    ReInit();
    glutPostRedisplay();
    break;
      case '6':
    minFilter = linear_mipmap_nearest;
    ReInit();
    glutPostRedisplay();
    break;
      case '7':
    minFilter = linear_mipmap_linear;
    ReInit();
    glutPostRedisplay();
    break;
      case 27:
    free(image->data);
    exit(0);
    }
}

void SpecialKey(int key, int x, int y)
{

    switch (key) {
      case GLUT_KEY_LEFT:
    yRotation -= 0.5;
    autoRotate = FALSE;
    ReInit();
    glutPostRedisplay();
    break;
      case GLUT_KEY_RIGHT:
    yRotation += 0.5;
    autoRotate = FALSE;
    glutPostRedisplay();
    ReInit();
    break;
      case GLUT_KEY_UP:
    xRotation -= 0.5;
    autoRotate = FALSE;
    ReInit();
    glutPostRedisplay();
    break;
      case GLUT_KEY_DOWN:
    xRotation += 0.5;
    autoRotate = FALSE;
    ReInit();
    glutPostRedisplay();
    break;
    }
}

void Args(int argc, char **argv)
{
    GLint i;

    doubleBuffer = GL_FALSE;
    numComponents = 4;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    } else if (strcmp(argv[i], "-f") == 0) {
        if (i+1 >= argc || argv[i+1][0] == '-') {
        printf("-f (No file name).\n");
        exit(1);
        } else {
        image = rgbImageLoad(argv[++i]);
        if (image == NULL) {
            printf("-f (Bad file name).\n");
            exit(1);
        }
        }
    } else if (strcmp(argv[i], "-4") == 0) {
        numComponents = 4;
    } else if (strcmp(argv[i], "-3") == 0) {
        numComponents = 3;
    }
    }
}

int main(int argc, char **argv)
{
    GLenum type;

    glutInit(&argc, argv);
    Args(argc, argv);

    if (image == NULL) {
    printf("No texture file.\n");
    exit(1);
    }

    type = GLUT_RGB | GLUT_DEPTH;
    type |= (doubleBuffer) ? GLUT_DOUBLE : GLUT_SINGLE;
    glutInitDisplayMode(type);
    glutInitWindowSize(W, H);
    glutCreateWindow("Texture Test");

    Init();

    glutReshapeFunc(Reshape);
    glutKeyboardFunc(Key);
    glutSpecialFunc(SpecialKey);
    glutDisplayFunc(Draw);
    glutIdleFunc(Draw);

    glutMainLoop();
}

</pre>
</body>
</html>


Strech.c

stretch.C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <malloc.h>
#include <fcntl.h>
#include <math.h>
#include <GL/glut.h>
#include "rgb.h"

#ifdef WIN32
#define powf pow
#define logf log
#endif

#define STEPCOUNT 40
#define FALSE 0
#define TRUE 1
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#define MIN(a, b) (((a) < (b)) ? (a) : (b))

enum {
    OP_NOOP = 0,
    OP_STRETCH,
    OP_DRAWPOINT,
    OP_DRAWIMAGE
};

typedef struct _cRec {
    float x, y;
} cRec;

typedef struct _vertexRec {
    float x, y;
    float dX, dY;
    float tX, tY;
} vertexRec;

GLenum doubleBuffer;
int imageSizeX, imageSizeY;
RGBImageRec *image = NULL;
cRec cList[50];
vertexRec vList[5];
int cCount, cIndex[2], cStep;
GLenum op = OP_NOOP;

void DrawImage(void)
{

    glRasterPos2i(0, 0);
    glDrawPixels(image->sizeX, image->sizeY, GL_RGB, GL_UNSIGNED_BYTE,
         image->data);

    if (doubleBuffer) {
    glutSwapBuffers();
    } else {
    glFlush();
    }

    glRasterPos2i(0, 0);
    glDrawPixels(image->sizeX, image->sizeY, GL_RGB, GL_UNSIGNED_BYTE,
         image->data);
}

void DrawPoint(void)
{
    int i;

    glColor3f(1.0, 0.0, 1.0);
    glPointSize(3.0);
    glBegin(GL_POINTS);
    for (i = 0; i < cCount; i++) {
        glVertex2f(cList[i].x, cList[i].y);
    }
    glEnd();

    if (doubleBuffer) {
    glutSwapBuffers();
    } else {
    glFlush();
    }
}

void InitVList(void)
{

    vList[0].x = 0.0;
    vList[0].y = 0.0;
    vList[0].dX = 0.0;
    vList[0].dY = 0.0;
    vList[0].tX = 0.0;
    vList[0].tY = 0.0;

    vList[1].x = (float)imageSizeX;
    vList[1].y = 0.0;
    vList[1].dX = 0.0;
    vList[1].dY = 0.0;
    vList[1].tX = 1.0;
    vList[1].tY = 0.0;

    vList[2].x = (float)imageSizeX;
    vList[2].y = (float)imageSizeY;
    vList[2].dX = 0.0;
    vList[2].dY = 0.0;
    vList[2].tX = 1.0;
    vList[2].tY = 1.0;

    vList[3].x = 0.0;
    vList[3].y = (float)imageSizeY;
    vList[3].dX = 0.0;
    vList[3].dY = 0.0;
    vList[3].tX = 0.0;
    vList[3].tY = 1.0;

    vList[4].x = cList[0].x;
    vList[4].y = cList[0].y;
    vList[4].dX = (cList[1].x - cList[0].x) / STEPCOUNT;
    vList[4].dY = (cList[1].y - cList[0].y) / STEPCOUNT;
    vList[4].tX = cList[0].x / (float)imageSizeX;
    vList[4].tY = cList[0].y / (float)imageSizeY;
}

void ScaleImage(int sizeX, int sizeY)
{
    GLubyte *buf;

    buf = (GLubyte *)malloc(3*sizeX*sizeY);
    gluScaleImage(GL_RGB, image->sizeX, image->sizeY, GL_UNSIGNED_BYTE,
                  image->data, sizeX, sizeY, GL_UNSIGNED_BYTE, buf);
    free(image->data);
    image->data = buf;
    image->sizeX = sizeX;
    image->sizeY = sizeY;
}

void SetPoint(int x, int y)
{

    cList[cCount].x = (float)x;
    cList[cCount].y = (float)y;
    cCount++;
}

void Stretch(void)
{

    glBegin(GL_TRIANGLES);
    glTexCoord2f(vList[0].tX, vList[0].tY);
    glVertex2f(vList[0].x, vList[0].y);
    glTexCoord2f(vList[1].tX, vList[1].tY);
    glVertex2f(vList[1].x, vList[1].y);
    glTexCoord2f(vList[4].tX, vList[4].tY);
    glVertex2f(vList[4].x, vList[4].y);
    glEnd();

    glBegin(GL_TRIANGLES);
    glTexCoord2f(vList[1].tX, vList[1].tY);
    glVertex2f(vList[1].x, vList[1].y);
    glTexCoord2f(vList[2].tX, vList[2].tY);
    glVertex2f(vList[2].x, vList[2].y);
    glTexCoord2f(vList[4].tX, vList[4].tY);
    glVertex2f(vList[4].x, vList[4].y);
    glEnd();

    glBegin(GL_TRIANGLES);
    glTexCoord2f(vList[2].tX, vList[2].tY);
    glVertex2f(vList[2].x, vList[2].y);
    glTexCoord2f(vList[3].tX, vList[3].tY);
    glVertex2f(vList[3].x, vList[3].y);
    glTexCoord2f(vList[4].tX, vList[4].tY);
    glVertex2f(vList[4].x, vList[4].y);
    glEnd();

    glBegin(GL_TRIANGLES);
    glTexCoord2f(vList[3].tX, vList[3].tY);
    glVertex2f(vList[3].x, vList[3].y);
    glTexCoord2f(vList[0].tX, vList[0].tY);
    glVertex2f(vList[0].x, vList[0].y);
    glTexCoord2f(vList[4].tX, vList[4].tY);
    glVertex2f(vList[4].x, vList[4].y);
    glEnd();

    if (doubleBuffer) {
    glutSwapBuffers();
    } else {
    glFlush();
    }

    if (++cStep < STEPCOUNT) {
    vList[4].x += vList[4].dX;
    vList[4].y += vList[4].dY;
    } else {
    cIndex[0] = cIndex[1];
    cIndex[1] = cIndex[1] + 1;
    if (cIndex[1] == cCount) {
        cIndex[1] = 0;
    }
    vList[4].dX = (cList[cIndex[1]].x - cList[cIndex[0]].x) / STEPCOUNT;
    vList[4].dY = (cList[cIndex[1]].y - cList[cIndex[0]].y) / STEPCOUNT;
    cStep = 0;
    }
}

void Key(unsigned char key, int x, int y)
{

    switch (key) {
      case ' ':
    if (cCount > 1) {
        InitVList();
        cIndex[0] = 0;
        cIndex[1] = 1;
        cStep = 0;
        glEnable(GL_TEXTURE_2D);
        op = OP_STRETCH;
    }
    glutPostRedisplay();
    break;
      case 27:
    free(image->data);
        exit(0);
    }
}

void Mouse(int button, int state, int mouseX, int mouseY)
{

    if (state == GLUT_DOWN) {
    if (op == OP_STRETCH) {
        glDisable(GL_TEXTURE_2D);
        cCount = 0;
        op = OP_DRAWIMAGE;
    } else {
        SetPoint(mouseX, imageSizeY-mouseY);
        op = OP_DRAWPOINT;
    }
    glutPostRedisplay();
    }
}

void Animate(void)
{

    switch (op) {
      case OP_STRETCH:
    Stretch();
    break;
      case OP_DRAWPOINT:
    DrawPoint();
    break;
      case OP_DRAWIMAGE:
    DrawImage();
    break;
    }
}

static void Args(int argc, char **argv)
{
    GLint i;

    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    } else if (strcmp(argv[i], "-f") == 0) {
        if (i+1 >= argc || argv[i+1][0] == '-') {
        printf("-f (No file name).\n");
        exit(1);
        } else {
        image = rgbImageLoad(argv[++i]);
        if (image == NULL) {
            printf("-f (Bad file name).\n");
            exit(1);
        }
        }
    }
    }
}

int main(int argc, char **argv)
{
    GLenum type;

    glutInit(&argc, argv);
    Args(argc, argv);

    if (image == NULL) {
    printf("No texture file.\n");
    exit(1);
    }

    imageSizeX = (int)powf(2.0, (float)((int)(logf(image->sizeX)/logf(2.0))));
    imageSizeY = (int)powf(2.0, (float)((int)(logf(image->sizeY)/logf(2.0))));

    type = GLUT_RGB;
    type |= (doubleBuffer) ? GLUT_DOUBLE : GLUT_SINGLE;
    glutInitDisplayMode(type);
    glutInitWindowSize(imageSizeX, imageSizeY);
    glutCreateWindow("Stretch");

    glViewport(0, 0, imageSizeX, imageSizeY);
    gluOrtho2D(0, imageSizeX, 0, imageSizeY);
    glClearColor(0.0, 0.0, 0.0, 0.0);

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glPixelStorei(GL_PACK_ALIGNMENT, 1);

    ScaleImage(imageSizeX, imageSizeY);

    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexImage2D(GL_TEXTURE_2D, 0, 3, image->sizeX, image->sizeY, 0,
                 GL_RGB, GL_UNSIGNED_BYTE, (unsigned char *)image->data);

    cCount = 0;
    cIndex[0] = 0;
    cIndex[1] = 0;
    cStep = 0;
    op = OP_DRAWIMAGE;

    glutKeyboardFunc(Key);
    glutMouseFunc(Mouse);
    glutIdleFunc(Animate);
    glutDisplayFunc(Animate);
    glutMainLoop();
}

</pre>
</body>
</html>


teapot.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <GL/glut.h>

GLenum doubleBuffer;

float rotX = 0.0, rotY = 0.0;
int teaList;

long patchData[][16] = {
    {102,103,104,105,4,5,6,7,8,9,10,11,12,13,14,15},
    {12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27},
    {24,25,26,27,29,30,31,32,33,34,35,36,37,38,39,40},
    {96,96,96,96,97,98,99,100,101,101,101,101,0,1,2,3,},
    {0,1,2,3,106,107,108,109,110,111,112,113,114,115,116,117},
    {118,118,118,118,124,122,119,121,123,126,125,120,40,39,38,37},
    {41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56},
    {53,54,55,56,57,58,59,60,61,62,63,64,28,65,66,67},
    {68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83},
    {80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95}
};

float cpData[][3] = {
    {0.2,0,2.7},{0.2,-0.112,2.7},{0.112,-0.2,2.7},{0,-0.2,2.7},
    {1.3375,0,2.53125},{1.3375,-0.749,2.53125},{0.749,-1.3375,2.53125},
    {0,-1.3375,2.53125},{1.4375,0,2.53125},{1.4375,-0.805,2.53125},
    {0.805,-1.4375,2.53125},{0,-1.4375,2.53125},{1.5,0,2.4},{1.5,-0.84,2.4},
    {0.84,-1.5,2.4},{0,-1.5,2.4},{1.75,0,1.875},{1.75,-0.98,1.875},
    {0.98,-1.75,1.875},{0,-1.75,1.875},{2,0,1.35},{2,-1.12,1.35},
    {1.12,-2,1.35},{0,-2,1.35},{2,0,0.9},{2,-1.12,0.9},{1.12,-2,0.9},
    {0,-2,0.9},{-2,0,0.9},{2,0,0.45},{2,-1.12,0.45},{1.12,-2,0.45},
    {0,-2,0.45},{1.5,0,0.225},{1.5,-0.84,0.225},{0.84,-1.5,0.225},
    {0,-1.5,0.225},{1.5,0,0.15},{1.5,-0.84,0.15},{0.84,-1.5,0.15},
    {0,-1.5,0.15},{-1.6,0,2.025},{-1.6,-0.3,2.025},{-1.5,-0.3,2.25},
    {-1.5,0,2.25},{-2.3,0,2.025},{-2.3,-0.3,2.025},{-2.5,-0.3,2.25},
    {-2.5,0,2.25},{-2.7,0,2.025},{-2.7,-0.3,2.025},{-3,-0.3,2.25},
    {-3,0,2.25},{-2.7,0,1.8},{-2.7,-0.3,1.8},{-3,-0.3,1.8},{-3,0,1.8},
    {-2.7,0,1.575},{-2.7,-0.3,1.575},{-3,-0.3,1.35},{-3,0,1.35},
    {-2.5,0,1.125},{-2.5,-0.3,1.125},{-2.65,-0.3,0.9375},{-2.65,0,0.9375},
    {-2,-0.3,0.9},{-1.9,-0.3,0.6},{-1.9,0,0.6},{1.7,0,1.425},
    {1.7,-0.66,1.425},{1.7,-0.66,0.6},{1.7,0,0.6},{2.6,0,1.425},
    {2.6,-0.66,1.425},{3.1,-0.66,0.825},{3.1,0,0.825},{2.3,0,2.1},
    {2.3,-0.25,2.1},{2.4,-0.25,2.025},{2.4,0,2.025},{2.7,0,2.4},
    {2.7,-0.25,2.4},{3.3,-0.25,2.4},{3.3,0,2.4},{2.8,0,2.475},
    {2.8,-0.25,2.475},{3.525,-0.25,2.49375},{3.525,0,2.49375},
    {2.9,0,2.475},{2.9,-0.15,2.475},{3.45,-0.15,2.5125},{3.45,0,2.5125},
    {2.8,0,2.4},{2.8,-0.15,2.4},{3.2,-0.15,2.4},{3.2,0,2.4},{0,0,3.15},
    {0.8,0,3.15},{0.8,-0.45,3.15},{0.45,-0.8,3.15},{0,-0.8,3.15},
    {0,0,2.85},{1.4,0,2.4},{1.4,-0.784,2.4},{0.784,-1.4,2.4},{0,-1.4,2.4},
    {0.4,0,2.55},{0.4,-0.224,2.55},{0.224,-0.4,2.55},{0,-0.4,2.55},
    {1.3,0,2.55},{1.3,-0.728,2.55},{0.728,-1.3,2.55},{0,-1.3,2.55},
    {1.3,0,2.4},{1.3,-0.728,2.4},{0.728,-1.3,2.4},{0,-1.3,2.4},{0,0,0},
    {1.425,-0.798,0},{1.5,0,0.075},{1.425,0,0},{0.798,-1.425,0},
    {0,-1.5,0.075},{0,-1.425,0},{1.5,-0.84,0.075},{0.84,-1.5,0.075}
};

void Teapot(long grid)
{
    float p[4][4][3], q[4][4][3], r[4][4][3], s[4][4][3];
    long i, j, k, l;

    teaList = 1;
    glNewList(teaList, GL_COMPILE);
    glPushMatrix();
    glRotatef(270.0, 1.0, 0.0, 0.0);
    for (i = 0; i < 10; i++) {
    for (j = 0; j < 4; j++) {
        for (k = 0; k < 4; k++) {
        for (l = 0; l < 3; l++) {
            p[j][k][l] = cpData[patchData[i][j*4+k]][l];
            q[j][k][l] = cpData[patchData[i][j*4+(3-k)]][l];
            if (l == 1) {
            q[j][k][l] *= -1.0;
            }
            if (i < 6) {
            r[j][k][l] = cpData[patchData[i][j*4+(3-k)]][l];
            if (l == 0) {
                r[j][k][l] *= -1.0;
            }
            s[j][k][l] = cpData[patchData[i][j*4+k]][l];
            if (l == 0) {
                s[j][k][l] *= -1.0;
            }
            if (l == 1) {
                s[j][k][l] *= -1.0;
            }
            }
        }
        }
    }
    glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, &p[0][0][0]);
    glEnable(GL_MAP2_VERTEX_3);
    glMapGrid2f(grid, 0.0, 1.0, grid, 0.0, 1.0);
    glEvalMesh2(GL_FILL, 0, grid, 0, grid);
    glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, &q[0][0][0]);
    glEvalMesh2(GL_FILL, 0, grid, 0, grid);
    if (i < 6) {
        glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, &r[0][0][0]);
        glEvalMesh2(GL_FILL, 0, grid, 0, grid);
        glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, &s[0][0][0]);
        glEvalMesh2(GL_FILL, 0, grid, 0, grid);
    }
    }
    glDisable(GL_MAP2_VERTEX_3);
    glPopMatrix();
    glEndList();
}

static void Init(void)
{
    float position[] = {0.0, 3.0, 3.0, 0.0};
    float local_view[] = {0.0};
    float ambient[] = {0.1745, 0.01175, 0.01175};
    float diffuse[] = {0.61424, 0.04136, 0.04136};
    float specular[] = {0.727811, 0.626959, 0.626959};

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);

    glLightfv(GL_LIGHT0, GL_POSITION, position);
    glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view);

    glFrontFace(GL_CW);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_AUTO_NORMAL);
    glEnable(GL_NORMALIZE);

    glMaterialfv(GL_FRONT, GL_AMBIENT, ambient);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, specular);
    glMaterialf(GL_FRONT, GL_SHININESS, 0.6*128.0);

    glClearColor(0.5, 0.5, 0.5, 1.0);
    glColor3f(1.0, 1.0, 1.0);

    Teapot(14);
}

static void Reshape(int w, int h)
{

    glViewport(0, 0, (GLint)w, (GLint)h);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-6.0, 6.0, -6.0, 6.0, -1.0, 10.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

static void Key(unsigned char key, int x, int y)
{

    switch (key) {
      case 27:
    exit(0);
    }
}

static void SpecialKey(int key, int x, int y)
{

    switch (key) {
      case GLUT_KEY_UP:
    rotX -= 20.0;
    glutPostRedisplay();
    break;
      case GLUT_KEY_DOWN:
    rotX += 20.0;
    glutPostRedisplay();
    break;
      case GLUT_KEY_LEFT:
    rotY -= 20.0;
    glutPostRedisplay();
    break;
      case GLUT_KEY_RIGHT:
    rotY += 20.0;
    glutPostRedisplay();
    break;
    }
}

static void Draw(void)
{

    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    glPushMatrix();

    glTranslatef(0.0, 0.0, -5.0);
    glRotatef(rotY, 0.0,1.0,0.0);
    glRotatef(rotX, 1.0,0.0,0.0);

    glCallList(teaList);

    glPopMatrix();

    if (doubleBuffer) {
    glutSwapBuffers();
    } else {
    glFlush();
    }
}

static void Args(int argc, char **argv)
{
    GLint i;

    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    }
    }
}

int main(int argc, char **argv)
{
    GLenum type;

    glutInit(&argc, argv);
    Args(argc, argv);

    type = GLUT_RGB | GLUT_DEPTH;
    type |= (doubleBuffer) ? GLUT_DOUBLE : GLUT_SINGLE;
    glutInitDisplayMode(type);
    glutInitWindowSize(300, 300);
    glutCreateWindow("TeaPot");

    Init();

    glutReshapeFunc(Reshape);
    glutKeyboardFunc(Key);
    glutSpecialFunc(SpecialKey);
    glutDisplayFunc(Draw);
    glutMainLoop();
}


twotexture.C

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <math.h>
#include <stdlib.h>
#include <GL/glut.h>
#include "rgb.h"

GLenum doubleBuffer;
int winW = 300, winH = 300;

RGBImageRec *earthImage = NULL, *skyImage = NULL;
GLint skyList, earthList;

float *minFilter, *magFilter, *sWrapMode, *tWrapMode;
float decal[] = {GL_DECAL};
float modulate[] = {GL_MODULATE};
float repeat[] = {GL_REPEAT};
float clamp[] = {GL_CLAMP};
float nr[] = {GL_NEAREST};
float ln[] = {GL_LINEAR};
float nr_mipmap_nr[] = {GL_NEAREST_MIPMAP_NEAREST};
float nr_mipmap_ln[] = {GL_NEAREST_MIPMAP_LINEAR};
float ln_mipmap_nr[] = {GL_LINEAR_MIPMAP_NEAREST};
float ln_mipmap_ln[] = {GL_LINEAR_MIPMAP_LINEAR};

int horizon;
float texMinX, texMinY, texMaxX, texMaxY;

static void Init(void)
{

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    magFilter = nr;
    minFilter = nr;
    sWrapMode = repeat;
    tWrapMode = repeat;

    glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, decal);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, sWrapMode);
    glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, tWrapMode);
    glEnable(GL_TEXTURE_2D);

    glClearColor(0.0, 0.0, 0.0, 0.0);

    horizon = winH / 2;

    texMinX = 0.25;
    texMaxX = 0.75;

    texMinY = 0.25;
    texMaxY = 0.75;

    skyList = glGenLists(1);
    glNewList(skyList, GL_COMPILE);
    gluBuild2DMipmaps(GL_TEXTURE_2D, 3, skyImage->sizeX, skyImage->sizeY,
              GL_RGB, GL_UNSIGNED_BYTE, skyImage->data);
    glEndList();

    earthList = glGenLists(1);
    glNewList(earthList, GL_COMPILE);
    gluBuild2DMipmaps(GL_TEXTURE_2D, 3, earthImage->sizeX, earthImage->sizeY,
              GL_RGB, GL_UNSIGNED_BYTE, earthImage->data);
    glEndList();
}

static void Reshape(int width, int height)
{

    winW = width;
    winH = height;

    glViewport(0, 0, winW, winH);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0, winW, 0, winH);
    glMatrixMode(GL_MODELVIEW);
}

static void Key(unsigned char key, int x, int y)
{

    switch (key) {
      case '1':
    horizon -= 5;
    texMinY -= 5.0 / (float)winH;
    texMinY += 5.0 / (float)winH;
    texMaxY += 5.0 / (float)winH;
    glutPostRedisplay();
    break;
      case '2':
    horizon += 5;
    texMinY += 5.0 / (float)winH;
    texMinY -= 5.0 / (float)winH;
    texMaxY -= 5.0 / (float)winH;
    glutPostRedisplay();
    break;
      case 27:
    exit(0);
    }
}

static void SpecialKey(int key, int x, int y)
{

    switch (key) {
      case GLUT_KEY_LEFT:
    texMinX -= 5.0 / (float)winW;
    texMaxX -= 5.0 / (float)winW;
    glutPostRedisplay();
    break;
      case GLUT_KEY_RIGHT:
    texMinX += 5.0 / (float)winW;
    texMaxX += 5.0 / (float)winW;
    glutPostRedisplay();
    break;
      case GLUT_KEY_UP:
    texMinY += 5.0 / (float)winH;
    texMaxY += 5.0 / (float)winH;
    glutPostRedisplay();
    break;
      case GLUT_KEY_DOWN:
    texMinY -= 5.0 / (float)winH;
    texMaxY -= 5.0 / (float)winH;
    glutPostRedisplay();
    break;
    }
}

static void Draw(void)
{

    glClear(GL_COLOR_BUFFER_BIT);

    glPushMatrix();

    glCallList(skyList);
    glBegin(GL_POLYGON);
    glTexCoord2f(texMinX, texMinY);
    glVertex2i(0, horizon);
    glTexCoord2f(texMaxX, texMinY);
    glVertex2i(winW, horizon);
    glTexCoord2f(texMaxX, texMaxY);
    glVertex2i(winW, winH);
    glTexCoord2f(texMinX, texMaxY);
    glVertex2i(0, winH);
    glEnd();

    glCallList(earthList);
    glBegin(GL_POLYGON);
    glTexCoord2f(0.0, 0.0);
    glVertex2i(0, 0);
    glTexCoord2f(1.0, 0.0);
    glVertex2i(winW, 0);
    glTexCoord2f(1.0, 1.0);
    glVertex2i(winW, horizon);
    glTexCoord2f(0.0, 1.0);
    glVertex2i(0, horizon);
    glEnd();

    glPopMatrix();

    if (doubleBuffer) {
    glutSwapBuffers();
    } else {
    glFlush();
    }
}

static void Args(int argc, char **argv)
{
    GLint i;

    doubleBuffer = GL_FALSE;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    } else if (strcmp(argv[i], "-earth") == 0) {
        if (i+1 >= argc || argv[i+1][0] == '-') {
        printf("-earth (No file name).\n");
        exit(1);
        } else {
        earthImage = rgbImageLoad(argv[++i]);
        if (earthImage == NULL) {
            printf("-earth (Bad file name).\n");
            exit(1);
        }
        }
    } else if (strcmp(argv[i], "-sky") == 0) {
        if (i+1 >= argc || argv[i+1][0] == '-') {
        printf("-sky (No file name).\n");
        exit(1);
        } else {
        skyImage = rgbImageLoad(argv[++i]);
        if (skyImage == NULL) {
            printf("-sky (Bad file name).\n");
            exit(1);
        }
        }
    }
    }
}

int main(int argc, char **argv)
{
    GLenum type;

    glutInit(&argc, argv);
    Args(argc, argv);

    if (earthImage == NULL) {
    printf("No earth texture file.\n");
    exit(1);
    }
    if (skyImage == NULL) {
    printf("No sky texture file.\n");
    exit(1);
    }

    type = GLUT_RGB;
    type |= (doubleBuffer) ? GLUT_DOUBLE : GLUT_SINGLE;
    glutInitDisplayMode(type);
    glutInitWindowSize(winW, winH);
    glutCreateWindow("Two Texture Test");

    Init();

    glutReshapeFunc(Reshape);
    glutKeyboardFunc(Key);
    glutSpecialFunc(SpecialKey);
    glutDisplayFunc(Draw);
    glutMainLoop();
}


wave.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <GL/glut.h>

#define PI 3.14159265358979323846

#define GETCOORD(frame, x, y) (&(theMesh.coords[frame*theMesh.numCoords+(x)+(y)*(theMesh.widthX+1)]))
#define GETFACET(frame, x, y) (&(theMesh.facets[frame*theMesh.numFacets+(x)+(y)*theMesh.widthX]))

GLenum rgb, doubleBuffer;

GLint colorIndexes1[3];
GLint colorIndexes2[3];
GLenum clearMask = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT;

GLenum smooth = GL_FALSE;
GLenum lighting = GL_TRUE;
GLenum depth = GL_TRUE;
GLenum stepMode = GL_FALSE;
GLenum spinMode = GL_FALSE;
GLint contouring = 0;

GLint widthX, widthY;
GLint checkerSize;
float height;

GLint frames, curFrame = 0, nextFrame = 0;

struct facet {
    float color[3];
    float normal[3];
};
struct coord {
    float vertex[3];
    float normal[3];
};
struct mesh {
    GLint widthX, widthY;
    GLint numFacets;
    GLint numCoords;
    GLint frames;
    struct coord *coords;
    struct facet *facets;
} theMesh;

GLubyte contourTexture1[] = {
    255, 255, 255, 255,
    255, 255, 255, 255,
    255, 255, 255, 255,
    127, 127, 127, 127,
};
GLubyte contourTexture2[] = {
    255, 255, 255, 255,
    255, 127, 127, 127,
    255, 127, 127, 127,
    255, 127, 127, 127,
};

static void Animate(void)
{
    struct coord *coord;
    struct facet *facet;
    float *lastColor;
    float *thisColor;
    GLint i, j;

    glClear(clearMask);

    if (nextFrame || !stepMode) {
    curFrame++;
    }
    if (curFrame >= theMesh.frames) {
    curFrame = 0;
    }

    if ((nextFrame || !stepMode) && spinMode) {
    glRotatef(5.0, 0.0, 0.0, 1.0);
    }
    nextFrame = 0;

    for (i = 0; i < theMesh.widthX; i++) {
    glBegin(GL_QUAD_STRIP);
    lastColor = NULL;
    for (j = 0; j < theMesh.widthY; j++) {
        facet = GETFACET(curFrame, i, j);
        if (!smooth && lighting) {
        glNormal3fv(facet->normal);
        }
        if (lighting) {
        if (rgb) {
            thisColor = facet->color;
            glColor3fv(facet->color);
        } else {
            thisColor = facet->color;
            glMaterialfv(GL_FRONT_AND_BACK, GL_COLOR_INDEXES,
                 facet->color);
        }
        } else {
        if (rgb) {
            thisColor = facet->color;
            glColor3fv(facet->color);
        } else {
            thisColor = facet->color;
            glIndexf(facet->color[1]);
        }
        }

        if (!lastColor || (thisColor[0] != lastColor[0] && smooth)) {
        if (lastColor) {
            glEnd();
            glBegin(GL_QUAD_STRIP);
        }
        coord = GETCOORD(curFrame, i, j);
        if (smooth && lighting) {
            glNormal3fv(coord->normal);
        }
        glVertex3fv(coord->vertex);

        coord = GETCOORD(curFrame, i+1, j);
        if (smooth && lighting) {
            glNormal3fv(coord->normal);
        }
        glVertex3fv(coord->vertex);
        }

        coord = GETCOORD(curFrame, i, j+1);
        if (smooth && lighting) {
        glNormal3fv(coord->normal);
        }
        glVertex3fv(coord->vertex);

        coord = GETCOORD(curFrame, i+1, j+1);
        if (smooth && lighting) {
        glNormal3fv(coord->normal);
        }
        glVertex3fv(coord->vertex);

        lastColor = thisColor;
    }
    glEnd();
    }

    if (doubleBuffer) {
    glutSwapBuffers();
    } else {
    glFlush();
    }
}

static void SetColorMap(void)
{
    static float green[3] = {0.2, 1.0, 0.2};
    static float red[3] = {1.0, 0.2, 0.2};
    float *color, percent;
    GLint *indexes, entries, i, j;

    entries = glutGet(GLUT_WINDOW_COLORMAP_SIZE);

    colorIndexes1[0] = 1;
    colorIndexes1[1] = 1 + (GLint)((entries - 1) * 0.3);
    colorIndexes1[2] = (GLint)((entries - 1) * 0.5);
    colorIndexes2[0] = 1 + (GLint)((entries - 1) * 0.5);
    colorIndexes2[1] = 1 + (GLint)((entries - 1) * 0.8);
    colorIndexes2[2] = entries - 1;

    for (i = 0; i < 2; i++) {
    switch (i) {
      case 0:
        color = green;
        indexes = colorIndexes1;
        break;
      case 1:
        color = red;
        indexes = colorIndexes2;
        break;
    }

    for (j = indexes[0]; j < indexes[1]; j++) {
        percent = 0.2 + 0.8 * (j - indexes[0]) /
              (float)(indexes[1] - indexes[0]);
        glutSetColor(j, percent*color[0], percent*color[1],
                 percent*color[2]);
    }
    for (j=indexes[1]; j<=indexes[2]; j++) {
        percent = (j - indexes[1]) / (float)(indexes[2] - indexes[1]);
        glutSetColor(j, percent*(1-color[0])+color[0],
             percent*(1-color[1])+color[1],
             percent*(1-color[2])+color[2]);
    }
    }
}

static void InitMesh(void)
{
    struct coord *coord;
    struct facet *facet;
    float dp1[3], dp2[3];
    float *pt1, *pt2, *pt3;
    float angle, d, x, y;
    GLint numFacets, numCoords, frameNum, i, j;

    theMesh.widthX = widthX;
    theMesh.widthY = widthY;
    theMesh.frames = frames;

    numFacets = widthX * widthY;
    numCoords = (widthX + 1) * (widthY + 1);

    theMesh.numCoords = numCoords;
    theMesh.numFacets = numFacets;

    theMesh.coords = (struct coord *)malloc(frames*numCoords*
                        sizeof(struct coord));
    theMesh.facets = (struct facet *)malloc(frames*numFacets*
                        sizeof(struct facet));
    if (theMesh.coords == NULL || theMesh.facets == NULL) {
    printf("Out of memory.\n");
    exit(0);
    }

    for (frameNum = 0; frameNum < frames; frameNum++) {
    for (i = 0; i <= widthX; i++) {
        x = i / (float)widthX;
        for (j = 0; j <= widthY; j++) {
        y = j / (float)widthY;

        d = sqrt(x*x+y*y);
        if (d == 0.0) {
            d = 0.0001;
        }
        angle = 2 * PI * d + (2 * PI / frames * frameNum);

        coord = GETCOORD(frameNum, i, j);

        coord->vertex[0] = x - 0.5;
        coord->vertex[1] = y - 0.5;
        coord->vertex[2] = (height - height * d) * cos(angle);

        coord->normal[0] = -(height / d) * x * ((1 - d) * 2 * PI *
                   sin(angle) + cos(angle));
        coord->normal[1] = -(height / d) * y * ((1 - d) * 2 * PI *
                   sin(angle) + cos(angle));
        coord->normal[2] = -1;

        d = 1.0 / sqrt(coord->normal[0]*coord->normal[0]+
                   coord->normal[1]*coord->normal[1]+1);
        coord->normal[0] *= d;
        coord->normal[1] *= d;
        coord->normal[2] *= d;
        }
    }
    for (i = 0; i < widthX; i++) {
        for (j = 0; j < widthY; j++) {
        facet = GETFACET(frameNum, i, j);
        if (((i/checkerSize)%2)^(j/checkerSize)%2) {
            if (rgb) {
            facet->color[0] = 1.0;
            facet->color[1] = 0.2;
            facet->color[2] = 0.2;
            } else {
            facet->color[0] = colorIndexes1[0];
            facet->color[1] = colorIndexes1[1];
            facet->color[2] = colorIndexes1[2];
            }
        } else {
            if (rgb) {
            facet->color[0] = 0.2;
            facet->color[1] = 1.0;
            facet->color[2] = 0.2;
            } else {
            facet->color[0] = colorIndexes2[0];
            facet->color[1] = colorIndexes2[1];
            facet->color[2] = colorIndexes2[2];
            }
        }
        pt1 = GETCOORD(frameNum, i, j)->vertex;
        pt2 = GETCOORD(frameNum, i, j+1)->vertex;
        pt3 = GETCOORD(frameNum, i+1, j+1)->vertex;

        dp1[0] = pt2[0] - pt1[0];
        dp1[1] = pt2[1] - pt1[1];
        dp1[2] = pt2[2] - pt1[2];

        dp2[0] = pt3[0] - pt2[0];
        dp2[1] = pt3[1] - pt2[1];
        dp2[2] = pt3[2] - pt2[2];

        facet->normal[0] = dp1[1] * dp2[2] - dp1[2] * dp2[1];
        facet->normal[1] = dp1[2] * dp2[0] - dp1[0] * dp2[2];
        facet->normal[2] = dp1[0] * dp2[1] - dp1[1] * dp2[0];

        d = 1.0 / sqrt(facet->normal[0]*facet->normal[0]+
                   facet->normal[1]*facet->normal[1]+
                   facet->normal[2]*facet->normal[2]);

        facet->normal[0] *= d;
        facet->normal[1] *= d;
        facet->normal[2] *= d;
        }
    }
    }
}

static void InitMaterials(void)
{
    static float ambient[] = {0.1, 0.1, 0.1, 1.0};
    static float diffuse[] = {0.5, 1.0, 1.0, 1.0};
    static float position[] = {90.0, 90.0, 150.0, 0.0};
    static float front_mat_shininess[] = {60.0};
    static float front_mat_specular[] = {0.2, 0.2, 0.2, 1.0};
    static float front_mat_diffuse[] = {0.5, 0.28, 0.38, 1.0};
    static float back_mat_shininess[] = {60.0};
    static float back_mat_specular[] = {0.5, 0.5, 0.2, 1.0};
    static float back_mat_diffuse[] = {1.0, 1.0, 0.2, 1.0};
    static float lmodel_ambient[] = {1.0, 1.0, 1.0, 1.0};
    static float lmodel_twoside[] = {GL_TRUE};

    glMatrixMode(GL_PROJECTION);
    gluPerspective(450, 1.0, 0.5, 10.0);

    glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
    glLightfv(GL_LIGHT0, GL_POSITION, position);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
    glLightModelfv(GL_LIGHT_MODEL_TWO_SIDE, lmodel_twoside);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    glMaterialfv(GL_FRONT, GL_SHININESS, front_mat_shininess);
    glMaterialfv(GL_FRONT, GL_SPECULAR, front_mat_specular);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, front_mat_diffuse);
    glMaterialfv(GL_BACK, GL_SHININESS, back_mat_shininess);
    glMaterialfv(GL_BACK, GL_SPECULAR, back_mat_specular);
    glMaterialfv(GL_BACK, GL_DIFFUSE, back_mat_diffuse);
    if (rgb) {
    glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);
    }

    if (rgb) {
    glEnable(GL_COLOR_MATERIAL);
    } else {
    SetColorMap();
    }
}

static void InitTexture(void)
{

    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
}

static void Init(void)
{

    glClearColor(0.0, 0.0, 0.0, 0.0);

    glShadeModel(GL_FLAT);

    glFrontFace(GL_CW);

    glEnable(GL_DEPTH_TEST);

    InitMaterials();
    InitTexture();
    InitMesh();

    glMatrixMode(GL_MODELVIEW);
    glTranslatef(0.0, 0.4, -1.8);
    glScalef(2.0, 2.0, 2.0);
    glRotatef(-35.0, 1.0, 0.0, 0.0);
    glRotatef(35.0, 0.0, 0.0, 1.0);
}

static void Reshape(int width, int height)
{

    glViewport(0, 0, width, height);
}

static void Key(unsigned char key, int x, int y)
{

    switch (key) {
      case 'c':
    contouring++;
    if (contouring == 1) {
        static GLfloat map[4] = {0, 0, 20, 0};

        glTexImage2D(GL_TEXTURE_2D, 0, 1, 4, 4, 0, GL_LUMINANCE,
             GL_UNSIGNED_BYTE, (GLvoid *)contourTexture1);
        glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
        glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
        glTexGenfv(GL_S, GL_OBJECT_PLANE, map);
        glTexGenfv(GL_T, GL_OBJECT_PLANE, map);
        glEnable(GL_TEXTURE_2D);
        glEnable(GL_TEXTURE_GEN_S);
        glEnable(GL_TEXTURE_GEN_T);
    } else if (contouring == 2) {
        static GLfloat map[4] = {0, 0, 20, 0};

        glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
        glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
        glPushMatrix();
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glTexGenfv(GL_S, GL_EYE_PLANE, map);
        glTexGenfv(GL_T, GL_EYE_PLANE, map);
        glPopMatrix();
    } else {
        contouring = 0;
        glDisable(GL_TEXTURE_GEN_S);
        glDisable(GL_TEXTURE_GEN_T);
        glDisable(GL_TEXTURE_2D);
    }
    break;
      case 's':
    smooth = !smooth;
    if (smooth) {
        glShadeModel(GL_SMOOTH);
    } else {
        glShadeModel(GL_FLAT);
    }
    break;
      case 'l':
    lighting = !lighting;
    if (lighting) {
        glEnable(GL_LIGHTING);
        glEnable(GL_LIGHT0);
        if (rgb) {
        glEnable(GL_COLOR_MATERIAL);
        }
    } else {
        glDisable(GL_LIGHTING);
        glDisable(GL_LIGHT0);
        if (rgb) {
        glDisable(GL_COLOR_MATERIAL);
        }
    }
    break;
      case 'd':
    depth = !depth;
    if (depth) {
        glEnable(GL_DEPTH_TEST);
        clearMask |= GL_DEPTH_BUFFER_BIT;
    } else {
        glDisable(GL_DEPTH_TEST);
        clearMask &= ~GL_DEPTH_BUFFER_BIT;
    }
    break;
      case ' ':
    stepMode = !stepMode;
    if (stepMode) {
        glutIdleFunc(0);
        glutDisplayFunc(Animate);
    } else {
        glutIdleFunc(Animate);
        glutDisplayFunc(0);
    }
    break;
      case 'n':
    if (stepMode) {
        nextFrame = 1;
    }
    break;
      case 'a':
    spinMode = !spinMode;
    break;
      case 27:
    exit(0);
    }
}

static void Args(int argc, char **argv)
{
    GLint i;

    rgb = GL_TRUE;
    doubleBuffer = GL_FALSE;
    frames = 10;
    widthX = 10;
    widthY = 10;
    checkerSize = 2;
    height = 0.2;

    for (i = 1; i < argc; i++) {
    if (strcmp(argv[i], "-ci") == 0) {
        rgb = GL_FALSE;
    } else if (strcmp(argv[i], "-rgb") == 0) {
        rgb = GL_TRUE;
    } else if (strcmp(argv[i], "-sb") == 0) {
        doubleBuffer = GL_FALSE;
    } else if (strcmp(argv[i], "-db") == 0) {
        doubleBuffer = GL_TRUE;
    } else if (strcmp(argv[i], "-grid") == 0) {
        if (i+2 >= argc || argv[i+1][0] == '-' || argv[i+2][0] == '-') {
        printf("-grid (No numbers).\n");
        } else {
        widthX = atoi(argv[++i]);
        widthY = atoi(argv[++i]);
        }
    } else if (strcmp(argv[i], "-size") == 0) {
        if (i+1 >= argc || argv[i+1][0] == '-') {
        printf("-checker (No number).\n");
        } else {
        checkerSize = atoi(argv[++i]);
        }
    } else if (strcmp(argv[i], "-wave") == 0) {
        if (i+1 >= argc || argv[i+1][0] == '-') {
        printf("-wave (No number).\n");
        } else {
        height = atof(argv[++i]);
        }
    } else if (strcmp(argv[i], "-frames") == 0) {
        if (i+1 >= argc || argv[i+1][0] == '-') {
        printf("-frames (No number).\n");
        } else {
        frames = atoi(argv[++i]);
        }
    }
    }
}

int main(int argc, char **argv)
{
    GLenum type;

    glutInit(&argc, argv);
    Args(argc, argv);

    type = GLUT_DEPTH;
    type |= (rgb) ? GLUT_RGB : GLUT_INDEX;
    type |= (doubleBuffer) ? GLUT_DOUBLE : GLUT_SINGLE;
    glutInitDisplayMode(type);
    glutInitWindowSize(300, 300);
    glutCreateWindow("Wave Demo");

    Init();

    glutReshapeFunc(Reshape);
    glutKeyboardFunc(Key);
    glutIdleFunc(Animate);
    glutDisplayFunc(Animate);
    glutMainLoop();
}


OpenGL Basics - Win32 Tutorial Sample Code

OpenGL Win32 Tutorial Sample Code

Code Samples released by SGI with the OpenGL 1.1 distribution in 1997. These are very useful for beginning OpenGL coding and learning OpenGL program structure. Advanced rendering and later extensions are not covered in these examples.

Download All Sample Code as a single Zip file

An example of the minimal Win32 & OpenGL program. It only works in 16 bit color modes or higher (since it doesn't create a palette).

Source code: minimal.c.

Snapshots: triangle (shown).

An example of an OpenGL animation loop using the Win32 API. Also demonstrates palette management for both RGB and Color Index modes and general strategies for message handling.

Source code: animate.c.

Snapshots: rgb mode (shown), color index mode.

An example of processing mouse events in an OpenGL program using the Win32 API.

Source code: mouse.c.

Snapshots: rotated (shown), rotated + translated + zoomed.

An example of using draw pixels and pixel zoom operations in OpenGL using the Win32 API.

Source code: image.c.

Snapshots: opengl (shown).

Simple example of rendering to a Windows DIB (Device Independent Bitmap).

Source code: simpledib.c.

Snapshots: torus (shown).

Simple program that enumerates all of the pixel formats available.

Source code: wglinfo.c.

Snapshots: small output (shown).


minimal.c

/* An example of the minimal Win32 & OpenGL program.  It only works in
   16 bit color modes or higher (since it doesn't create a
   palette). */

#include 			/* must include this before GL/gl.h */
#include 			/* OpenGL header file */
#include 			/* OpenGL utilities header file */
#include

void
display()
{
    /* rotate a triangle around */
    glClear(GL_COLOR_BUFFER_BIT);
    glBegin(GL_TRIANGLES);
    glColor3f(1.0f, 0.0f, 0.0f);
    glVertex2i(0,  1);
    glColor3f(0.0f, 1.0f, 0.0f);
    glVertex2i(-1, -1);
    glColor3f(0.0f, 0.0f, 1.0f);
    glVertex2i(1, -1);
    glEnd();
    glFlush();
}

LONG WINAPI
WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static PAINTSTRUCT ps;

    switch(uMsg) {
    case WM_PAINT:
    display();
    BeginPaint(hWnd, &ps);
    EndPaint(hWnd, &ps);
    return 0;

    case WM_SIZE:
    glViewport(0, 0, LOWORD(lParam), HIWORD(lParam));
    PostMessage(hWnd, WM_PAINT, 0, 0);
    return 0;

    case WM_CHAR:
    switch (wParam) {
    case 27:			/* ESC key */
        PostQuitMessage(0);
        break;
    }
    return 0;

    case WM_CLOSE:
    PostQuitMessage(0);
    return 0;
    }

    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

HWND
CreateOpenGLWindow(char* title, int x, int y, int width, int height,
           BYTE type, DWORD flags)
{
    int         pf;
    HDC         hDC;
    HWND        hWnd;
    WNDCLASS    wc;
    PIXELFORMATDESCRIPTOR pfd;
    static HINSTANCE hInstance = 0;

    /* only register the window class once - use hInstance as a flag. */
    if (!hInstance) {
    hInstance = GetModuleHandle(NULL);
    wc.style         = CS_OWNDC;
    wc.lpfnWndProc   = (WNDPROC)WindowProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_WINLOGO);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = "OpenGL";

    if (!RegisterClass(&wc)) {
        MessageBox(NULL, "RegisterClass() failed:  "
               "Cannot register window class.", "Error", MB_OK);
        return NULL;
    }
    }

    hWnd = CreateWindow("OpenGL", title, WS_OVERLAPPEDWINDOW |
            WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
            x, y, width, height, NULL, NULL, hInstance, NULL);

    if (hWnd == NULL) {
    MessageBox(NULL, "CreateWindow() failed:  Cannot create a window.",
           "Error", MB_OK);
    return NULL;
    }

    hDC = GetDC(hWnd);

    /* there is no guarantee that the contents of the stack that become
       the pfd are zeroed, therefore _make sure_ to clear these bits. */
    memset(&pfd, 0, sizeof(pfd));
    pfd.nSize        = sizeof(pfd);
    pfd.nVersion     = 1;
    pfd.dwFlags      = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | flags;
    pfd.iPixelType   = type;
    pfd.cColorBits   = 32;

    pf = ChoosePixelFormat(hDC, &pfd);
    if (pf == 0) {
    MessageBox(NULL, "ChoosePixelFormat() failed:  "
           "Cannot find a suitable pixel format.", "Error", MB_OK);
    return 0;
    }

    if (SetPixelFormat(hDC, pf, &pfd) == FALSE) {
    MessageBox(NULL, "SetPixelFormat() failed:  "
           "Cannot set format specified.", "Error", MB_OK);
    return 0;
    }

    DescribePixelFormat(hDC, pf, sizeof(PIXELFORMATDESCRIPTOR), &pfd);

    ReleaseDC(hDC, hWnd);

    return hWnd;
}

int APIENTRY
WinMain(HINSTANCE hCurrentInst, HINSTANCE hPreviousInst,
    LPSTR lpszCmdLine, int nCmdShow)
{
    HDC hDC;				/* device context */
    HGLRC hRC;				/* opengl context */
    HWND  hWnd;				/* window */
    MSG   msg;				/* message */

    hWnd = CreateOpenGLWindow("minimal", 0, 0, 256, 256, PFD_TYPE_RGBA, 0);
    if (hWnd == NULL)
    exit(1);

    hDC = GetDC(hWnd);
    hRC = wglCreateContext(hDC);
    wglMakeCurrent(hDC, hRC);

    ShowWindow(hWnd, nCmdShow);

    while(GetMessage(&msg, hWnd, 0, 0)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
    }

    wglMakeCurrent(NULL, NULL);
    ReleaseDC(hDC, hWnd);
    wglDeleteContext(hRC);
    DestroyWindow(hWnd);

    return msg.wParam;
}


animate.c

/* An example of an OpenGL animation loop using the Win32 API. Also
   demonstrates palette management for RGB and color index modes and
   general strategies for message handling. */

#include 			/* must include this before GL/gl.h */
#include 			/* OpenGL header file */
#include

HDC hDC;				/* device context */
HPALETTE hPalette = 0;			/* custom palette (if needed) */
GLboolean animate = GL_TRUE;		/* animation flag */

void
display()
{
    /* rotate a triangle around */
    glClear(GL_COLOR_BUFFER_BIT);
    if (animate)
    glRotatef(1.0f, 0.0f, 0.0f, 1.0f);
    glBegin(GL_TRIANGLES);
    glIndexi(1);
    glColor3f(1.0f, 0.0f, 0.0f);
    glVertex2i(0,  1);
    glIndexi(2);
    glColor3f(0.0f, 1.0f, 0.0f);
    glVertex2i(-1, -1);
    glIndexi(3);
    glColor3f(0.0f, 0.0f, 1.0f);
    glVertex2i(1, -1);
    glEnd();
    glFlush();
    SwapBuffers(hDC);			/* nop if singlebuffered */
}

LONG WINAPI
WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static PAINTSTRUCT ps;

    switch(uMsg) {
    case WM_PAINT:
    display();
    BeginPaint(hWnd, &ps);
    EndPaint(hWnd, &ps);
    return 0;

    case WM_SIZE:
    glViewport(0, 0, LOWORD(lParam), HIWORD(lParam));
    PostMessage(hWnd, WM_PAINT, 0, 0);
    return 0;

    case WM_CHAR:
    switch (wParam) {
    case 27:			/* ESC key */
        PostQuitMessage(0);
        break;
    case ' ':
        animate = !animate;
        break;
    }
    return 0;

    case WM_ACTIVATE:
    if (IsIconic(hWnd))
        animate = GL_FALSE;
    else
        animate = GL_TRUE;
    return 0;

    case WM_PALETTECHANGED:
    if (hWnd == (HWND)wParam)
        break;
    /* fall through to WM_QUERYNEWPALETTE */

    case WM_QUERYNEWPALETTE:
    if (hPalette) {
        UnrealizeObject(hPalette);
        SelectPalette(hDC, hPalette, FALSE);
        RealizePalette(hDC);
        return TRUE;
    }
    return FALSE;

    case WM_CLOSE:
    PostQuitMessage(0);
    return 0;
    }

    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

HWND
CreateOpenGLWindow(char* title, int x, int y, int width, int height,
           BYTE type, DWORD flags)
{
    int         n, pf;
    HWND        hWnd;
    WNDCLASS    wc;
    LOGPALETTE* lpPal;
    PIXELFORMATDESCRIPTOR pfd;
    static HINSTANCE hInstance = 0;

    /* only register the window class once - use hInstance as a flag. */
    if (!hInstance) {
    hInstance = GetModuleHandle(NULL);
    wc.style         = CS_OWNDC;
    wc.lpfnWndProc   = (WNDPROC)WindowProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_WINLOGO);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = "OpenGL";

    if (!RegisterClass(&wc)) {
        MessageBox(NULL, "RegisterClass() failed:  "
               "Cannot register window class.", "Error", MB_OK);
        return NULL;
    }
    }

    hWnd = CreateWindow("OpenGL", title, WS_OVERLAPPEDWINDOW |
            WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
            x, y, width, height, NULL, NULL, hInstance, NULL);

    if (hWnd == NULL) {
    MessageBox(NULL, "CreateWindow() failed:  Cannot create a window.",
           "Error", MB_OK);
    return NULL;
    }

    hDC = GetDC(hWnd);

    /* there is no guarantee that the contents of the stack that become
       the pfd are zeroed, therefore _make sure_ to clear these bits. */
    memset(&pfd, 0, sizeof(pfd));
    pfd.nSize        = sizeof(pfd);
    pfd.nVersion     = 1;
    pfd.dwFlags      = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | flags;
    pfd.iPixelType   = type;
    pfd.cColorBits   = 32;

    pf = ChoosePixelFormat(hDC, &pfd);
    if (pf == 0) {
    MessageBox(NULL, "ChoosePixelFormat() failed:  "
           "Cannot find a suitable pixel format.", "Error", MB_OK);
    return 0;
    }

    if (SetPixelFormat(hDC, pf, &pfd) == FALSE) {
    MessageBox(NULL, "SetPixelFormat() failed:  "
           "Cannot set format specified.", "Error", MB_OK);
    return 0;
    }

    DescribePixelFormat(hDC, pf, sizeof(PIXELFORMATDESCRIPTOR), &pfd);

    if (pfd.dwFlags & PFD_NEED_PALETTE ||
    pfd.iPixelType == PFD_TYPE_COLORINDEX) {

    n = 1 << pfd.cColorBits;
    if (n > 256) n = 256;

    lpPal = (LOGPALETTE*)malloc(sizeof(LOGPALETTE) +
                    sizeof(PALETTEENTRY) * n);
    memset(lpPal, 0, sizeof(LOGPALETTE) + sizeof(PALETTEENTRY) * n);
    lpPal->palVersion = 0x300;
    lpPal->palNumEntries = n;

    GetSystemPaletteEntries(hDC, 0, n, &lpPal->palPalEntry[0]);

    /* if the pixel type is RGBA, then we want to make an RGB ramp,
       otherwise (color index) set individual colors. */
    if (pfd.iPixelType == PFD_TYPE_RGBA) {
        int redMask = (1 << pfd.cRedBits) - 1;
        int greenMask = (1 << pfd.cGreenBits) - 1;
        int blueMask = (1 << pfd.cBlueBits) - 1;
        int i;

        /* fill in the entries with an RGB color ramp. */
        for (i = 0; i < n; ++i) {
        lpPal->palPalEntry[i].peRed =
            (((i >> pfd.cRedShift)   & redMask)   * 255) / redMask;
        lpPal->palPalEntry[i].peGreen =
            (((i >> pfd.cGreenShift) & greenMask) * 255) / greenMask;
        lpPal->palPalEntry[i].peBlue =
            (((i >> pfd.cBlueShift)  & blueMask)  * 255) / blueMask;
        lpPal->palPalEntry[i].peFlags = 0;
        }
    } else {
        lpPal->palPalEntry[0].peRed = 0;
        lpPal->palPalEntry[0].peGreen = 0;
        lpPal->palPalEntry[0].peBlue = 0;
        lpPal->palPalEntry[0].peFlags = PC_NOCOLLAPSE;
        lpPal->palPalEntry[1].peRed = 255;
        lpPal->palPalEntry[1].peGreen = 0;
        lpPal->palPalEntry[1].peBlue = 0;
        lpPal->palPalEntry[1].peFlags = PC_NOCOLLAPSE;
        lpPal->palPalEntry[2].peRed = 0;
        lpPal->palPalEntry[2].peGreen = 255;
        lpPal->palPalEntry[2].peBlue = 0;
        lpPal->palPalEntry[2].peFlags = PC_NOCOLLAPSE;
        lpPal->palPalEntry[3].peRed = 0;
        lpPal->palPalEntry[3].peGreen = 0;
        lpPal->palPalEntry[3].peBlue = 255;
        lpPal->palPalEntry[3].peFlags = PC_NOCOLLAPSE;
    }

    hPalette = CreatePalette(lpPal);
    if (hPalette) {
        SelectPalette(hDC, hPalette, FALSE);
        RealizePalette(hDC);
    }

    free(lpPal);
    }

    ReleaseDC(hDC, hWnd);

    return hWnd;
}

int APIENTRY
WinMain(HINSTANCE hCurrentInst, HINSTANCE hPreviousInst,
    LPSTR lpszCmdLine, int nCmdShow)
{
    HGLRC hRC;				/* opengl context */
    HWND  hWnd;				/* window */
    MSG   msg;				/* message */
    DWORD buffer = PFD_DOUBLEBUFFER;	/* buffering type */
    BYTE  color  = PFD_TYPE_RGBA;	/* color type */

    if (strstr(lpszCmdLine, "-sb")) {
    buffer = 0;
    }
    if (strstr(lpszCmdLine, "-ci")) {
    color = PFD_TYPE_COLORINDEX;
    }
    if (strstr(lpszCmdLine, "-h")) {
    MessageBox(NULL, "animate [-ci] [-sb]\n"
           "  -sb   single buffered\n"
           "  -ci   color index\n",
           "Usage help", MB_ICONINFORMATION);
    exit(0);
    }

    hWnd = CreateOpenGLWindow("animate", 0, 0, 256, 256, color, buffer);
    if (hWnd == NULL)
    exit(1);

    hDC = GetDC(hWnd);
    hRC = wglCreateContext(hDC);
    wglMakeCurrent(hDC, hRC);

    ShowWindow(hWnd, SW_SHOW);
    UpdateWindow(hWnd);

    while (1) {
    while(PeekMessage(&msg, hWnd, 0, 0, PM_NOREMOVE)) {
        if(GetMessage(&msg, hWnd, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
        } else {
        goto quit;
        }
    }
    display();
    }

quit:

    wglMakeCurrent(NULL, NULL);
    ReleaseDC(hDC, hWnd);
    wglDeleteContext(hRC);
    DestroyWindow(hWnd);
    if (hPalette)
    DeleteObject(hPalette);

    return 0;
}


mouse.c

/* An example of processing mouse events in an OpenGL program using
   the Win32 API. */

#include 			/* must include this before GL/gl.h */
#include 			/* OpenGL header file */
#include 			/* OpenGL utilities header file */
#include

enum {
    PAN = 1,				/* pan state bit */
    ROTATE,				/* rotate state bits */
    ZOOM				/* zoom state bit */
};

HDC hDC;				/* device context */
HPALETTE hPalette = 0;			/* custom palette (if needed) */
GLfloat trans[3];			/* current translation */
GLfloat rot[2];				/* current rotation */

static void update(int state, int ox, int nx, int oy, int ny)
{
    int dx = ox - nx;
    int dy = ny - oy;

    switch(state) {
    case PAN:
    trans[0] -= dx / 100.0f;
    trans[1] -= dy / 100.0f;
    break;
    case ROTATE:
    rot[0] += (dy * 180.0f) / 500.0f;
    rot[1] -= (dx * 180.0f) / 500.0f;
#define clamp(x) x = x > 360.0f ? x-360.0f : x < -360.0f ? x+=360.0f : x
    clamp(rot[0]);
    clamp(rot[1]);
    break;
    case ZOOM:
    trans[2] -= (dx+dy) / 100.0f;
    break;
    }
}

void
init()
{
    glEnable(GL_DEPTH_TEST);
}

void
reshape(int width, int height)
{
    glViewport(0, 0, width, height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0, (float)width/height, 0.001, 100.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.0f, 0.0f, -3.0f);
}

void
display()
{
    /* rotate a triangle around */
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glPushMatrix();
    glTranslatef(trans[0], trans[1], trans[2]);
    glRotatef(rot[0], 1.0f, 0.0f, 0.0f);
    glRotatef(rot[1], 0.0f, 1.0f, 0.0f);
    glBegin(GL_TRIANGLES);

#define TOP glIndexi(1); glColor3f(1.0f, 0.0f, 0.0f); glVertex3i(0, 1, 0)
#define FR  glIndexi(2); glColor3f(0.0f, 1.0f, 0.0f); glVertex3i(1, -1, 1)
#define FL  glIndexi(3); glColor3f(0.0f, 0.0f, 1.0f); glVertex3i(-1, -1, 1)
#define BR  glIndexi(3); glColor3f(0.0f, 0.0f, 1.0f); glVertex3i(1, -1, -1)
#define BL  glIndexi(2); glColor3f(0.0f, 1.0f, 0.0f); glVertex3i(-1, -1, -1)

    TOP; FL; FR;
    TOP; FR; BR;
    TOP; BR; BL;
    TOP; BL; FL;
    FR; FL; BL;
    BL; BR; FR;

    glEnd();
    glPopMatrix();
    glFlush();
    SwapBuffers(hDC);			/* nop if singlebuffered */
}

LONG WINAPI
WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static PAINTSTRUCT ps;
    static GLboolean left  = GL_FALSE;	/* left button currently down? */
    static GLboolean right = GL_FALSE;	/* right button currently down? */
    static GLuint    state   = 0;	/* mouse state flag */
    static int omx, omy, mx, my;

    switch(uMsg) {
    case WM_PAINT:
    display();
    BeginPaint(hWnd, &ps);
    EndPaint(hWnd, &ps);
    return 0;

    case WM_SIZE:
    reshape(LOWORD(lParam), HIWORD(lParam));
    PostMessage(hWnd, WM_PAINT, 0, 0);
    return 0;

    case WM_CHAR:
    switch (wParam) {
    case 27:			/* ESC key */
        PostQuitMessage(0);
        break;
    }
    return 0;

    case WM_LBUTTONDOWN:
    case WM_RBUTTONDOWN:
    /* if we don't set the capture we won't get mouse move
           messages when the mouse moves outside the window. */
    SetCapture(hWnd);
    mx = LOWORD(lParam);
    my = HIWORD(lParam);
    if (uMsg == WM_LBUTTONDOWN)
        state |= PAN;
    if (uMsg == WM_RBUTTONDOWN)
        state |= ROTATE;
    return 0;

    case WM_LBUTTONUP:
    case WM_RBUTTONUP:
    /* remember to release the capture when we are finished. */
    ReleaseCapture();
    state = 0;
    return 0;

    case WM_MOUSEMOVE:
    if (state) {
        omx = mx;
        omy = my;
        mx = LOWORD(lParam);
        my = HIWORD(lParam);
        /* Win32 is pretty braindead about the x, y position that
           it returns when the mouse is off the left or top edge
           of the window (due to them being unsigned). therefore,
           roll the Win32's 0..2^16 pointer co-ord range to the
           more amenable (and useful) 0..+/-2^15. */
        if(mx & 1 << 15) mx -= (1 << 16);
        if(my & 1 << 15) my -= (1 << 16);
        update(state, omx, mx, omy, my);
        PostMessage(hWnd, WM_PAINT, 0, 0);
    }
    return 0;

    case WM_PALETTECHANGED:
    if (hWnd == (HWND)wParam)
        break;
    /* fall through to WM_QUERYNEWPALETTE */

    case WM_QUERYNEWPALETTE:
    if (hPalette) {
        UnrealizeObject(hPalette);
        SelectPalette(hDC, hPalette, FALSE);
        RealizePalette(hDC);
        return TRUE;
    }
    return FALSE;

    case WM_CLOSE:
    PostQuitMessage(0);
    return 0;
    }

    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

HWND
CreateOpenGLWindow(char* title, int x, int y, int width, int height,
           BYTE type, DWORD flags)
{
    int         n, pf;
    HWND        hWnd;
    WNDCLASS    wc;
    LOGPALETTE* lpPal;
    PIXELFORMATDESCRIPTOR pfd;
    static HINSTANCE hInstance = 0;

    /* only register the window class once - use hInstance as a flag. */
    if (!hInstance) {
    hInstance = GetModuleHandle(NULL);
    wc.style         = CS_OWNDC;
    wc.lpfnWndProc   = (WNDPROC)WindowProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_WINLOGO);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = "OpenGL";

    if (!RegisterClass(&wc)) {
        MessageBox(NULL, "RegisterClass() failed:  "
               "Cannot register window class.", "Error", MB_OK);
        return NULL;
    }
    }

    hWnd = CreateWindow("OpenGL", title, WS_OVERLAPPEDWINDOW |
            WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
            x, y, width, height, NULL, NULL, hInstance, NULL);

    if (hWnd == NULL) {
    MessageBox(NULL, "CreateWindow() failed:  Cannot create a window.",
           "Error", MB_OK);
    return NULL;
    }

    hDC = GetDC(hWnd);

    /* there is no guarantee that the contents of the stack that become
       the pfd are zeroed, therefore _make sure_ to clear these bits. */
    memset(&pfd, 0, sizeof(pfd));
    pfd.nSize        = sizeof(pfd);
    pfd.nVersion     = 1;
    pfd.dwFlags      = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | flags;
    pfd.iPixelType   = type;
    pfd.cDepthBits   = 32;
    pfd.cColorBits   = 32;

    pf = ChoosePixelFormat(hDC, &pfd);
    if (pf == 0) {
    MessageBox(NULL, "ChoosePixelFormat() failed:  "
           "Cannot find a suitable pixel format.", "Error", MB_OK);
    return 0;
    }

    if (SetPixelFormat(hDC, pf, &pfd) == FALSE) {
    MessageBox(NULL, "SetPixelFormat() failed:  "
           "Cannot set format specified.", "Error", MB_OK);
    return 0;
    }

    DescribePixelFormat(hDC, pf, sizeof(PIXELFORMATDESCRIPTOR), &pfd);

    if (pfd.dwFlags & PFD_NEED_PALETTE ||
    pfd.iPixelType == PFD_TYPE_COLORINDEX) {

    n = 1 << pfd.cColorBits;
    if (n > 256) n = 256;

    lpPal = (LOGPALETTE*)malloc(sizeof(LOGPALETTE) +
                    sizeof(PALETTEENTRY) * n);
    memset(lpPal, 0, sizeof(LOGPALETTE) + sizeof(PALETTEENTRY) * n);
    lpPal->palVersion = 0x300;
    lpPal->palNumEntries = n;

    GetSystemPaletteEntries(hDC, 0, n, &lpPal->palPalEntry[0]);

    /* if the pixel type is RGBA, then we want to make an RGB ramp,
       otherwise (color index) set individual colors. */
    if (pfd.iPixelType == PFD_TYPE_RGBA) {
        int redMask = (1 << pfd.cRedBits) - 1;
        int greenMask = (1 << pfd.cGreenBits) - 1;
        int blueMask = (1 << pfd.cBlueBits) - 1;
        int i;

        /* fill in the entries with an RGB color ramp. */
        for (i = 0; i < n; ++i) {
        lpPal->palPalEntry[i].peRed =
            (((i >> pfd.cRedShift)   & redMask)   * 255) / redMask;
        lpPal->palPalEntry[i].peGreen =
            (((i >> pfd.cGreenShift) & greenMask) * 255) / greenMask;
        lpPal->palPalEntry[i].peBlue =
            (((i >> pfd.cBlueShift)  & blueMask)  * 255) / blueMask;
        lpPal->palPalEntry[i].peFlags = 0;
        }
    } else {
        lpPal->palPalEntry[0].peRed = 0;
        lpPal->palPalEntry[0].peGreen = 0;
        lpPal->palPalEntry[0].peBlue = 0;
        lpPal->palPalEntry[0].peFlags = PC_NOCOLLAPSE;
        lpPal->palPalEntry[1].peRed = 255;
        lpPal->palPalEntry[1].peGreen = 0;
        lpPal->palPalEntry[1].peBlue = 0;
        lpPal->palPalEntry[1].peFlags = PC_NOCOLLAPSE;
        lpPal->palPalEntry[2].peRed = 0;
        lpPal->palPalEntry[2].peGreen = 255;
        lpPal->palPalEntry[2].peBlue = 0;
        lpPal->palPalEntry[2].peFlags = PC_NOCOLLAPSE;
        lpPal->palPalEntry[3].peRed = 0;
        lpPal->palPalEntry[3].peGreen = 0;
        lpPal->palPalEntry[3].peBlue = 255;
        lpPal->palPalEntry[3].peFlags = PC_NOCOLLAPSE;
    }

    hPalette = CreatePalette(lpPal);
    if (hPalette) {
        SelectPalette(hDC, hPalette, FALSE);
        RealizePalette(hDC);
    }

    free(lpPal);
    }

    ReleaseDC(hDC, hWnd);

    return hWnd;
}

int APIENTRY
WinMain(HINSTANCE hCurrentInst, HINSTANCE hPreviousInst,
    LPSTR lpszCmdLine, int nCmdShow)
{
    HGLRC hRC;				/* opengl context */
    HWND  hWnd;				/* window */
    MSG   msg;				/* message */
    DWORD buffer = PFD_DOUBLEBUFFER;	/* buffering type */
    BYTE  color  = PFD_TYPE_RGBA;	/* color type */

    if (strstr(lpszCmdLine, "-sb")) {
    buffer = 0;
    }
    if (strstr(lpszCmdLine, "-ci")) {
    color = PFD_TYPE_COLORINDEX;
    }
    if (strstr(lpszCmdLine, "-h")) {
    MessageBox(NULL, "mouse [-ci] [-sb]\n"
           "  -sb   single buffered\n"
           "  -ci   color index\n",
           "Usage help", MB_ICONINFORMATION);
    exit(0);
    }

    hWnd = CreateOpenGLWindow("mouse", 0, 0, 256, 256, color, buffer);
    if (hWnd == NULL)
    exit(1);

    hDC = GetDC(hWnd);
    hRC = wglCreateContext(hDC);
    wglMakeCurrent(hDC, hRC);

    init();

    ShowWindow(hWnd, nCmdShow);

    while(GetMessage(&msg, hWnd, 0, 0)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
    }

    wglMakeCurrent(NULL, NULL);
    ReleaseDC(hDC, hWnd);
    wglDeleteContext(hRC);
    DestroyWindow(hWnd);
    if (hPalette)
    DeleteObject(hPalette);

    return msg.wParam;
}


image.c

/* An example of using draw pixels and pixel zoom operations in OpenGL
   using the Win32 API. */

#include 			/* must include this before GL/gl.h */
#include 			/* OpenGL header file */
#include 			/* OpenGL utilities header file */
#include
#include "opengl.h"
#include "texture.h"

HDC hDC;				/* device context */
HPALETTE hPalette = 0;			/* custom palette (if needed) */
unsigned* image;			/* texture image */
int iwidth, iheight, idepth;

void
reshape(int width, int height)
{
    glViewport(0, 0, width, height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, width, 0, height, -1, 1);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glRasterPos2i(0, 0);
    glPixelZoom((float)width/iwidth, (float)height/iheight);
}

void
display()
{
    glClear(GL_COLOR_BUFFER_BIT);
    glDrawPixels(iwidth, iheight, GL_RGBA, GL_UNSIGNED_BYTE, image);
    glFlush();
}

LONG WINAPI
WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static PAINTSTRUCT ps;

    switch(uMsg) {
    case WM_PAINT:
    display();
    BeginPaint(hWnd, &ps);
    EndPaint(hWnd, &ps);
    return 0;

    case WM_SIZE:
    reshape(LOWORD(lParam), HIWORD(lParam));
    PostMessage(hWnd, WM_PAINT, 0, 0);
    return 0;

    case WM_CHAR:
    switch (wParam) {
    case 27:			/* ESC key */
        PostQuitMessage(0);
        break;
    }
    return 0;

    case WM_PALETTECHANGED:
    if (hWnd == (HWND)wParam)
        break;
    /* fall through to WM_QUERYNEWPALETTE */

    case WM_QUERYNEWPALETTE:
    if (hPalette) {
        UnrealizeObject(hPalette);
        SelectPalette(hDC, hPalette, FALSE);
        RealizePalette(hDC);
        return TRUE;
    }
    return FALSE;

    case WM_CLOSE:
    PostQuitMessage(0);
    return 0;
    }

    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

HWND
CreateOpenGLWindow(char* title, int x, int y, int width, int height,
           BYTE type, DWORD flags)
{
    int         n, pf;
    HWND        hWnd;
    WNDCLASS    wc;
    LOGPALETTE* lpPal;
    PIXELFORMATDESCRIPTOR pfd;
    static HINSTANCE hInstance = 0;

    /* only register the window class once - use hInstance as a flag. */
    if (!hInstance) {
    hInstance = GetModuleHandle(NULL);
    wc.style         = CS_OWNDC;
    wc.lpfnWndProc   = (WNDPROC)WindowProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_WINLOGO);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = "OpenGL";

    if (!RegisterClass(&wc)) {
        MessageBox(NULL, "RegisterClass() failed:  "
               "Cannot register window class.", "Error", MB_OK);
        return NULL;
    }
    }

    hWnd = CreateWindow("OpenGL", title, WS_OVERLAPPEDWINDOW |
            WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
            x, y, width, height, NULL, NULL, hInstance, NULL);

    if (hWnd == NULL) {
    MessageBox(NULL, "CreateWindow() failed:  Cannot create a window.",
           "Error", MB_OK);
    return NULL;
    }

    hDC = GetDC(hWnd);

    /* there is no guarantee that the contents of the stack that become
       the pfd are zeroed, therefore _make sure_ to clear these bits. */
    memset(&pfd, 0, sizeof(pfd));
    pfd.nSize        = sizeof(pfd);
    pfd.nVersion     = 1;
    pfd.dwFlags      = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | flags;
    pfd.iPixelType   = type;
    pfd.cDepthBits   = 32;
    pfd.cColorBits   = 32;

    pf = ChoosePixelFormat(hDC, &pfd);
    if (pf == 0) {
    MessageBox(NULL, "ChoosePixelFormat() failed:  "
           "Cannot find a suitable pixel format.", "Error", MB_OK);
    return 0;
    }

    if (SetPixelFormat(hDC, pf, &pfd) == FALSE) {
    MessageBox(NULL, "SetPixelFormat() failed:  "
           "Cannot set format specified.", "Error", MB_OK);
    return 0;
    }

    DescribePixelFormat(hDC, pf, sizeof(PIXELFORMATDESCRIPTOR), &pfd);

    if (pfd.dwFlags & PFD_NEED_PALETTE) {
    int redMask, greenMask, blueMask, i;

    n = 1 << pfd.cColorBits;
    if (n > 256) n = 256;

    lpPal = (LOGPALETTE*)malloc(sizeof(LOGPALETTE) +
                    sizeof(PALETTEENTRY) * n);
    memset(lpPal, 0, sizeof(LOGPALETTE) + sizeof(PALETTEENTRY) * n);
    lpPal->palVersion = 0x300;
    lpPal->palNumEntries = n;

    GetSystemPaletteEntries(hDC, 0, n, &lpPal->palPalEntry[0]);

    /* assume an RGBA pixel type. */
    redMask = (1 << pfd.cRedBits) - 1;
    greenMask = (1 << pfd.cGreenBits) - 1;
    blueMask = (1 << pfd.cBlueBits) - 1;

    /* fill in the entries with an RGB color ramp. */
    for (i = 0; i < n; ++i) {
        lpPal->palPalEntry[i].peRed =
        (((i >> pfd.cRedShift)   & redMask)   * 255) / redMask;
        lpPal->palPalEntry[i].peGreen =
        (((i >> pfd.cGreenShift) & greenMask) * 255) / greenMask;
        lpPal->palPalEntry[i].peBlue =
        (((i >> pfd.cBlueShift)  & blueMask)  * 255) / blueMask;
        lpPal->palPalEntry[i].peFlags = 0;
    }

    hPalette = CreatePalette(lpPal);
    if (hPalette) {
        SelectPalette(hDC, hPalette, FALSE);
        RealizePalette(hDC);
    }

    free(lpPal);
    }

    ReleaseDC(hDC, hWnd);

    return hWnd;
}

int
main(int argc, char** argv)
{
    HGLRC hRC;				/* opengl context */
    HWND  hWnd;				/* window */
    MSG   msg;				/* message */

    if (argc > 1) {
    if (strcmp(argv[1], "-h") == 0) {
        fprintf(stderr, "%s [file]\n", argv[0]);
        fprintf(stderr, "  file   Iris RGB format image file\n");
        exit(0);
    }
    image = read_texture(argv[1], &iwidth, &iheight, &idepth);
    } else {
    image = opengl_image;
    iwidth = opengl_width;
    iheight = opengl_height;
    idepth = opengl_depth;
    }

    hWnd = CreateOpenGLWindow(argv[0], 0, 0, 256, 256,
                  PFD_TYPE_RGBA, 0);
    if (hWnd == NULL)
    exit(1);

    hDC = GetDC(hWnd);
    hRC = wglCreateContext(hDC);
    wglMakeCurrent(hDC, hRC);

#if 0 /* code to create a header file out of an image. */
    printf("int _width = %d;\n", iwidth);
    printf("int _height = %d;\n", iheight);
    printf("int _depth = %d;\n", idepth);
    printf("unsigned _image[] = {\n");
    for (j = 0; j < iheight; j++) {
     for (i = 0; i < iwidth; i++) {
         printf("0x%x, ", image[j * iwidth + i]);
     }
     printf("\n");
    }
    printf("};\n");
    exit(0);
#endif

    ShowWindow(hWnd, SW_SHOW);

    while(GetMessage(&msg, hWnd, 0, 0)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
    }

    wglMakeCurrent(NULL, NULL);
    ReleaseDC(hDC, hWnd);
    wglDeleteContext(hRC);
    DestroyWindow(hWnd);
    if (hPalette)
    DeleteObject(hPalette);

    return 0;
}


SimpleDIB.c

/*
** Simple example of rendering to a Windows DIB (Device Independent Bitmap)
*/
#include
#include
#include

#if !defined(M_PI)
#define M_PI 3.14159265F
#endif

char *className = "OpenGL";
char *windowName = "Simple DIB Example";
int winX = 0, winY = 0;
int winWidth = 256, winHeight = 256;

HDC hDC;
HDC hDCFrontBuffer;
HGLRC hGLRC;
HPALETTE hPalette;
HBITMAP hBitmap, hOldBitmap;

void (*idleFunc)(void);

BOOL colorIndexMode = FALSE;
BOOL doubleBuffered = FALSE;
BOOL renderToDIB = TRUE;

/* Struct used to manage color ramps */
struct colorIndexState {
    GLfloat amb[3];	/* ambient color / bottom of ramp */
    GLfloat diff[3];	/* diffuse color / middle of ramp */
    GLfloat spec[3];	/* specular color / top of ramp */
    GLfloat ratio;	/* ratio of diffuse to specular in ramp */
    GLint indexes[3];	/* where ramp was placed in palette */
};

/*
** Each entry in this array corresponds to a color ramp in the
** palette.  The indexes member of each struct is updated to
** reflect the placement of the color ramp in the palette.
*/
#define NUM_COLORS (sizeof(colors) / sizeof(colors[0]))
struct colorIndexState colors[] = {
    {
        { 0.0F, 0.0F, 0.0F },
        { 0.1F, 0.6F, 0.3F },
        { 1.0F, 1.0F, 1.0F },
        0.75F, { 0, 0, 0 },
    },
    {
        { 0.0F, 0.0F, 0.0F },
        { 0.0F, 0.2F, 0.5F },
        { 1.0F, 1.0F, 1.0F },

        0.75F, { 0, 0, 0 },
    },
    {
        { 0.0F, 0.05F, 0.05F },
        { 0.6F, 0.0F, 0.8F },
        { 1.0F, 1.0F, 1.0F },
        0.75F, { 0, 0, 0 },
    },
};

void
drawTorus(void)
{
    int numMajor = 32;
    int numMinor = 24;
    float majorRadius = 0.6F;
    float minorRadius = 0.2F;
    double majorStep = 2.0F*M_PI / numMajor;
    double minorStep = 2.0F*M_PI / numMinor;
    int i, j;

    for (i=0; i winHeight) {
    GLfloat aspect = (GLfloat) winWidth / (GLfloat) winHeight;
    glFrustum(-0.5F*aspect, 0.5F*aspect, -0.5F, 0.5F, 1.0F, 3.0F);
    } else {
    GLfloat aspect = (GLfloat) winHeight / (GLfloat) winWidth;
    glFrustum(-0.5F, 0.5F, -0.5F*aspect, 0.5F*aspect, 1.0F, 3.0F);
    }
    glMatrixMode(GL_MODELVIEW);
}

void
init(void)
{
    GLfloat matShine = 20.00F;
    GLfloat light0Pos[4] = { 0.70F, 0.70F, 1.25F, 0.00F };

    glClearColor(colors[2].diff[0], colors[2].diff[1], colors[2].diff[2], 1.0F);
    glClearIndex((GLfloat) colors[2].indexes[1]);

    setProjection();
    glTranslatef(0.0F, 0.0F, -2.0F);

    glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, matShine);
    glLightfv(GL_LIGHT0, GL_POSITION, light0Pos);
    glEnable(GL_LIGHT0);

    glEnable(GL_LIGHTING);
    glEnable(GL_DEPTH_TEST);

    if (!colorIndexMode) {
    glEnable(GL_COLOR_MATERIAL);
    }
}

void
doRedraw(void)
{
    static GLfloat x, y, z;

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glPushMatrix();
    glRotatef(x, 1.0F, 0.0F, 0.0F);
    glRotatef(y, 0.0F, 1.0F, 0.0F);
    glRotatef(z, 0.0F, 0.0F, 1.0F);

    drawTorus();

    glPopMatrix();

    if (renderToDIB) {
    glFinish();
    BitBlt(hDCFrontBuffer, 0, 0, winWidth, winHeight, hDC, 0, 0, SRCCOPY);
    GdiFlush();
    } else {
    SwapBuffers(hDC);
    }

    x += 5.0F;
    if (x > 360.0F) x -= 360.0F;
    y += 7.0F;
    if (y > 360.0F) y -= 360.0F;
    z += 9.0F;
    if (z > 360.0F) z -= 360.0F;
}

void
redraw(void)
{
    idleFunc = doRedraw;
}

void
resize(void)
{
    setProjection();
    glViewport(0, 0, winWidth, winHeight);
}

/*****************************************************************/

void
setupDIB(HDC hDC)
{
    BITMAPINFO *bmInfo;
    BITMAPINFOHEADER *bmHeader;
    UINT usage;
    VOID *base;
    int bmiSize;
    int bitsPerPixel;

    bmiSize = sizeof(*bmInfo);
    bitsPerPixel = GetDeviceCaps(hDC, BITSPIXEL);

    switch (bitsPerPixel) {
    case 8:
    /* bmiColors is 256 WORD palette indices */
    bmiSize += (256 * sizeof(WORD)) - sizeof(RGBQUAD);
    break;
    case 16:
    /* bmiColors is 3 WORD component masks */
    bmiSize += (3 * sizeof(DWORD)) - sizeof(RGBQUAD);
    break;
    case 24:
    case 32:
    default:
    /* bmiColors not used */
    break;
    }

    bmInfo = (BITMAPINFO *) calloc(1, bmiSize);
    bmHeader = &bmInfo->bmiHeader;

    bmHeader->biSize = sizeof(*bmHeader);
    bmHeader->biWidth = winWidth;
    bmHeader->biHeight = winHeight;
    bmHeader->biPlanes = 1;			/* must be 1 */
    bmHeader->biBitCount = bitsPerPixel;
    bmHeader->biXPelsPerMeter = 0;
    bmHeader->biYPelsPerMeter = 0;
    bmHeader->biClrUsed = 0;			/* all are used */
    bmHeader->biClrImportant = 0;		/* all are important */

    switch (bitsPerPixel) {
    case 8:
    bmHeader->biCompression = BI_RGB;
    bmHeader->biSizeImage = 0;
    usage = DIB_PAL_COLORS;
    /* bmiColors is 256 WORD palette indices */
    {
        WORD *palIndex = (WORD *) &bmInfo->bmiColors[0];
        int i;

        for (i=0; i<256; i++) {
        palIndex[i] = i;
        }
    }
    break;
    case 16:
    bmHeader->biCompression = BI_RGB;
    bmHeader->biSizeImage = 0;
    usage = DIB_RGB_COLORS;
    /* bmiColors is 3 WORD component masks */
    {
        DWORD *compMask = (DWORD *) &bmInfo->bmiColors[0];

        compMask[0] = 0xF800;
        compMask[1] = 0x07E0;
        compMask[2] = 0x001F;
    }
    break;
    case 24:
    case 32:
    default:
    bmHeader->biCompression = BI_RGB;
    bmHeader->biSizeImage = 0;
    usage = DIB_RGB_COLORS;
    /* bmiColors not used */
    break;
    }

    hBitmap = CreateDIBSection(hDC, bmInfo, usage, &base, NULL, 0);
    if (hBitmap == NULL) {
    (void) MessageBox(WindowFromDC(hDC),
        "Failed to create DIBSection.",
        "OpenGL application error",
        MB_ICONERROR | MB_OK);
    exit(1);
    }

    hOldBitmap = SelectObject(hDC, hBitmap);

    free(bmInfo);
}

void
resizeDIB(HDC hDC)
{
    SelectObject(hDC, hOldBitmap);
    DeleteObject(hBitmap);
    setupDIB(hDC);
}

void
setupPalette(HDC hDC)
{
    PIXELFORMATDESCRIPTOR pfd;
    LOGPALETTE* pPal;
    int pixelFormat = GetPixelFormat(hDC);
    int paletteSize;

    DescribePixelFormat(hDC, pixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pfd);

    /*
    ** Determine if a palette is needed and if so what size.
    */
    if (pfd.dwFlags & PFD_NEED_PALETTE) {
    paletteSize = 1 << pfd.cColorBits;
    } else if (pfd.iPixelType == PFD_TYPE_COLORINDEX) {
    paletteSize = 4096;
    } else {
    return;
    }

    pPal = (LOGPALETTE*)
    malloc(sizeof(LOGPALETTE) + paletteSize * sizeof(PALETTEENTRY));
    pPal->palVersion = 0x300;
    pPal->palNumEntries = paletteSize;

    if (pfd.iPixelType == PFD_TYPE_RGBA) {
    /*
    ** Fill the logical paletee with RGB color ramps
    */
    int redMask = (1 << pfd.cRedBits) - 1;
    int greenMask = (1 << pfd.cGreenBits) - 1;
    int blueMask = (1 << pfd.cBlueBits) - 1;
    int i;

    for (i=0; ipalPalEntry[i].peRed =
            (((i >> pfd.cRedShift) & redMask) * 255) / redMask;
        pPal->palPalEntry[i].peGreen =
            (((i >> pfd.cGreenShift) & greenMask) * 255) / greenMask;
        pPal->palPalEntry[i].peBlue =
            (((i >> pfd.cBlueShift) & blueMask) * 255) / blueMask;
        pPal->palPalEntry[i].peFlags = 0;
    }
    } else {
    /*
    ** Fill the logical palette with color ramps.
    **
    ** Set up the logical palette so that it can be realized
    ** into the system palette as an identity palette.
    **
    ** 1) The default static entries should be present and at the right
    **    location.  The easiest way to do this is to grab them from
    **    the current system palette.
    **
    ** 2) All non-static entries should be initialized to unique values.
    **    The easiest way to do this is to ensure that all of the non-static
    **    entries have the PC_NOCOLLAPSE flag bit set.
    */
    int numRamps = NUM_COLORS;
    int rampSize = (paletteSize - 20) / numRamps;
    int extra = (paletteSize - 20) - (numRamps * rampSize);
    int i, r;

    /*
    ** Initialize static entries by copying them from the
    ** current system palette.
    */
    GetSystemPaletteEntries(hDC, 0, paletteSize, &pPal->palPalEntry[0]);

    /*
    ** Fill in non-static entries with desired colors.
    */
    for (r=0; rpalPalEntry[rampBase];
        int diffSize = (int) (rampSize * colors[r].ratio);
        int specSize = rampSize - diffSize;

        for (i=0; ipalPalEntry[index];

        pe->peRed = (BYTE) 0;
        pe->peGreen = (BYTE) 0;
        pe->peBlue = (BYTE) 0;
        pe->peFlags = PC_NOCOLLAPSE;
    }
    }

    hPalette = CreatePalette(pPal);
    free(pPal);

    if (hPalette) {
    SelectPalette(hDC, hPalette, FALSE);
    RealizePalette(hDC);
    }
}

void
setupPixelFormat(HDC hDC)
{
    PIXELFORMATDESCRIPTOR pfd = {
    sizeof(PIXELFORMATDESCRIPTOR),	/* size of this pfd */
    1,				/* version num */
    PFD_SUPPORT_OPENGL,		/* support OpenGL */
    0,				/* pixel type */
    0,				/* 8-bit color depth */
    0, 0, 0, 0, 0, 0,		/* color bits (ignored) */
    0,				/* no alpha buffer */
    0,				/* alpha bits (ignored) */
    0,				/* no accumulation buffer */
    0, 0, 0, 0,			/* accum bits (ignored) */
    16,				/* depth buffer */
    0,				/* no stencil buffer */
    0,				/* no auxiliary buffers */
    PFD_MAIN_PLANE,			/* main layer */
    0,				/* reserved */
    0, 0, 0,			/* no layer, visible, damage masks */
    };
    int SelectedPixelFormat;
    BOOL retVal;

    pfd.cColorBits = GetDeviceCaps(hDC, BITSPIXEL);

    if (colorIndexMode) {
    pfd.iPixelType = PFD_TYPE_COLORINDEX;
    } else {
    pfd.iPixelType = PFD_TYPE_RGBA;
    }

    if (doubleBuffered) {
        pfd.dwFlags |= PFD_DOUBLEBUFFER;
    }

    if (renderToDIB) {
    pfd.dwFlags |= PFD_DRAW_TO_BITMAP;
    } else {
    pfd.dwFlags |= PFD_DRAW_TO_WINDOW;
    }

    SelectedPixelFormat = ChoosePixelFormat(hDC, &pfd);
    if (SelectedPixelFormat == 0) {
    (void) MessageBox(WindowFromDC(hDC),
        "Failed to find acceptable pixel format.",
        "OpenGL application error",
        MB_ICONERROR | MB_OK);
    exit(1);
    }

    retVal = SetPixelFormat(hDC, SelectedPixelFormat, &pfd);
    if (retVal != TRUE) {
    (void) MessageBox(WindowFromDC(hDC),
        "Failed to set pixel format.",
        "OpenGL application error",
        MB_ICONERROR | MB_OK);
    exit(1);
    }
}

LRESULT APIENTRY
WndProc(
    HWND hWnd,
    UINT message,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (message) {
    case WM_CREATE:
    return 0;
    case WM_DESTROY:
    PostQuitMessage(0);
    return 0;
    case WM_SIZE:
    if (hGLRC) {
        winWidth = (int) LOWORD(lParam);
        winHeight = (int) HIWORD(lParam);
        /*
        if (renderToDIB) {
        resizeDIB(hDC);
        }
        */
        resize();
        return 0;
    }
    case WM_PALETTECHANGED:
    /*
    ** Update palette mapping if this *is not* the active window.
    */
    if (hGLRC && hPalette && (HWND) wParam != hWnd) {
        UnrealizeObject(hPalette);
        SelectPalette(hDC, hPalette, FALSE);
        RealizePalette(hDC);
        redraw();
        return 0;
    }
    break;
    case WM_QUERYNEWPALETTE:
    /*
    ** Update palette mapping if this *is* the active window.
    */
    if (hGLRC && hPalette) {
        UnrealizeObject(hPalette);
        SelectPalette(hDC, hPalette, FALSE);
        RealizePalette(hDC);
        redraw();
        return TRUE;
    }
    break;
    case WM_PAINT:
    /*
    ** Update the window.  Don't use the device context returned by
    ** BeginPaint as it won't have the right palette selected into it.
    */
    if (hGLRC) {
        PAINTSTRUCT ps;

        BeginPaint(hWnd, &ps);
        redraw();
        EndPaint(hWnd, &ps);
        return 0;
    }
    break;
    case WM_CHAR:
    switch ((int)wParam) {
    case VK_ESCAPE:
        DestroyWindow(hWnd);
        return 0;
    case VK_SPACE:
        if (idleFunc) {
        idleFunc = NULL;
        } else {
        idleFunc = doRedraw;
        }
    default:
        break;
    }
    break;
    default:
    break;
    }

    /* Deal with any unprocessed messages */
    return DefWindowProc(hWnd, message, wParam, lParam);
}

int APIENTRY
WinMain(
    HINSTANCE hCurrentInst,
    HINSTANCE hPreviousInst,
    LPSTR lpszCmdLine,
    int nCmdShow)
{
    WNDCLASS wndClass;
    HWND hWnd;
    MSG msg;

    /* Define and register a window class */
    wndClass.style = CS_HREDRAW | CS_VREDRAW;
    wndClass.lpfnWndProc = WndProc;
    wndClass.cbClsExtra = 0;
    wndClass.cbWndExtra = 0;
    wndClass.hInstance = hCurrentInst;
    wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndClass.hbrBackground = GetStockObject(WHITE_BRUSH);
    wndClass.lpszMenuName = NULL;
    wndClass.lpszClassName = className;
    RegisterClass(&wndClass);

    /* Create a window of the previously defined class */
    hWnd = CreateWindow(
    className,		/* Window class's name */
    windowName,		/* Title bar text */
    WS_OVERLAPPEDWINDOW |	/* The window's style */
    WS_CLIPCHILDREN |
    WS_CLIPSIBLINGS,
    winX, winY,		/* Position */
    winWidth, winHeight,	/* Size */
    NULL,			/* Parent window's handle */
    NULL,			/* Menu handle */
    hCurrentInst,		/* Instance handle */
    NULL);			/* No additional data */

    /* Map the window to the screen */
    ShowWindow(hWnd, nCmdShow);

    /* Force the window to repaint itself */
    UpdateWindow(hWnd);

    /*
    ** Set up for OpenGL rendering.  Bind the rendering context to
    ** the same device context that the palette will be selected into.
    */
    hDC = GetDC(hWnd);
    hDCFrontBuffer = hDC;
    if (renderToDIB) {
    hDC = CreateCompatibleDC(hDCFrontBuffer);
    setupDIB(hDC);
    }
    setupPixelFormat(hDC);
    setupPalette(hDC);
    hGLRC = wglCreateContext(hDC);
    wglMakeCurrent(hDC, hGLRC);
    init();
    idleFunc = doRedraw;

    /* Process Messages */
    while (1) {
    /* execute the idle function while there are no messages to process */
    while (idleFunc &&
           PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE) == FALSE)
    {
        (*idleFunc)();
    }
    if (GetMessage(&msg, NULL, 0, 0) != TRUE) {
        break;
    }
    TranslateMessage(&msg);
    DispatchMessage(&msg);
    }

    /*
    ** Finish OpenGL rendering.
    */
    idleFunc = NULL;
    if (hGLRC) {
    wglMakeCurrent(NULL, NULL);
    wglDeleteContext(hGLRC);
    }
    ReleaseDC(hWnd, hDC);

    return msg.wParam;
}


wglinfo.c

/*

    wglinfo.c
    Nate Robins, 1997

    Shows a graph of all the visuals that support OpenGL and their
    capabilities.  Just like (well, almost) glxinfo on SGI's.
    Only the -v (verbose) and -h (help) command line arguments are
    implemented.

    A legend for the table that this baby spits out

        -----------------------------------------------

    visual ~= pixel format descriptor
    id      = pixel format number (integer from 1 - max pixel formats)
    dep     = cColorBits      - color depth
    xsp     = no analog       - transparent pixel (currently always ".")
    bfsz    = cColorBits      - framebuffer size (no analog in Win32?)
    lvl     = bReserved       - overlay(>0), underlay(<0), main plane(0).
    rgci    = iPixelType      - rb = rgba mode, ci = color index mode.
    db      = dwFlags & PFD_DOUBLEBUFFER - double buffer flag (y = yes)
    stro    = dwFlags & PFD_STEREO       - stereo flag        (y = yes)
    rsz     = cRedBits        - # bits of red
    gsz     = cGreenBits      - # bits of green
    bsz     = cBlueBits       - # bits of blue
    asz     = cAlphaBits      - # bits of alpha
    axbf    = cAuxBuffers     - # of aux buffers
    dpth    = cDepthBits      - # bits of depth
    stcl    = cStencilBits    - # bits of stencil
    accum r = cAccumRedBits   - # bits of red in accumulation buffer
    accum g = cAccumGreenBits - # bits of green in accumulation buffer
    accum b = cAccumBlueBits  - # bits of blue in accumulation buffer
    accum a = cAccumAlphaBits - # bits of alpha in accumulation buffer
    ms      = no analog  - multisample buffers
 */

#include 			/* must include this before GL/gl.h */
#include 			/* OpenGL header file */
#include 			/* OpenGL utilities header file */
#include

void VisualInfo(HDC hDC, int verbose)
{
    int i, maxpf;

    PIXELFORMATDESCRIPTOR pfd;

    /* calling DescribePixelFormat() with NULL args return maximum
       number of pixel formats */

    maxpf = DescribePixelFormat(hDC, 0, 0, NULL);

    if (!verbose) {

 printf("   visual  x  bf lv rg d st  r  g  b a  ax dp st accum buffs  ms \n");
 printf(" id dep cl sp sz l  ci b ro sz sz sz sz bf th cl  r  g  b  a ns b\n");
 printf("-----------------------------------------------------------------\n");

    /* loop through all the pixel formats */

    for(i = 1; i <= maxpf; i++) {

    DescribePixelFormat(hDC, i, sizeof(PIXELFORMATDESCRIPTOR), &pfd);

    /* only describe this format if it supports OpenGL */
    if(!(pfd.dwFlags & PFD_SUPPORT_OPENGL))
        continue;
    /* other criteria could be tested here for actual pixel format
           choosing in an application:

       for (...each pixel format...) {

         if (pfd.dwFlags & PFD_SUPPORT_OPENGL &&
             pfd.dwFlags & PFD_DOUBLEBUFFER &&
             pfd.cDepthBits >= 24 &&
             pfd.cColorBits >= 24)
             {
                goto found;
                 }
           }
       ... not found so exit ...

           found:

       ... found so use it ...
    */

    /* print out the information for this pixel format */

    printf("0x%02x ", i);
    printf("%2d ", pfd.cColorBits);
    if(pfd.dwFlags & PFD_DRAW_TO_WINDOW)      printf("wn ");
    else if(pfd.dwFlags & PFD_DRAW_TO_BITMAP) printf("bm ");
    else printf(".  ");

    /* should find transparent pixel from LAYERPLANEDESCRIPTOR */
    printf(" . ");
    printf("%2d ", pfd.cColorBits);
    /* bReserved field indicates number of over/underlays */

    if(pfd.bReserved) printf(" %d ", pfd.bReserved);
    else printf(" . ");

    printf(" %c ", pfd.iPixelType == PFD_TYPE_RGBA ? 'r' : 'c');
    printf("%c ", pfd.dwFlags & PFD_DOUBLEBUFFER ? 'y' : '.');
    printf(" %c ", pfd.dwFlags & PFD_STEREO ? 'y' : '.');
    if(pfd.cRedBits && pfd.iPixelType == PFD_TYPE_RGBA)
        printf("%2d ", pfd.cRedBits);
    else printf(" . ");

    if(pfd.cGreenBits && pfd.iPixelType == PFD_TYPE_RGBA)

        printf("%2d ", pfd.cGreenBits);

    else printf(" . ");
    if(pfd.cBlueBits && pfd.iPixelType == PFD_TYPE_RGBA)
        printf("%2d ", pfd.cBlueBits);
    else printf(" . ");

    if(pfd.cAlphaBits && pfd.iPixelType == PFD_TYPE_RGBA)
        printf("%2d ", pfd.cAlphaBits);
    else printf(" . ");

    if(pfd.cAuxBuffers)     printf("%2d ", pfd.cAuxBuffers);
    else printf(" . ");

    if(pfd.cDepthBits)      printf("%2d ", pfd.cDepthBits);
    else printf(" . ");

    if(pfd.cStencilBits)    printf("%2d ", pfd.cStencilBits);
    else printf(" . ");

    if(pfd.cAccumRedBits)   printf("%2d ", pfd.cAccumRedBits);
    else printf(" . ");

    if(pfd.cAccumGreenBits) printf("%2d ", pfd.cAccumGreenBits);
    else printf(" . ");

    if(pfd.cAccumBlueBits)  printf("%2d ", pfd.cAccumBlueBits);
    else printf(" . ");

    if(pfd.cAccumAlphaBits) printf("%2d ", pfd.cAccumAlphaBits);
    else printf(" . ");

    /* no multisample in Win32 */
    printf(" . .\n");
    }
    /* print table footer */

 printf("-----------------------------------------------------------------\n");
 printf("   visual  x  bf lv rg d st  r  g  b a  ax dp st accum buffs  ms \n");
 printf(" id dep cl sp sz l  ci b ro sz sz sz sz bf th cl  r  g  b  a ns b\n");
 printf("-----------------------------------------------------------------\n");

    } else {				/* verbose output. */
    /* loop through all the pixel formats */
    for(i = 1; i <= maxpf; i++) {

        DescribePixelFormat(hDC, i, sizeof(PIXELFORMATDESCRIPTOR), &pfd);

        /* only describe this format if it supports OpenGL */
        if(!(pfd.dwFlags & PFD_SUPPORT_OPENGL))
        continue;
        printf("Visual ID: %2d  depth=%d  class=%s\n", i, pfd.cDepthBits,
           pfd.cColorBits <= 8 ? "PseudoColor" : "TrueColor");
        printf("    bufferSize=%d level=%d renderType=%s doubleBuffer=%d stereo=%d\n", pfd.cColorBits, pfd.bReserved, pfd.iPixelType == PFD_TYPE_RGBA ? "rgba" : "ci", pfd.dwFlags & PFD_DOUBLEBUFFER, pfd.dwFlags & PFD_STEREO);
        printf("    rgba: redSize=%d greenSize=%d blueSize=%d alphaSize=%d\n", pfd.cRedBits, pfd.cGreenBits, pfd.cBlueBits, pfd.cAlphaBits);
        printf("    auxBuffers=%d depthSize=%d stencilSize=%d\n", pfd.cAuxBuffers, pfd.cDepthBits, pfd.cStencilBits);
        printf("    accum: redSize=%d greenSize=%d blueSize=%d alphaSize=%d\n", pfd.cAccumRedBits, pfd.cAccumGreenBits, pfd.cAccumBlueBits, pfd.cAccumAlphaBits);
        printf("    multiSample=%d multisampleBuffers=%d\n", 0, 0);
        printf("    Opaque.\n");
    }
    }
}

LONG WINAPI WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static PAINTSTRUCT ps;
    switch(uMsg) {
    case WM_PAINT:
    BeginPaint(hWnd, &ps);
    EndPaint(hWnd, &ps);
    return 0;

    case WM_SIZE:
    glViewport(0, 0, LOWORD(lParam), HIWORD(lParam));
    PostMessage(hWnd, WM_PAINT, 0, 0);
    return 0;

    case WM_CHAR:
    switch (wParam) {
    case 27:			/* ESC key */
        PostQuitMessage(0);
        break;
}
    return 0;
    case WM_CLOSE:
    PostQuitMessage(0);
    return 0;
    }
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

HWND CreateOpenGLWindow(char* title, int x, int y, int width, int height,
           BYTE type, DWORD flags)
{
    int         pf;
    HDC         hDC;
    HWND        hWnd;
    WNDCLASS    wc;
    PIXELFORMATDESCRIPTOR pfd;
    static HINSTANCE hInstance = 0;
    /* only register the window class once - use hInstance as a flag. */
    if (!hInstance) {

    hInstance = GetModuleHandle(NULL);
    wc.style         = CS_OWNDC;
    wc.lpfnWndProc   = (WNDPROC)WindowProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_WINLOGO);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = "OpenGL";

    if (!RegisterClass(&wc)) {
        MessageBox(NULL, "RegisterClass() failed:  "
               "Cannot register window class.", "Error", MB_OK);
        return NULL;
    }
    }

    hWnd = CreateWindow("OpenGL", title, WS_OVERLAPPEDWINDOW |
            WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
            x, y, width, height, NULL, NULL, hInstance, NULL);

    if (hWnd == NULL) {
    MessageBox(NULL, "CreateWindow() failed:  Cannot create a window.",
           "Error", MB_OK);
    return NULL;
    }

    hDC = GetDC(hWnd);

    /* there is no guarantee that the contents of the stack that become
       the pfd are zeroed, therefore _make sure_ to clear these bits. */
    memset(&pfd, 0, sizeof(pfd));
    pfd.nSize        = sizeof(pfd);
    pfd.nVersion     = 1;
    pfd.dwFlags      = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | flags;
    pfd.iPixelType   = type;
    pfd.cColorBits   = 32;

    pf = ChoosePixelFormat(hDC, &pfd);
    if (pf == 0) {

    MessageBox(NULL, "ChoosePixelFormat() failed:  "
           "Cannot find a suitable pixel format.", "Error", MB_OK);
    return 0;
    }

    if (SetPixelFormat(hDC, pf, &pfd) == FALSE) {
    MessageBox(NULL, "SetPixelFormat() failed:  "
           "Cannot set format specified.", "Error", MB_OK);
    return 0;
    }

    DescribePixelFormat(hDC, pf, sizeof(PIXELFORMATDESCRIPTOR), &pfd);

    ReleaseDC(hDC, hWnd);

    return hWnd;
}

int
main(int argc, char** argv)
{
    HDC hDC;				/* device context */
    HGLRC hRC;				/* opengl context */
    HWND  hWnd;				/* window */
    MSG   msg;				/* message */
    int   i;
    char* s;
    char  t[80];
    char* p;
    int verbose = 0;

    while (--argc) {
    if (strcmp("-h", argv[argc]) == 0) {
        printf("Usage: wglinfo [-v] [-t] [-m] [-h] [-dispay ] [-nfbc] [-fpcinfo]\n");
        printf("        -v: Print visuals info in verbose form.\n");
     printf("        -t: Print verbose table (not implemented on Win32)\n");
         printf("        -m: Don't print mid table headers (in long tables). (not on Win32)\n");
         printf("        -display : Print GLX visuals on specified server. (not on Win32)\n");
         printf("        -nfbc: Don't use fbconfig extension (not available on Win32)\n");
         printf("        -fbcinfo: print out additional fbconfig information (not on Win32)\n");
         printf("        -h: This screen.\n");
         exit(0);
     } else if (strcmp("-v", argv[argc]) == 0) {
         verbose = 1;
     }
     }

     hWnd = CreateOpenGLWindow("wglinfo", 0, 0, 100, 100, PFD_TYPE_RGBA, 0);
     if (hWnd == NULL)
     exit(1);

     hDC = GetDC(hWnd);
     hRC = wglCreateContext(hDC);
     wglMakeCurrent(hDC, hRC);

     ShowWindow(hWnd, SW_HIDE);

     /* output header information */
     printf("display: N/A\n");
     printf("server wgl vendor string: N/A\n");
     printf("server wgl version string: N/A\n");
     printf("server wgl extensions (WGL_): N/A\n");
     printf("client wgl version: N/A\n");
     printf("client wgl extensions (WGL_): none\n");
     printf("OpenGL vendor string: %s\n", glGetString(GL_VENDOR));
     printf("OpenGL renderer string: %s\n", glGetString(GL_RENDERER));
     printf("OpenGL version string: %s\n", glGetString(GL_VERSION));
     printf("OpenGL extensions (GL_): \n");

     /* do the magic to separate all extensions with comma's, except
        for the last one that _may_ terminate in a space. */
     i = 0;
     s = (char*)glGetString(GL_EXTENSIONS);
     t[79] = '\0';
     while(*s) {
       t[i++] = *s;
       if(*s == ' ') {
       if (*(s+1) != '\0') {
           t[i-1] = ',';
        t[i] = ' ';
        p = &t[i++];
        } else {	       /* zoinks! last one terminated in a space! */
        t[i-1] = '\0';
        }
    }
    if(i > 80 - 5) {
        *p = t[i] = '\0';
        printf("    %s\n", t);
        p++;
        i = strlen(p);
        strcpy(t, p);
    }
    s++;
    }
    t[i] = '\0';
    printf("    %s.\n\n", t);

    /* enumerate all the formats */
    VisualInfo(hDC, verbose);

    PostQuitMessage(0);
    while(GetMessage(&msg, hWnd, 0, 0)) {
    TranslateMessage(&msg);
 DispatchMessage(&msg);
    }

    wglMakeCurrent(NULL, NULL);
    ReleaseDC(hDC, hWnd);
    wglDeleteContext(hRC);
    DestroyWindow(hWnd);

    return msg.wParam;
}


Exemples OpenGL

Exemples OpenGL Vu dans http://raphaello.univ-fcomte.fr/IG/OpenGL/Exemples GLUt
Avec la librairire GLUt
Un bras robot | Schéma de fonctionnement du glOrtho |
Un carré avec dégradé de couleurs | Schéma de fonctionnement du glFrustum |
Une courbe de Bézier | Schéma de fonctionnement du gluPerspective |
Élimination des parties cachées | Schéma facteur de forme en radiosité |
Les ensembles de facettes | L'hémisphère en radiosité |
Ombrages flat et smooth (uni et lissé) | L'hémicube en radiosité |
Des roues dentées | Valeur des éléments de l'hémicube en radiosité |
L'animation avec la librairie GLUt | L'occulation entre facettes en radiosité |
Une liste d'affichage | Le Z-Buffer sur l'hémicube en radiosité |
Une autre liste d'affichage | Des boules s'entrechoquant |
Les lumières | Un objet en déformation |
Un maillage de Bézier | Une boule bondissante |
Les matériaux | Une sphère spéculaire |
L'utilisation de la souris au sein de la librairie GLUt | Un Tie Bomber (OpengGL 1.1 nécessaire) |
Les NURBS | Un Tie Raptor (OpengGL 1.1 nécessaire) |
Les objets de la librairie GLUt | Un AT-AT de l'Empire (OpengGL 1.1 nécessaire) |
Les primitives graphiques | Une X-Wing ouverte (OpengGL 1.1 nécessaire) |
La visualisation | Un oiseau de proie Klingon (OpengGL 1.1 nécessaire) |
Les projections | Un oiseau de guerre Romulien (OpengGL 1.1 nécessaire) |
L'utilisation de la fonction Reshape au sein de la librairie GLUt | Une police de caractères bitmap |
La sauvegarde d'images en OpenGL | Une police de caractères vectorielle |
Une sphère blanche | Calcul des normales pour Lambert, Gouraud et Phong |
Une surface de Bézier | Problème lié à l'utilisation de normales moyennes pour les calculs d'illumination |
Le survol d'un plan constitué de sphères | Décomposition d'un rayon lumineux incident |
Des teapots | Calcul de la lumière spéculaire |
Un système Terre-Lune | L'utilisation du buffer accumulation pour réaliser de l'anti-aliasing |
Une surface de Bézier texturée | Une animation sur base d'anneaux Olympiques |
Une surface texturée modélisant un échiquier | Des teapots clippés |
L'utilisation des viewports | Le brouillard |
La sélection d'objets | - | -

Avec la librairie Aux
Un bras robot | Décomposition d'un rayon lumineux incident |
Un carré blanc | Calcul de la lumière spéculaire |
Un carré avec dégradé de couleurs | L'utilisation du buffer accumulation pour réaliser de l'anti-aliasing |
Une courbe de Bézier | Une animation sur base d'anneaux Olympiques |
Élimination des parties cachées | Des teapots clippés |
Les ensembles de facettes | Le brouillard |
Ombrages flat et smooth (uni et lissé) | Des boules s'entrechoquant |
Des roues dentées | Un objet en déformation |
L'animation avec la librairie Aux | Une boule bondissante |
Une liste d'affichage | Une sphère spéculaire |
Une autre liste d'affichage | Un Tie Bomber (OpengGL 1.1 nécessaire) |
Les lumières | Un Tie Raptor (OpengGL 1.1 nécessaire) |
Un maillage de Bézier | Un AT-AT de l'Empire (OpengGL 1.1 nécessaire) |
Les matériaux | Une X-Wing ouverte (OpengGL 1.1 nécessaire) |
L'utilisation de la souris au sein de la librairie Aux | Un oiseau de proie Klingon (OpengGL 1.1 nécessaire) |
Les NURBS | Un oiseau de guerre Romulien (OpengGL 1.1 nécessaire) |
Les objets de la librairie Aux | Une police de caractères bitmap |
Les primitives graphiques | Une police de caractères vectorielle |
La visualisation | Des teapots |
Les projections | Un système Terre-Lune |
L'utilisation de la fonction Reshape au sein de la librairie Aux | Une surface de Bézier texturée |
La sauvegarde d'images en OpenGL | Une surface texturée modélisant un échiquier |
Une sphère blanche | L'utilisation des viewports |
Une surface de Bézier | La sélection d'objets |
Calcul des normales pour Lambert, Gouraud et Phong | Le survol d'un plan constitué de sphères |
Problème lié à l'utilisation de normales moyennes pour les calculs d'illumination | - | -

RETOUR


Calcul de la lumière spéculaire

L'exécutable

  • x, X, y, Y, z et Z pour tourner la scène
  • up, down, left, right pour modifier l'orientation du rayon incident

  • jaune : rayon incident
  • rouge : rayon réfléchi
  • vert : axe vers l'observateur
  • bleu : normale
  • texte : valeur du cos de l'angle et valeur de l'angle

Image67.gif (7062 octets)

Le source : LumiereSpeculaire.cpp

#include "windows.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <stdio.h>
#include <math.h>

#include "modulefont.h"

static GLfloat view_rotx=25.0F ;
static GLfloat view_roty=0.0F ;
static GLfloat view_rotz=0.0F ;
static GLfloat px=0.0F ;
static GLfloat pz=0.0F ;
static float ymax ;

void myinit(void) {
  makeRasterFont();
  GLfloat light_ambient[] = { 0.0F,0.0F,0.0F,1.0F };
  GLfloat light_diffuse[] = { 1.0F,1.0F,1.0F,1.0F };
  GLfloat light_specular[] = { 0.0F,0.0F,0.0F,1.0F };
  GLfloat light_position[] = { 0.0F,0.0F,1.0F,0.0F };
  glLightfv(GL_LIGHT0,GL_AMBIENT,light_ambient);
  glLightfv(GL_LIGHT0,GL_DIFFUSE,light_diffuse);
  glLightfv(GL_LIGHT0,GL_SPECULAR,light_specular);
  glLightfv(GL_LIGHT0,GL_POSITION,light_position);
  glEnable(GL_LIGHT0);
  glEnable(GL_AUTO_NORMAL);
  glDepthFunc(GL_LESS);
}

float norme(float x,float y,float z) {
  return((float) sqrt(x*x+y*y+z*z)) ;
}

void CALLBACK display(void) {
  float jaune[] = { 1.0F,1.0F,0.0F,1.0F };
  float blanc[] = { 1.0F,1.0F,1.0F,1.0F };
  float rouge[] = { 1.0F,0.0F,0.0F,1.0F };
  float bleu[] = { 0.0F,0.0F,1.0F,1.0F };
  float vert[] = { 0.0F,1.0F,0.0F,1.0F };
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glEnable(GL_DEPTH_TEST);
  glPushMatrix();
  float rx = px+9.0F ;
  float ry = 11.0F ;
  float rz = pz ;
  float nr = norme(rx,ry,rz) ;
  rx /= nr ;
  ry /= nr ;
  rz /= nr ;
  float ex = 8.0F-px ;
  float ey = 11.0F ;
  float ez = -pz ;
  float ne = norme(ex,ey,ez) ;
  ex /= ne ;
  ey /= ne ;
  ez /= ne ;
  float scal = ex*rx+ey*ry+ez*rz;
  glRotatef(view_rotx,1.0,0.0,0.0);
  glRotatef(view_roty,0.0,1.0,0.0);
  glRotatef(view_rotz,0.0,0.0,1.0);
  glEnable(GL_LIGHTING);
  glPushMatrix();
  glMaterialfv(GL_FRONT,GL_DIFFUSE,jaune) ;
  glTranslatef(-9.0F,5.0F,0.0F) ;
  auxSolidSphere(0.5);
  glPopMatrix();
  glPushMatrix();
  glMaterialfv(GL_FRONT,GL_DIFFUSE,vert) ;
  glTranslatef(8.0F,5.0F,0.0F) ;
  auxSolidSphere(0.5);
  glPopMatrix();
  glPushMatrix();
  glDisable(GL_LIGHTING);
  glColor4fv(jaune) ;
  glBegin(GL_LINES);
  glVertex3f(-9.0F,5.0F,0.0F);
  glVertex3f(px,-6.0F,pz);
  glEnd();
  glColor4fv(rouge) ;
  glBegin(GL_LINES);
  glVertex3f(2*px+9.0F,5.0F,2*pz);
  glVertex3f(px,-6.0F,pz);
  glEnd();
  glColor4fv(bleu) ;
  glBegin(GL_LINES);
  glVertex3f(px,8.0F,pz);
  glVertex3f(px,-6.0F,pz);
  glEnd();
  glColor4fv(vert) ;
  glBegin(GL_LINES);
  glVertex3f(8.0F,5.0F,0.0F);
  glVertex3f(px,-6.0F,pz);
  glEnd();
  glEnable(GL_LIGHTING);
  glPopMatrix();
  glPushMatrix();
  glMaterialfv(GL_FRONT,GL_DIFFUSE,blanc) ;
  glTranslatef(0.0F,-6.25F,0.0F) ;
  auxSolidBox(16.0,0.5,14.0) ;
  glPopMatrix();
  glPopMatrix();
  glDisable(GL_LIGHTING);
  GLfloat white[4] = { 1.0,1.0,1.0,1.0 };
  glDisable(GL_DEPTH_TEST);
  glColor4fv(white);
  glRasterPos3f(-10,ymax-1,0);
  char st[50] ;
  sprintf(st,"%f -> %lf degres",scal,acos(scal)*180/3.14159);
  printString(st);
  glFlush();
  auxSwapBuffers();
}

void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  ymax = h*11.0F/w ;
  glOrtho(-11,11,-h*11.0/w,ymax,-15.0,15.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void CALLBACK keyLeft(void) {
  px -= 0.1F ;
  if ( px < -6.0F )
    px = -6.0F ;
}

void CALLBACK keyRight(void) {
  px += 0.1F ;
  if ( px > 6.0F )
    px = 6.0F ;
}

void CALLBACK keyUp(void) {
  pz -= 0.1F ;
  if ( pz < -6.0F )
    pz = -6.0F ;
}

void CALLBACK keyDown(void) {
  pz += 0.1F ;
  if ( pz > 6.0F )
    pz = 6.0F ;
}

void CALLBACK keyx(void) {
  view_rotx++ ;
}

void CALLBACK keyX(void) {
  view_rotx-- ;
}

void CALLBACK keyy(void) {
  view_roty++ ;
}

void CALLBACK keyY(void) {
  view_roty-- ;
}

void CALLBACK keyz(void) {
  view_rotz++ ;
}

void CALLBACK keyZ(void) {
  view_rotz-- ;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGB|AUX_DEPTH);
  auxInitPosition(0,0,300,300);
  auxInitWindow("Réflexion spéculaire");
  myinit();
  auxKeyFunc(AUX_LEFT,keyLeft) ;
  auxKeyFunc(AUX_RIGHT,keyRight) ;
  auxKeyFunc(AUX_UP,keyUp) ;
  auxKeyFunc(AUX_DOWN,keyDown) ;
  auxKeyFunc(AUX_x,keyx) ;
  auxKeyFunc(AUX_X,keyX) ;
  auxKeyFunc(AUX_y,keyy) ;
  auxKeyFunc(AUX_Y,keyY) ;
  auxKeyFunc(AUX_z,keyz) ;
  auxKeyFunc(AUX_Z,keyZ) ;
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}

Le source : ModuleFont.cpp

Le source : ModuleFont.h

  WB01624_.gif (281 octets)  RETOUR


Calcul des normales moyennes pour Lambert, Gouraud et Phong

L'exécutable

Le source: NormalesMoyennes.cpp
#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <math.h>

static GLfloat view_rotx=-90.0 ;
static GLfloat view_roty=0.0 ;
static GLfloat view_rotz=0.0;
static int s = 0 ;

void myinit(void) {
  GLfloat specular[] = { 0.0,0.0,0.0,0.0 };
  GLfloat shinines[] = { 0.0 };
  GLfloat l_pos[] = { 1.0,1.0,1.0,0.0 };
  glClearColor(1.0,1.0,1.0,1.0) ;
  glMaterialfv(GL_FRONT,GL_SPECULAR,specular);
  glMaterialfv(GL_FRONT,GL_SHININESS,shinines);
  glLightfv(GL_LIGHT0,GL_POSITION,l_pos);
  glEnable(GL_LIGHT0);
  glDepthFunc(GL_LESS);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_NORMALIZE);
}

void normal(float px,float py,float pz,float dx,float dy,float dz) {
  float vx = dy ;
  float vy = -dx ;
  float vz = 0.0F ;
  float d =(float) sqrt(dy*dy+dx*dx) ;
  vx /= d ;
  vy /= d ;
  float vx1 = (2.0F*dx+vx)/3.0F ;
  float vy1 = (2.0F*dy+vy)/3.0F ;
  float vz1 = (2.0F*dz+vz)/3.0F ;
  float vx2 = (2.0F*dx-vx)/3.0F ;
  float vy2 = (2.0F*dy-vy)/3.0F ;
  float vz2 = (2.0F*dz-vz)/3.0F ;
  glPushMatrix() ;
  glTranslatef(px,py,pz) ;
  glBegin(GL_LINES) ;
  glVertex3f(0.0F,0.0F,0.0F) ;
  glVertex3f(dx,dy,dz) ;
  glVertex3f(dx,dy,dz) ;
  glVertex3f(vx1,vy1,vz1) ;
  glVertex3f(dx,dy,dz) ;
  glVertex3f(vx2,vy2,vz2) ;
  glEnd() ;
  glPopMatrix() ;
}

void scene1(void) {
  GLfloat noir[] = { 0.0F,0.0F,0.0F,1.0F };
  GLfloat rouge[] = { 1.0F,0.0F,0.0F,1.0F };
  GLfloat vert[] = { 0.0F,1.0F,0.0F,1.0F };
  GLfloat gris[] = { 0.7F,0.7F,0.7F,1.0F };
  glMaterialfv(GL_FRONT,GL_DIFFUSE,gris);
  glEnable(GL_LIGHTING);
  glPushMatrix();
  float p[] = { 0.0F,0.0F,0.0F } ;
  float p1[] = { -5.0F,-4.0F,-3.0F } ;
  float p2[] = { 1.0F,-5.0F,-4.0F } ;
  float p3[] = { 4.0F,1.0F,-5.0F } ;
  float p4[] = { 2.0F,5.0F,-4.0F } ;
  float p5[] = { -2.0F,4.0F,-4.0F } ;
  glRotatef(view_rotx,1.0,0.0,0.0);
  glRotatef(view_roty,0.0,1.0,0.0);
  glRotatef(view_rotz,0.0,0.0,1.0);
  glBegin(GL_TRIANGLES) ;
  glNormal3f(0.94F,0.26F,2.84F) ;
  glVertex3fv(p) ;
  glNormal3f(-1.19F,-0.53F,2.7F) ;
  glVertex3fv(p1) ;
  glNormal3f(1.33F,-1.52F,2.22F) ;
  glVertex3fv(p2) ;
  glNormal3f(0.94F,0.26F,2.84F) ;
  glVertex3fv(p) ;
  glNormal3f(1.33F,-1.52F,2.22F) ;
  glVertex3fv(p2) ;
  glNormal3f(2.35F,-0.12F,1.86F) ;
  glVertex3fv(p3) ;
  glNormal3f(0.94F,0.26F,2.84F) ;
  glVertex3fv(p) ;
  glNormal3f(2.35F,-0.12F,1.86F) ;
  glVertex3fv(p3) ;
  glNormal3f(1.46F,1.4F,2.21F) ;
  glVertex3fv(p4) ;
  glNormal3f(0.94F,0.26F,2.84F) ;
  glVertex3fv(p) ;
  glNormal3f(1.46F,1.4F,2.21F) ;
  glVertex3fv(p4) ;
  glNormal3f(-0.84F,1.66F,2.35F) ;
  glVertex3fv(p5) ;
  glNormal3f(0.94F,0.26F,2.84F) ;
  glVertex3fv(p) ;
  glNormal3f(-0.84F,1.66F,2.35F) ;
  glVertex3fv(p5) ;
  glNormal3f(-1.19F,-0.53F,2.7F) ;
  glVertex3fv(p1) ;
  glEnd() ;
  glMaterialfv(GL_FRONT,GL_DIFFUSE,noir);
  glLineWidth(2.0) ;
  glDisable(GL_LIGHTING);
  glColor3f(0.0F,0.0F,1.0F);
  normal(-1.33F,-3.0F,-2.33F,0.081F,-1.86F,2.34F) ;
  normal(1.66F,-1.33F,-3.0F,2.32F,-0.88F,1.68F) ;
  normal(2.0F,2.0F,-3.0F,2.23F,0.64F,1.91F) ;
  normal(0.0F,3.0F,-2.66F,0.49F,1.97F,2.21F) ;
  normal(-2.33F,0.0F,-2.33F,-2.0F,1.0F,2.0F) ;
  glColor3f(1.0F,0.0F,0.0F);
  normal(0.0F,0.0F,0.0F,0.94F,0.26F,2.84F) ;
  glColor3f(0.0F,1.0F,0.0F);
  normal(p1[0],p1[1],p1[2],-1.19F,-0.53F,2.7F) ;
  normal(p2[0],p2[1],p2[2],1.33F,-1.52F,2.22F) ;
  normal(p3[0],p3[1],p3[2],2.35F,-0.12F,1.86F) ;
  normal(p4[0],p4[1],p4[2],1.46F,1.4F,2.21F) ;
  normal(p5[0],p5[1],p5[2],-0.84F,1.66F,2.35F) ;
  glPopMatrix();
}

void scene2(void) {
  GLfloat noir[] = { 0.0F,0.0F,0.0F,1.0F };
  GLfloat gris[] = { 0.7F,0.7F,0.7F,1.0F };
  glMaterialfv(GL_FRONT,GL_DIFFUSE,gris);
  glEnable(GL_LIGHTING);
  glPushMatrix();
  float p[] = { 0.0F,0.0F,0.0F } ;
  float p1[] = { -5.0F,-4.0F,-3.0F } ;
  float p2[] = { 1.0F,-5.0F,-4.0F } ;
  float p3[] = { 4.0F,1.0F,-5.0F } ;
  float p4[] = { 2.0F,5.0F,-4.0F } ;
  float p5[] = { -2.0F,4.0F,-4.0F } ;
  glRotatef(view_rotx,1.0,0.0,0.0);
  glRotatef(view_roty,0.0,1.0,0.0);
  glRotatef(view_rotz,0.0,0.0,1.0);
  glBegin(GL_TRIANGLES) ;
  glNormal3f(0.081F,-1.86F,2.34F) ;
  glVertex3fv(p) ;
  glVertex3fv(p1) ;
  glVertex3fv(p2) ;
  glNormal3f(2.32F,-0.88F,1.68F) ;
  glVertex3fv(p) ;
  glVertex3fv(p2) ;
  glVertex3fv(p3) ;
  glNormal3f(2.23F,0.64F,1.91F) ;
  glVertex3fv(p) ;
  glVertex3fv(p3) ;
  glVertex3fv(p4) ;
  glNormal3f(0.49F,1.97F,2.21F) ;
  glVertex3fv(p) ;
  glVertex3fv(p4) ;
  glVertex3fv(p5) ;
  glNormal3f(-2.0F,1.0F,2.0F) ;
  glVertex3fv(p) ;
  glVertex3fv(p5) ;
  glVertex3fv(p1) ;
  glEnd() ;
  glMaterialfv(GL_FRONT,GL_DIFFUSE,noir);
  glLineWidth(2.0) ;
  glDisable(GL_LIGHTING);
  glColor3f(0.0F,0.0F,1.0F);
  normal(-1.33F,-3.0F,-2.33F,0.081F,-1.86F,2.34F) ;
  normal(1.66F,-1.33F,-3.0F,2.32F,-0.88F,1.68F) ;
  normal(2.0F,2.0F,-3.0F,2.23F,0.64F,1.91F) ;
  normal(0.0F,3.0F,-2.66F,0.49F,1.97F,2.21F) ;
  normal(-2.33F,0.0F,-2.33F,-2.0F,1.0F,2.0F) ;
  glColor3f(1.0F,0.0F,0.0F);
  normal(0.0F,0.0F,0.0F,0.94F,0.26F,2.84F) ;
  glColor3f(0.0F,1.0F,0.0F);
  normal(p1[0],p1[1],p1[2],-1.19F,-0.53F,2.7F) ;
  normal(p2[0],p2[1],p2[2],1.33F,-1.52F,2.22F) ;
  normal(p3[0],p3[1],p3[2],2.35F,-0.12F,1.86F) ;
  normal(p4[0],p4[1],p4[2],1.46F,1.4F,2.21F) ;
  normal(p5[0],p5[1],p5[2],-0.84F,1.66F,2.35F) ;
  glPopMatrix();
}

void CALLBACK display(void) {
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  if ( s )
    scene1() ;
    else
    scene2() ;
  glFlush();
  auxSwapBuffers();
}

void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(-8.5,8.5,-8.5*(float)h/(float)w,8.5*(float)h/(float)w,-8,8);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef(0.0F,1.0F,0.0F);
}

void CALLBACK up(void) {
  view_rotx++ ;
}

void CALLBACK down(void) {
  view_rotx-- ;
}

void CALLBACK left(void) {
  view_roty++ ;
}

void CALLBACK right(void) {
  view_roty-- ;
}

void CALLBACK keyz(void) {
  view_rotz++ ;
}

void CALLBACK keyZ(void) {
  view_rotz-- ;
}

void CALLBACK keySpace() {
  s = 1 - s ;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGB|AUX_DEPTH);
  auxInitPosition(0,0,350,250);
  auxInitWindow("Calcul des normales moyennes");
  myinit();
  auxKeyFunc(AUX_UP,up) ;
  auxKeyFunc(AUX_DOWN,down) ;
  auxKeyFunc(AUX_LEFT,left) ;
  auxKeyFunc(AUX_RIGHT,right) ;
  auxKeyFunc(AUX_z,keyz) ;
  auxKeyFunc(AUX_Z,keyZ) ;
  auxKeyFunc(AUX_SPACE,keySpace) ;
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}

RETOUR


Composantes lumineuses générées au niveau d'une interface

L'exécutable

  • x, X, y, Y, z et Z pour tourner la scène
  • n et N pour modifier le facteur n
  • up, down, left, right, u et d pour modifier l'orientation du rayon incident

  • jaune : rayon incident
  • rouge : rayon réfléchi
  • bleu : rayon transmis
  • vert : lumière diffusée
  • magenta : normale
  • n : ni/nt

Image67.gif (7062 octets)

Le source : Composantes.cpp

Le source : ModuleFont.cpp

Le source : ModuleFont.h

#include "windows.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <stdio.h>
#include <math.h>

#include "modulefont.h"

static GLfloat view_rotx=25.0F ;
static GLfloat view_roty=0.0F ;
static GLfloat view_rotz=0.0F ;
static GLfloat px=-9.0F ;
static GLfloat py=9.0F ;
static GLfloat pz=0.0F ;
static GLfloat nfact=1.0F ;
static float ymax ;

void myinit(void) {
  GLfloat light_ambient[] = { 0.0F,0.0F,0.0F,1.0F };
  GLfloat light_diffuse[] = { 1.0F,1.0F,1.0F,1.0F };
  GLfloat light_specular[] = { 0.0F,0.0F,0.0F,1.0F };
  GLfloat light_position0[] = { 0.0F,0.0F,1.0F,0.0F };
  GLfloat light_position1[] = { 0.0F,0.0F,1.0F,0.0F };
  glLightfv(GL_LIGHT0,GL_AMBIENT,light_ambient);
  glLightfv(GL_LIGHT0,GL_DIFFUSE,light_diffuse);
  glLightfv(GL_LIGHT1,GL_DIFFUSE,light_diffuse);
  glLightfv(GL_LIGHT0,GL_SPECULAR,light_specular);
  glLightfv(GL_LIGHT0,GL_POSITION,light_position0);
  glLightfv(GL_LIGHT1,GL_POSITION,light_position1);
  glEnable(GL_LIGHT0);
  glEnable(GL_LIGHT1);
  glEnable(GL_AUTO_NORMAL);
  glDepthFunc(GL_LESS);
  glClearAccum(0.0,0.0,0.0,0.0);
  makeRasterFont();
}

float norme(float x,float y,float z) {
  return((float) sqrt(x*x+y*y+z*z)) ;
}

void normalise(float *x,float *y,float *z) {
  float n = norme(*x,*y,*z);
  *x /= n ;
  *y /= n ;
  *z /= n ;
}

void reflexion(float px,float py,float pz) {
  float rouge[] = { 1.0F,0.0F,0.0F,1.0F };
  glColor4fv(rouge) ;
  glBegin(GL_LINES);
  float rx = -px ;
  float ry = py ;
  float rz = -pz ;
  normalise(&rx,&ry,&rz) ;
  glVertex3f(10*rx,10*ry,10*rz);
  glVertex3f(0.0F,0.0F,0.0F);
  glEnd();
}

void diffusion(float px,float py,float pz) {
  float vert[] = { 0.0F,0.4F,0.0F,1.0F };
  px *= -1 ;
  py *= -1 ;
  pz *= -1 ;
  normalise(&px,&py,&pz) ;
  float d = -py ;
  glColor4fv(vert) ;
  glBegin(GL_LINES);
  for ( int j = 15 ; j < 90 ; j+= 30 ) {
    float anglex = j * 3.14159F / 180.0F ;
    float daz = (((j-15)/30)%2 == 0) ? 20.0F : 0.0F ;
    for ( int i = 0 ; i < 360 ; i+= 40 ) {
      float anglez = (i+daz) * 3.14159F / 180.0F ;
      float x =(float) (cos(anglez)*cos(anglex)*5*d) ;
      float z =(float) (sin(anglez)*cos(anglex)*5*d) ;
      float y =(float) (sin(anglex)*5*d) ;
      glVertex3f(0.0F,0.0F,0.0F) ;
      glVertex3f(x,y,z) ; } }
  glEnd();
}

float produitScalaire(float ix,float iy, float iz,float nx,float ny, float nz) {
  return(ix*nx+iy*ny+iz*nz) ;
}

void transmission(float px,float py,float pz) {
  float bleu[] = { 0.0F,0.0F,1.0F,1.0F };
  float ix = px ;
  float iy = py ;
  float iz = pz ;
  normalise(&ix,&iy,&iz) ;
  float nx = 0.0F ;
  float ny = 1.0F ;
  float nz = 0.0F ;
  float ni = produitScalaire(ix,iy,iz,nx,ny,nz) ;
  float t = nfact*ni-(float) sqrt(1-nfact*nfact*(1-ni*ni)) ;
  float tx = t*nx - nfact*ix ;
  float ty = t*ny - nfact*iy ;
  float tz = t*nz - nfact*iz ;
  glColor4fv(bleu) ;
  glBegin(GL_LINES);
  glVertex3f(10*tx,10*ty,10*tz);
  glVertex3f(0.0F,0.0F,0.0F);
  glEnd();
}

void displayRayons(void) {
  float jaune[] = { 1.0F,1.0F,0.0F,1.0F };
  float blanc[] = { 1.0F,1.0F,1.0F,1.0F };
  float magenta[] = { 1.0F,0.0F,1.0F,1.0F };
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  glRotatef(view_rotx,1.0,0.0,0.0);
  glRotatef(view_roty,0.0,1.0,0.0);
  glRotatef(view_rotz,0.0,0.0,1.0);
  glPushMatrix();
  glColor4fv(jaune) ;
  glBegin(GL_LINES);
  glVertex3f(px,py,pz);
  glVertex3f(0.0F,0.0F,0.0F);
  glEnd();
  glColor4fv(magenta) ;
  glBegin(GL_LINES);
  glVertex3f(0.0F,10.0F,0.0F);
  glVertex3f(0.0F,0.0F,0.0F);
  glEnd();
  glPopMatrix();
  glEnable(GL_LIGHTING);
  glPushMatrix();
  glMaterialfv(GL_FRONT,GL_DIFFUSE,jaune) ;
  glTranslatef(px,py,pz) ;
  auxSolidSphere(0.5);
  glPopMatrix();
  glDisable(GL_LIGHTING);
  reflexion(px,py,pz);
  diffusion(px,py,pz);
  transmission(px,py,pz);
  glPopMatrix();
  glDisable(GL_DEPTH_TEST);
  GLfloat white[4] = { 1.0,1.0,1.0,1.0 };
  glColor4fv(white);
  glRasterPos3f(-10,ymax-1,0);
  char st[50] ;
  sprintf(st,"n = %f",nfact);
  printString(st);
  glAccum(GL_ACCUM,0.8F);
}

void displayInterface(void) {
  float blanc[] = { 1.0F,1.0F,1.0F,0.5F };
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  glRotatef(view_rotx,1.0,0.0,0.0);
  glRotatef(view_roty,0.0,1.0,0.0);
  glRotatef(view_rotz,0.0,0.0,1.0);
  glEnable(GL_LIGHTING);
  glPushMatrix();
  glMaterialfv(GL_FRONT,GL_DIFFUSE,blanc) ;
  glTranslatef(0.0F,0.0F,0.0F) ;
  auxSolidBox(16.0,0.2,14.0) ;
  glPopMatrix();
  glDisable(GL_LIGHTING);
  glPopMatrix();
  glAccum(GL_ACCUM,0.2F);
}

void CALLBACK display(void) {
  glPushMatrix() ;
  glEnable(GL_DEPTH_TEST);
  glClear(GL_ACCUM_BUFFER_BIT);
  displayInterface() ;
  displayRayons() ;
  glAccum(GL_RETURN,1.0);
  glPopMatrix() ;
  glFlush();
  auxSwapBuffers() ;
}
void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  ymax = h*11.0F/w ;
  glOrtho(-11,11,-h*11.0/w,h*11.0/w,-15.0,15.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void CALLBACK keyLeft(void) {
  px -= 0.2F ;
  if ( px < -9.0F )
    px = -9.0F ;
}

void CALLBACK keyRight(void) {
  px += 0.2F ;
  if ( px > 9.0F )
    px = 9.0F ;
}

void CALLBACK keyPageUp(void) {
  py += 0.2F ;
  if ( py > 9.0F )
    py = 9.0F ;
}

void CALLBACK keyPageDown(void) {
  py -= 0.2F ;
  if ( py < -9.0F )
    py = -9.0F ;
}

void CALLBACK keyUp(void) {
  pz -= 0.2F ;
  if ( pz < -9.0F )
    pz = -9.0F ;
}

void CALLBACK keyDown(void) {
  pz += 0.2F ;
  if ( pz > 9.0F )
    pz = 9.0F ;
}

void CALLBACK keyx(void) {
  view_rotx += 2 ;
}

void CALLBACK keyX(void) {
  view_rotx -= 2 ;
}

void CALLBACK keyy(void) {
  view_roty += 2 ;
}

void CALLBACK keyY(void) {
  view_roty -= 2 ;
}

void CALLBACK keyz(void) {
  view_rotz += 2 ;
}

void CALLBACK keyZ(void) {
  view_rotz -= 2 ;
}

void CALLBACK keyn(void) {
  nfact *= 1.01F ;
}

void CALLBACK keyN(void) {
  nfact /= 1.01F ;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGB|AUX_DEPTH|AUX_ACCUM);
  auxInitPosition(0,0,300,300);
  auxInitWindow("Décomposition au niveau d'une interface");
  myinit();
  auxKeyFunc(AUX_U,keyPageUp) ;
  auxKeyFunc(AUX_u,keyPageUp) ;
  auxKeyFunc(AUX_D,keyPageDown) ;
  auxKeyFunc(AUX_d,keyPageDown) ;
  auxKeyFunc(AUX_LEFT,keyLeft) ;
  auxKeyFunc(AUX_RIGHT,keyRight) ;
  auxKeyFunc(AUX_UP,keyUp) ;
  auxKeyFunc(AUX_DOWN,keyDown) ;
  auxKeyFunc(AUX_x,keyx) ;
  auxKeyFunc(AUX_X,keyX) ;
  auxKeyFunc(AUX_y,keyy) ;
  auxKeyFunc(AUX_Y,keyY) ;
  auxKeyFunc(AUX_z,keyz) ;
  auxKeyFunc(AUX_Z,keyZ) ;
  auxKeyFunc(AUX_n,keyn) ;
  auxKeyFunc(AUX_N,keyN) ;
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
  WB01624_.gif (281 octets)  RETOUR


Configuration du viewport

L'exécutable

  • x et X pour régler la position du viewport en x
  • y et Y pour régler la position du viewport en y
  • a et A pour régler la taille du viewport en x
  • b et B pour régler la taille du viewport en y
  • v et V pour autoriser les changements de viewport
  • z et Z pour régler le zoom
  • o et O pour régler l'ouverture en perspective
  • p et P pour passer de la perspective à la projection orthographique
  • up, down, left et right pour les contrôles d'orientation

Utilisation de toute la surface de la fenêtre d'affichage

Utilisation d'une partie seulement de la fenêtre d'affichage

Le source: Viewport.cpp

#include <windows.h>

#include <stdio.h>
#include <stdlib.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include "GL/glaux.h"

static int proj = 0 ;
static int vp = 0 ;
static int vx = 0 ;
static int vy = 0 ;
static int vdx = 200 ;
static int vdy = 200 ;
static int ww ;
static int hh ;
static float anglex = 0.0F ;
static float angley = 0.0F ;
static float zoomPersp = 1.0F ;
static float zoomParal = 1.0F ;
static float ouverture = 1.0F ;

void projection(void) {
  if ( vp )
    glViewport(0,0,ww,hh);
    else
    glViewport(vx,vy,vdx,vdy);
  if ( proj ) {
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0*ouverture,(double) ww/hh,0.5,200.0);
    glMatrixMode(GL_MODELVIEW);
    glTranslatef(0.0F,0.0F,-5.0F*zoomPersp); }
    else {
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-2.5*zoomParal,2.5*zoomParal,-2.5*zoomParal*(double) hh/ww,2.5*zoomParal*(double) hh/ww,-25,25);
    glMatrixMode(GL_MODELVIEW);
    glTranslatef(0.0F,0.0F,-5.0F); }
}

void CALLBACK display(void) {
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  glColor3f(1.0F,1.0F,1.0F);
  projection();
  glPushMatrix();
  glRotatef(anglex,1.0F,0.0F,0.0F);
  glRotatef(angley,0.0F,1.0F,0.0F);
  glTranslatef(-1.0F,0.0F,0.0F) ;
  float jaune[] = { 1.0F,1.0F,0.0F };
  glMaterialfv(GL_FRONT,GL_DIFFUSE,jaune);
  auxSolidCube(2.0);
  glTranslatef(2.0F,0.0F,0.0F) ;
  float bleu[] = { 0.0F,0.0F,1.0F };
  glMaterialfv(GL_FRONT,GL_DIFFUSE,bleu);
  auxSolidSphere(1.3);
  glPopMatrix();
  glPopMatrix();
  glFlush();
  auxSwapBuffers() ;
}

void CALLBACK myinit(void) {
   glShadeModel(GL_SMOOTH);
  glDepthFunc(GL_LESS) ;
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
}

void CALLBACK myReshape(int w, int h) {
  ww = w ;
  hh = h ;
}

void CALLBACK keyo(void) {
  ouverture *= 1.01F ;
  ouverture *= 1.01F ;
}

void CALLBACK keyO(void) {
  ouverture /= 1.01F ;
  ouverture /= 1.01F ;
}

void CALLBACK keyz(void) {
  zoomPersp *= 1.01F ;
  zoomParal *= 1.01F ;
}

void CALLBACK keyZ(void) {
  zoomPersp /= 1.01F ;
  zoomParal /= 1.01F ;
}

void CALLBACK keyP(void) {
  proj++;
  proj %= 2;
}

void CALLBACK keyp(void) {
  proj++;
  proj %= 2;
}

void CALLBACK keyV(void) {
  vp++;
  vp %= 2;
}

void CALLBACK keyv(void) {
  vp++;
  vp %= 2;
}

void CALLBACK keyX(void) {
  vx++ ;
}

void CALLBACK keyx(void) {
  vx-- ;
}

void CALLBACK keyY(void) {
  vy++ ;
}

void CALLBACK keyy(void) {
  vy-- ;
}

void CALLBACK keyA(void) {
  vdx++ ;
}

void CALLBACK keya(void) {
  vdx-- ;
}

void CALLBACK keyB(void) {
  vdy++ ;
}

void CALLBACK keyb(void) {
  vdy-- ;
}

void CALLBACK up(void) {
  anglex++ ;
}

void CALLBACK down(void) {
  anglex-- ;
}

void CALLBACK left(void) {
  angley++ ;
}

void CALLBACK right(void) {
  angley-- ;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGBA|AUX_DEPTH);
  auxInitPosition(0,0,200,200);
  auxInitWindow("Visualisations");
  myinit();
  auxKeyFunc(AUX_X,keyX);
  auxKeyFunc(AUX_x,keyx);
  auxKeyFunc(AUX_Y,keyY);
  auxKeyFunc(AUX_y,keyy);
  auxKeyFunc(AUX_A,keyA);
  auxKeyFunc(AUX_a,keya);
  auxKeyFunc(AUX_B,keyB);
  auxKeyFunc(AUX_b,keyb);
  auxKeyFunc(AUX_O,keyO);
  auxKeyFunc(AUX_o,keyo);
  auxKeyFunc(AUX_V,keyV);
  auxKeyFunc(AUX_v,keyv);
  auxKeyFunc(AUX_P,keyP);
  auxKeyFunc(AUX_p,keyp);
  auxKeyFunc(AUX_Z,keyZ);
  auxKeyFunc(AUX_z,keyz);
  auxKeyFunc(AUX_UP,up) ;
  auxKeyFunc(AUX_DOWN,down) ;
  auxKeyFunc(AUX_LEFT,left) ;
  auxKeyFunc(AUX_RIGHT,right) ;
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Des anneaux olympiques en construction

L'exécutable

Le source: Olympic.cpp
#include "windows.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <sys/types.h>

static float anglex = 0.0F ;
static float angley = 0.0F ;

#ifndef RAND_MAX
#  define RAND_MAX 32767
#endif

#define XSIZE  100
#define YSIZE  75

#define RINGS 5
#define BLUERING 0
#define BLACKRING 1
#define REDRING 2
#define YELLOWRING 3
#define GREENRING 4

#define BACKGROUND 8

enum {
  BLACK = 0,
  RED,
  GREEN,
  YELLOW,
  BLUE,
  MAGENTA,
  CYAN,
  WHITE
};

unsigned char rgb_colors[RINGS][3];
int mapped_colors[RINGS];
float dests[RINGS][3];
float offsets[RINGS][3];
float angs[RINGS];
float rotAxis[RINGS][3];
int iters[RINGS];
GLuint theTorus;

void FillTorus(float rc, int numc, float rt, int numt) {
  int i, j, k;
  double s, t;
  double x, y, z;
  double pi, twopi;
  pi = 3.14159265358979323846;
  twopi= 2 * pi;
  for (i = 0; i < numc; i++) {
    glBegin(GL_QUAD_STRIP);
    for (j = 0; j <= numt; j++) {
      for (k = 1; k >= 0; k--) {
        s = (i + k) % numc + 0.5;
        t = j % numt;
        x = cos(t*twopi/numt) * cos(s*twopi/numc);
        y = sin(t*twopi/numt) * cos(s*twopi/numc);
        z = sin(s*twopi/numc);
        glNormal3f((float) x,(float) y,(float) z);
        x = (rt + rc * cos(s*twopi/numc)) * cos(t*twopi/numt);
        y = (rt + rc * cos(s*twopi/numc)) * sin(t*twopi/numt);
        z = rc * sin(s*twopi/numc);
        glVertex3f((float) x,(float) y,(float) z); } }
    glEnd(); }
}

float Clamp(int iters_left, float t) {
  if (iters_left < 3)
    return 0.0;
  return (iters_left-2)*t/iters_left;
}

void CALLBACK DrawScene(void) {
  int i, j;
  GLboolean goIdle;
  goIdle = GL_TRUE;
  for ( i = 0 ; i < RINGS ; i++ ) {
    if ( iters[i] ) {
      for ( j = 0 ; j < 3 ; j++ )
        offsets[i][j] = Clamp(iters[i], offsets[i][j]);
      angs[i] = Clamp(iters[i], angs[i]);
      iters[i]--;
      goIdle = GL_FALSE; } }
  if (goIdle)
     auxIdleFunc(NULL);
  glPushMatrix();
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  gluLookAt(0,0,10, 0,0,0, 0,1,0);
  for ( i = 0 ; i < RINGS ; i++ ) {
    glColor3ubv(rgb_colors[i]);
    glPushMatrix();
    glTranslatef(dests[i][0]+offsets[i][0], dests[i][1]+offsets[i][1],dests[i][2]+offsets[i][2]);
    glRotatef(angs[i], rotAxis[i][0], rotAxis[i][1], rotAxis[i][2]);
    glCallList(theTorus);
    glPopMatrix(); }
  glPopMatrix();
  glFlush();
  auxSwapBuffers();
}

float MyRand(void) {
  return 10.0F * ( (float) rand() / (float) RAND_MAX - 0.5F );
}

void ReInit(void) {
  int i;
  float deviation;
  deviation = MyRand() / 2;
  deviation = deviation * deviation;
  for (i = 0; i < RINGS; i++) {
    offsets[i][0] = MyRand();
    offsets[i][1] = MyRand();
    offsets[i][2] = MyRand();
    angs[i] = 260.0F * MyRand();
    rotAxis[i][0] = MyRand();
    rotAxis[i][1] = MyRand();
    rotAxis[i][2] = MyRand();
    iters[i] =(int) ( deviation * MyRand() + 60.0F); }
  auxIdleFunc(DrawScene);
}

void Init(void) {
  float base, height;
  float aspect, x, y;
  int i;
  float sc = 10;
  float top_y = 1.0F;
  float bottom_y = 0.0F;
  float top_z = 0.15F;
  float bottom_z = 0.69F;
  float spacing = 2.5F;
  static float lmodel_ambient[] = {0.0, 0.0, 0.0, 0.0};
  static float lmodel_twoside[] = {GL_FALSE};
  static float lmodel_local[] = {GL_FALSE};
  static float light0_ambient[] = {0.1F, 0.1F, 0.1F, 1.0F};
  static float light0_diffuse[] = {1.0F, 1.0F, 1.0F, 0.0F};
  static float light0_position[] = {0.8660254F, 0.5F, 1.0F, 0.0F};
  static float light0_specular[] = {1.0F, 1.0F, 1.0F, 0.0F};
  static float bevel_mat_ambient[] = {0.0F, 0.0F, 0.0F, 1.0F};
  static float bevel_mat_shininess[] = {40.0F};
  static float bevel_mat_specular[] = {1.0F, 1.0F, 1.0F, 0.0F};
  static float bevel_mat_diffuse[] = {1.0F, 0.0F, 0.0F, 0.0F};
  ReInit();
  for (i = 0; i < RINGS; i++)
    rgb_colors[i][0] = rgb_colors[i][1] = rgb_colors[i][2] = 0;
  rgb_colors[BLUERING][2] = 255;
  rgb_colors[REDRING][0] = 255;
  rgb_colors[GREENRING][1] = 255;
  rgb_colors[YELLOWRING][0] = 255;
  rgb_colors[YELLOWRING][1] = 255;
  mapped_colors[BLUERING] = BLUE;
  mapped_colors[REDRING] = RED;
  mapped_colors[GREENRING] = GREEN;
  mapped_colors[YELLOWRING] = YELLOW;
  mapped_colors[BLACKRING] = BLACK;
  dests[BLUERING][0] = -spacing;
  dests[BLUERING][1] = top_y;
  dests[BLUERING][2] = top_z;
  dests[BLACKRING][0] = 0.0;
  dests[BLACKRING][1] = top_y;
  dests[BLACKRING][2] = top_z;
  dests[REDRING][0] = spacing;
  dests[REDRING][1] = top_y;
  dests[REDRING][2] = top_z;
  dests[YELLOWRING][0] = -spacing / 2.0F;
  dests[YELLOWRING][1] = bottom_y;
  dests[YELLOWRING][2] = bottom_z;
  dests[GREENRING][0] = spacing / 2.0F;
  dests[GREENRING][1] = bottom_y;
  dests[GREENRING][2] = bottom_z;
  base = 2.0;
  height = 2.0;
  theTorus = glGenLists(1);
  glNewList(theTorus, GL_COMPILE);
  FillTorus(0.1F, 8, 1.0F, 25);
  glEndList();
  x = (float)XSIZE;
  y = (float)YSIZE;
  aspect = x / y;
  glEnable(GL_CULL_FACE);
  glCullFace(GL_BACK);
  glEnable(GL_DEPTH_TEST);
  glClearDepth(1.0);
  glClearColor(0.5, 0.5, 0.5, 0.0);
  glLightfv(GL_LIGHT0, GL_AMBIENT, light0_ambient);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, light0_diffuse);
  glLightfv(GL_LIGHT0, GL_SPECULAR, light0_specular);
  glLightfv(GL_LIGHT0, GL_POSITION, light0_position);
  glEnable(GL_LIGHT0);
  glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, lmodel_local);
  glLightModelfv(GL_LIGHT_MODEL_TWO_SIDE, lmodel_twoside);
  glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
  glEnable(GL_LIGHTING);
  glMaterialfv(GL_FRONT, GL_AMBIENT, bevel_mat_ambient);
  glMaterialfv(GL_FRONT, GL_SHININESS, bevel_mat_shininess);
  glMaterialfv(GL_FRONT, GL_SPECULAR, bevel_mat_specular);
  glMaterialfv(GL_FRONT, GL_DIFFUSE, bevel_mat_diffuse);
  glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);
  glEnable(GL_COLOR_MATERIAL);
  glShadeModel(GL_SMOOTH);
}

void CALLBACK Reshape(int width, int height) {
  glViewport(0, 0, width, height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity() ;
  gluPerspective(45,(double) width/height, 0.1, 100.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity() ;
}

void CALLBACK up(void) {
  anglex -= 2 ;
}

void CALLBACK down(void) {
  anglex += 2 ;
}

void CALLBACK left(void) {
  angley -= 2 ;
}

void CALLBACK right(void) {
  angley += 2 ;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGB|AUX_DEPTH);
  auxInitPosition(0,0,400,300);
  auxInitWindow("Les anneaux olympiques");
  Init();
  auxKeyFunc(AUX_UP,up) ;
  auxKeyFunc(AUX_DOWN,down) ;
  auxKeyFunc(AUX_LEFT,left) ;
  auxKeyFunc(AUX_RIGHT,right) ;
  auxReshapeFunc(Reshape);
  auxMainLoop(DrawScene);
}

RETOUR


Des boules s'entrechoquant

L'exécutable

Le source: BoulesRebondissantes.cpp

#include "windows.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <stdio.h>
#include <math.h>

struct boule {
  float x ;
  float y ;
  float dx ;
  float dy ;
  float couleur[4] ; } ;

#define N 10
static boule boules[N] = { {  -50.0F, 10.0F,-0.4F,0.8F,{1.0F,0.0F,0.0F,1.0F} },
                           {   20.0F,  0.0F,-1.0F,-0.3F,{0.0F,0.0F,1.0F,1.0F} },
                           { -130.0F,-120.0F,0.23F,-1.57F,{0.0F,1.0F,0.0F,1.0F} },
                           {  115.0F,  60.0F,-0.73F,0.77F,{1.0F,1.0F,1.0F,1.0F} },
                           { -125.0F, 100.0F,-1.53F,-0.27F,{0.0F,1.0F,1.0F,1.0F} },
                           {  120.0F, 120.0F,1.53F,-0.33F,{1.0F,0.0F,1.0F,1.0F} },
                           {   80.0F, -20.0F,-0.23F,1.47F,{1.0F,1.0F,0.0F,1.0F} },
                           {  -90.0F, -90.0F,-0.56F,0.97F,{0.5F,1.0F,1.0F,1.0F} },
                           {  110.0F, -70.0F,0.59F,1.67F,{1.0F,0.5F,1.0F,1.0F} },
                           {  -90.0F, -34.0F,0.52F,-1.07F,{1.0F,1.0F,0.5F,1.0F} } } ;
static int w = 0 ;
static int h = 0 ;

void myinit(void) {
  GLfloat light_ambient[] = { 0.0F,0.0F,0.0F,1.0F };
  GLfloat light_diffuse[] = { 1.0F,1.0F,1.0F,1.0F };
  GLfloat light_specular[] = { 1.0F,1.0F,1.0F,1.0F };
  GLfloat light_position[] = { 1.0F,1.0F,1.0F,0.0F };
  glLightfv(GL_LIGHT0,GL_AMBIENT,light_ambient);
  glLightfv(GL_LIGHT0,GL_DIFFUSE,light_diffuse);
  glLightfv(GL_LIGHT0,GL_SPECULAR,light_specular);
  glLightfv(GL_LIGHT0,GL_POSITION,light_position);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glDepthFunc(GL_LESS);
  glEnable(GL_DEPTH_TEST);
}

void afficheBoule(struct boule *b) {
  glPushMatrix();
  glMaterialfv(GL_FRONT,GL_DIFFUSE,b->couleur) ;
  glTranslatef(b->x,b->y,0.0F);
  auxSolidSphere(10.0);
  glPopMatrix();
}

void CALLBACK display(void) {
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  for ( int i = 0 ; i < N ; i++ )
    afficheBoule(&boules[i]);
  glFlush();
  auxSwapBuffers();
}

void adapteBoule(struct boule *b,int w,int h) {
  if ( b->x < -w/2+10 ) {
    b->x = -w/2+11.0F ; }
  if ( b->y < -h/2+10 ) {
    b->y = -h/2+11.0F ; }
  if ( b->x >= w/2-10 ) {
    b->x = w/2-11.0F ; }
  if ( b->y >= h/2-10 ) {
    b->y = h/2-11.0F ; }
}

void CALLBACK myReshape(int ww,int hh) {
  w = ww ;
  h = hh ;
  glViewport(0,0,ww,hh);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(-w/2,w/2,-h/2,h/2,-10.0,10.0);
  glMatrixMode(GL_MODELVIEW);
  for ( int i = 0 ; i < N ; i++ )
    adapteBoule(&boules[i],w,h);
}

void interioriseBoule(struct boule *b,int w,int h) {
  if ( b->x < -w/2+10 ) {
    b->dx = -b->dx ; }
  if ( b->y < -h/2+10 ) {
    b->dy = -b->dy ; }
  if ( b->x >= w/2-10 ) {
    b->dx = -b->dx ; }
  if ( b->y >= h/2-10 ) {
    b->dy = -b->dy ; }
  adapteBoule(b,w,h) ;
}

void bougeBoule(struct boule *b,int w,int h) {
  b->x += b->dx ;
  b->y += b->dy ;
  interioriseBoule(b,w,h) ;
}

float produitScalaire(float ix,float iy,float nx,float ny) {
  return(ix*nx+iy*ny) ;
}

float norme(float x,float y) {
  return((float) sqrt(x*x+y*y)) ;
}

void normalise(float *x,float *y) {
  float n = norme(*x,*y);
  *x /= n ;
  *y /= n ;
}

float distance(struct boule *b1,struct boule *b2) {
  float dx = b1->x - b2->x ;
  float dy = b1->y - b2->y ;
  return((float) sqrt(dx*dx+dy*dy)) ;
}

float distance(struct boule *b,int i,int j) {
  float dx = b[i].x - b[j].x ;
  float dy = b[i].y - b[j].y ;
  return((float) sqrt(dx*dx+dy*dy)) ;
}

int rapprochement(struct boule *b,int i,int j) {
  float d = distance(b,i,j) ;
  boule bb = b[i] ;
  bb.x += b[i].dx ;
  bb.y += b[i].dy ;
  float di = distance(&bb,&b[j]) ;
  bb = b[j] ;
  bb.x += b[j].dx ;
  bb.y += b[j].dy ;
  float dj = distance(&bb,&b[i]) ;
  if ( di > d )
    return(1) ;
  if ( dj > d )
    return(2) ;
  return (0) ;
}

void rebond(boule *b,float px,float py) {
  float ix = -b->dx ;
  float iy = -b->dy ;
  float d = norme(ix,iy) ;
  float nx = b->x - px ;
  float ny = b->y - py ;
  normalise(&ix,&iy) ;
  normalise(&nx,&ny) ;
  float s = produitScalaire(ix,iy,nx,ny) ;
  float rx = (2*nx*s-ix)*d ;
  float ry = (2*ny*s-iy)*d ;
  b->dx = rx ;
  b->dy = ry ;
}

void rebond2(boule *b,float px,float py) {
  float ix = -b->dx ;
  float iy = -b->dy ;
  float d = norme(ix,iy) ;
  float nx = b->x - px ;
  float ny = b->y - py ;
  normalise(&ix,&iy) ;
  normalise(&nx,&ny) ;
  float s = produitScalaire(ix,iy,nx,ny) ;
  float rx = (2*nx*s-ix)*d ;
  float ry = (2*ny*s-iy)*d ;
  b->dx = rx ;
  b->dy = ry ;
}

void CALLBACK idle(void) {
  for ( int i = 0 ; i < N ; i++ ) {
    boule aux = boules[i] ;
    bougeBoule(&boules[i],w,h);
    for ( int j = 0 ; j < N ; j++ )
      if ( i != j ) {
        if ( distance(boules,i,j) < 20 ) {
          boules[i] = aux ;
          float px = (boules[i].x+boules[j].x)/2.0F ;
          float py = (boules[i].y+boules[j].y)/2.0F ;
          int c = rapprochement(boules,i,j) ;
          if ( !c ) {
            rebond(&boules[i],px,py) ;
            rebond(&boules[j],px,py) ; }
            else {
            switch ( c ) {
              case 1 : rebond2(&boules[j],px,py) ;
                       rebond(&boules[i],px,py) ;
                       break ;
              case 2 : rebond(&boules[j],px,py) ;
                       rebond2(&boules[i],px,py) ;
                       break ; } } } } }
  display() ;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGB|AUX_DEPTH);
  auxInitPosition(0,0,300,300);
  auxInitWindow("10 sphères s'entrechoquant");
  myinit();
  auxIdleFunc(idle);
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Des engrenages

L'exécutable

Le source: Gears.cpp

#include "windows.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <math.h>
#include <stdlib.h>

#ifndef M_PI
#define M_PI 3.14159265F
#endif

static float cosF(double v) {
  return((float) cos(v)) ;
}

static float sinF(double v) {
  return((float) sin(v)) ;
}

static void gear(GLfloat inner_radius,GLfloat outer_radius,GLfloat width,GLint teeth,GLfloat tooth_depth ) {
  GLint i;
  GLfloat r0,r1,r2;
  GLfloat angle,da;
  GLfloat u,v,len;
  r0 = inner_radius;
  r1 = outer_radius-tooth_depth/2.0F;
  r2 = outer_radius+tooth_depth/2.0F;
  da = 2.0F*M_PI/teeth/4.0F;
  glShadeModel(GL_FLAT);
  glNormal3f(0.0,0.0,1.0);
  glBegin(GL_QUAD_STRIP);
  for ( i = 0 ; i <= teeth ; i++ ) {
    angle = i * 2.0F*M_PI / teeth;
    glVertex3f(r0*cosF(angle),r0*sinF(angle),width*0.5F);
    glVertex3f(r1*cosF(angle),r1*sinF(angle),width*0.5F);
    glVertex3f(r0*cosF(angle),r0*sinF(angle),width*0.5F);
    glVertex3f(r1*cosF(angle+3*da),r1*sinF(angle+3*da),width*0.5F); }
  glEnd();
  glBegin(GL_QUADS);
  da = 2.0F*M_PI/teeth/4.0F;
  for ( i = 0 ; i < teeth ; i++ ) {
    angle = i * 2.0F*M_PI / teeth;
    glVertex3f(r1*cosF(angle),r1*sinF(angle),width*0.5F);
    glVertex3f(r2*cosF(angle+da),r2*sinF(angle+da),width*0.5F);
    glVertex3f(r2*cosF(angle+2*da),r2*sinF(angle+2*da),width*0.5F);
    glVertex3f(r1*cosF(angle+3*da),r1*sinF(angle+3*da),width*0.5F); }
  glEnd();
  glNormal3f(0.0,0.0,-1.0);
  glBegin(GL_QUAD_STRIP);
  for ( i = 0 ; i <= teeth ; i++ ) {
    angle = i * 2.0F*M_PI / teeth;
    glVertex3f(r1*cosF(angle),r1*sinF(angle),-width*0.5F);
    glVertex3f(r0*cosF(angle),r0*sinF(angle),-width*0.5F);
    glVertex3f(r1*cosF(angle+3*da),r1*sinF(angle+3*da),-width*0.5F);
    glVertex3f(r0*cosF(angle),r0*sinF(angle),-width*0.5F); }
  glEnd();
  glBegin(GL_QUADS);
  da = 2.0F*M_PI/teeth/4.0F;
  for (i=0;i<teeth;i++) {
    angle = i * 2.0F*M_PI / teeth;
    glVertex3f(r1*cosF(angle+3*da),r1*sinF(angle+3*da),-width*0.5F);
    glVertex3f(r2*cosF(angle+2*da),r2*sinF(angle+2*da),-width*0.5F);
    glVertex3f(r2*cosF(angle+da),r2*sinF(angle+da),-width*0.5F);
    glVertex3f(r1*cosF(angle),r1*sinF(angle),-width*0.5F); }
  glEnd();
  glBegin(GL_QUAD_STRIP);
  for ( i = 0 ; i < teeth ; i++ ) {
    angle = i * 2.0F*M_PI / teeth;
    glVertex3f(r1*cosF(angle),r1*sinF(angle),width*0.5F );
    glVertex3f(r1*cosF(angle),r1*sinF(angle),-width*0.5F );
    u = r2*cosF(angle+da) - r1*cosF(angle);
    v = r2*sinF(angle+da) - r1*sinF(angle);
    len =(float) sqrt(u*u + v*v);
    u /= len;
    v /= len;
    glNormal3f(v,-u,0.0);
    glVertex3f(r2*cosF(angle+da),r2*sinF(angle+da),width*0.5F);
    glVertex3f(r2*cosF(angle+da),r2*sinF(angle+da),-width*0.5F);
    glNormal3f(cosF(angle),sinF(angle),0.0 );
    glVertex3f(r2*cosF(angle+2*da),r2*sinF(angle+2*da),width*0.5F);
    glVertex3f( r2*cosF(angle+2*da),r2*sinF(angle+2*da),-width*0.5F);
    u = r1*cosF(angle+3*da) - r2*cosF(angle+2*da);
    v = r1*sinF(angle+3*da) - r2*sinF(angle+2*da);
    glNormal3f(v,-u,0.0);
    glVertex3f(r1*cosF(angle+3*da),r1*sinF(angle+3*da),width*0.5F);
    glVertex3f(r1*cosF(angle+3*da),r1*sinF(angle+3*da),-width*0.5F);
    glNormal3f(cosF(angle),sinF(angle),0.0); }
  glVertex3f(r1*cosF(0),r1*sinF(0),width*0.5F);
  glVertex3f(r1*cosF(0),r1*sinF(0),-width*0.5F);
  glEnd();
  glShadeModel(GL_SMOOTH);
  glBegin(GL_QUAD_STRIP);
  for ( i = 0 ; i <= teeth ; i++ ) {
    angle = i * 2.0F*M_PI / teeth;
    glNormal3f(-cosF(angle),-sinF(angle),0.0);
    glVertex3f(r0*cosF(angle),r0*sinF(angle),-width*0.5F);
    glVertex3f(r0*cosF(angle),r0*sinF(angle),width*0.5F); }
  glEnd();
}

static GLfloat view_rotx=20.0 ;
static GLfloat view_roty=30.0 ;
static GLfloat view_rotz=0.0;
static GLint gear1 ;
static GLint gear2;
static GLint gear3;
static GLfloat angle = 0.0;
static GLfloat da = 2.0;
static GLuint limit;

static void CALLBACK draw(void) {
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  glRotatef(view_rotx,1.0,0.0,0.0);
  glRotatef(view_roty,0.0,1.0,0.0);
  glRotatef(view_rotz,0.0,0.0,1.0);
  glPushMatrix();
  glTranslatef(-3.0,-2.0,0.0);
  glRotatef(angle,0.0,0.0,1.0);
  glCallList(gear1);
  glPopMatrix();
  glPushMatrix();
  glTranslatef(3.1F,-2.0F,0.0F);
  glRotatef(-2.0F*angle-9.0F,0.0F,0.0F,1.0F);
  glCallList(gear2);
  glPopMatrix();
  glPushMatrix();
  glTranslatef(-3.1F,4.2F,0.0F);
  glRotatef(-2.0F*angle-25.0F,0.0F,0.0F,1.0F);
  glCallList(gear3);
  glPopMatrix();
  glPopMatrix();
  auxSwapBuffers();
}

static void CALLBACK idle(void) {
  angle += da;
  draw();
}

static void CALLBACK reshape(int width,int height) {
  GLfloat  h = (GLfloat) height/(GLfloat) width;
  glViewport(0,0,(GLint)width,(GLint)height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glFrustum(-1.0,1.0,-h,h,5.0,60.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef(0.0F,0.0F,-40.0F);
}

static void init(void) {
  static GLfloat pos[4] = {5.0F,5.0F,10.0F,0.0F };
  static GLfloat red[4] = {0.8F,0.1F,0.0F,1.0F };
  static GLfloat green[4] = {0.0F,0.8F,0.2F,1.0F };
  static GLfloat blue[4] = {0.2F,0.2F,1.0F,1.0F };
  glLightfv(GL_LIGHT0,GL_POSITION,pos);
  glEnable(GL_CULL_FACE);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glEnable(GL_DEPTH_TEST);
  gear1 = glGenLists(1);
  glNewList(gear1,GL_COMPILE);
  glMaterialfv(GL_FRONT,GL_AMBIENT_AND_DIFFUSE,red);
  gear(1.0F,4.0F,1.0F,20,0.7F);
  glEndList();
  gear2 = glGenLists(1);
  glNewList(gear2,GL_COMPILE);
  glMaterialfv(GL_FRONT,GL_AMBIENT_AND_DIFFUSE,green);
  gear(0.5F,2.0F,2.0F,10,0.7F);
  glEndList();
  gear3 = glGenLists(1);
  glNewList(gear3,GL_COMPILE);
  glMaterialfv(GL_FRONT,GL_AMBIENT_AND_DIFFUSE,blue);
  gear(1.3F,2.0F,0.5F,10,0.7F);
  glEndList();
  glEnable(GL_NORMALIZE);
}

void CALLBACK up(void) {
  view_rotx++ ;
}

void CALLBACK down(void) {
  view_rotx-- ;
}

void CALLBACK left(void) {
  view_roty++ ;
}

void CALLBACK right(void) {
  view_roty-- ;
}

void CALLBACK keyz(void) {
  view_rotz++ ;
}

void CALLBACK keyZ(void) {
  view_rotz-- ;
}

void CALLBACK space(void) {
  da *= -1 ;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGB|AUX_DEPTH);
  auxInitPosition(0,0,300,300);
  auxInitWindow("Gears");
  init();
  auxKeyFunc(AUX_UP,up) ;
  auxKeyFunc(AUX_DOWN,down) ;
  auxKeyFunc(AUX_LEFT,left) ;
  auxKeyFunc(AUX_RIGHT,right) ;
  auxKeyFunc(AUX_z,keyz) ;
  auxKeyFunc(AUX_Z,keyZ) ;
  auxKeyFunc(AUX_SPACE,space) ;
  auxIdleFunc(idle);
  auxReshapeFunc(reshape);
  auxMainLoop(draw);
}
   RETOUR


Des lumières colorées

L'exécutable

Le source: Lumieres.cpp

#include "windows.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

static float anglex = 0 ;
static float angley = 0 ;
static float anglez = 0 ;

void myinit(void) {
  GLfloat mat_specular[] = { 1.0,1.0,1.0,1.0 };
  GLfloat mat_shininess[] = { 50.0 };
  GLfloat light0_couleur[] = { 1.0,0.0,0.0,1.0 };
  GLfloat light1_couleur[] = { 0.0,1.0,0.0,1.0 };
  GLfloat light2_couleur[] = { 0.0,0.0,1.0,1.0 };
  glMaterialfv(GL_FRONT,GL_SPECULAR,mat_specular);
  glMaterialfv(GL_FRONT,GL_SHININESS,mat_shininess);
  glLightfv(GL_LIGHT0,GL_DIFFUSE,light0_couleur);
  glLightfv(GL_LIGHT1,GL_DIFFUSE,light1_couleur);
  glLightfv(GL_LIGHT2,GL_DIFFUSE,light2_couleur);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glEnable(GL_LIGHT1);
  glEnable(GL_LIGHT2);
  glDepthFunc(GL_LESS);
  glEnable(GL_DEPTH_TEST);
}

void CALLBACK display(void) {
  GLfloat light0_position[] = { 1.0,1.0,1.0,0.0 };
  GLfloat light1_position[] = { -1.0,1.0,1.0,0.0 };
  GLfloat light2_position[] = { 1.0,-1.0,1.0,0.0 };
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glPushMatrix() ;
  glRotatef(anglex,1.0F,0.0F,0.0F) ;
  glRotatef(angley,0.0F,1.0F,0.0F) ;
  glRotatef(anglez,0.0F,0.0F,1.0F) ;
  glLightfv(GL_LIGHT0,GL_POSITION,light0_position);
  glLightfv(GL_LIGHT1,GL_POSITION,light1_position);
  glLightfv(GL_LIGHT2,GL_POSITION,light2_position);
  glPopMatrix() ;
  glPushMatrix() ;
  auxSolidSphere(1.0);
  glPushMatrix() ;
  glFlush();
  auxSwapBuffers() ;
}

void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if (w <= h)
      glOrtho (-1.5,1.5,-1.5*(GLfloat)h/(GLfloat)w,1.5*(GLfloat)h/(GLfloat)w,-10.0,10.0);
    else
      glOrtho (-1.5*(GLfloat)w/(GLfloat)h,1.5*(GLfloat)w/(GLfloat)h,-1.5,1.5,-10.0,10.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void CALLBACK idle(void) {
  anglex++ ;
  angley-- ;
  anglez+=2 ;
  display() ;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGB|AUX_DEPTH);
  auxInitPosition(0,0,300,300);
  auxInitWindow("Lumières");
  myinit();
  auxIdleFunc(idle) ;
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Des teapots clippés avec matériaux interne et externe

L'exécutable

Le source: Tea.cpp
#include "windows.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <stdio.h>

static float anglex = 0.0F ;
static float angley = 0.0F ;

void myinit(void) {
  GLfloat light_ambient[] = { 0.0F,0.0F,0.0F,1.0F };
  GLfloat light_diffuse[] = { 1.0F,1.0F,1.0F,1.0F };
  GLfloat light_specular[] = { 1.0F,1.0F,1.0F,1.0F };
  GLfloat light_position[] = { 1.0F,1.0F,1.0F,0.0F };
  glLightfv(GL_LIGHT0,GL_AMBIENT,light_ambient);
  glLightfv(GL_LIGHT0,GL_DIFFUSE,light_diffuse);
  glLightfv(GL_LIGHT0,GL_SPECULAR,light_specular);
  glLightfv(GL_LIGHT0,GL_POSITION,light_position);
  glFrontFace(GL_CW);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glEnable(GL_AUTO_NORMAL);
  glEnable(GL_NORMALIZE);
  glDepthFunc(GL_LESS);
  glEnable(GL_DEPTH_TEST);
}

void CALLBACK display(void) {
  GLdouble eqn[4] = {1.0,0.0,-1.0,1.0};
  GLfloat two_side_on[] = { GL_TRUE };
  GLfloat two_side_off[] = { GL_FALSE };
  GLfloat mat_diffuse[] = { 0.8F,0.8F,0.8F,1.0F };
  GLfloat back_diffuse[] = { 0.8F,0.2F,0.8F,1.0F };
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  glRotatef(angley,0.0F,1.0F,0.0F) ;
  glRotatef(anglex,1.0F,0.0F,0.0F) ;
  glPushMatrix();
  glClipPlane(GL_CLIP_PLANE0,eqn);
  glEnable(GL_CLIP_PLANE0);
  glPushMatrix();
  glTranslatef(0.0F,2.0F,0.0F);
  auxSolidTeapot(1.0);
  glPopMatrix();
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,mat_diffuse);
  glPushMatrix();
  glTranslatef(0.0F,0.0F,0.0F);
  auxSolidTeapot(1.0);
  glPopMatrix();
  glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
  glMaterialfv(GL_BACK,GL_DIFFUSE,back_diffuse);
  glPushMatrix();
  glTranslatef(0.0F,-2.0F,0.0F);
  auxSolidTeapot(1.0);
  glPopMatrix();
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_FALSE);
  glDisable(GL_CLIP_PLANE0);
  glPopMatrix();
  glPopMatrix();
  glFlush();
  auxSwapBuffers();
}

void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if(w <= h)
      glOrtho(-2.0,2.0,-2.0*(GLfloat)h/(GLfloat)w,2.0*(GLfloat)h/(GLfloat)w,-10.0,10.0);
    else
      glOrtho(-2.0*(GLfloat)w/(GLfloat)h,2.0*(GLfloat)w/(GLfloat)h,-2.0,2.0,-10.0,10.0);
  glMatrixMode(GL_MODELVIEW);
}

void CALLBACK up(void) {
  anglex -= 2 ;
}

void CALLBACK down(void) {
  anglex += 2 ;
}

void CALLBACK left(void) {
  angley -= 2 ;
}

void CALLBACK right(void) {
  angley += 2 ;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGB|AUX_DEPTH);
  auxInitPosition(0,0,200,300);
  auxInitWindow("Un teapot clippé");
  myinit();
  auxKeyFunc(AUX_UP,up) ;
  auxKeyFunc(AUX_DOWN,down) ;
  auxKeyFunc(AUX_LEFT,left) ;
  auxKeyFunc(AUX_RIGHT,right) ;
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}

RETOUR


Des teapots

L'exécutable

Le source: Teapots.cpp

#include "windows.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void) {
  GLfloat ambient[] = { 0.0F,0.0F,0.0F,1.0F };
  GLfloat diffuse[] = { 1.0F,1.0F,1.0F,1.0F };
  GLfloat specular[] = { 1.0F,1.0F,1.0F,1.0F };
  GLfloat position[] = { 0.0F,3.0F,3.0F,0.0F };
  GLfloat lmodel_ambient[] = { 0.2F,0.2F,0.2F,1.0F };
  GLfloat local_view[] = { 0.0F };
  glLightfv(GL_LIGHT0,GL_AMBIENT,ambient);
  glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuse);
  glLightfv(GL_LIGHT0,GL_POSITION,position);
  glLightModelfv(GL_LIGHT_MODEL_AMBIENT,lmodel_ambient);
  glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER,local_view);
  glFrontFace (GL_CW);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glEnable(GL_AUTO_NORMAL);
  glEnable(GL_NORMALIZE);
  glEnable(GL_DEPTH_TEST);
  glDepthFunc(GL_LESS);
}

void renderTeapot (GLfloat x,GLfloat y,GLfloat ambr,GLfloat ambg,GLfloat ambb,GLfloat difr,GLfloat difg,GLfloat difb,GLfloat specr,GLfloat specg,GLfloat specb,GLfloat shine) {
  float mat[4];
  glPushMatrix();
  glTranslatef (x,y,0.0);
  mat[0] = ambr;
  mat[1] = ambg;
  mat[2] = ambb;
  mat[3] = 1.0;
  glMaterialfv(GL_FRONT,GL_AMBIENT,mat);
  mat[0] = difr;
  mat[1] = difg;
  mat[2] = difb;
  glMaterialfv(GL_FRONT,GL_DIFFUSE,mat);
  mat[0] = specr;
  mat[1] = specg;
  mat[2] = specb;
  glMaterialfv(GL_FRONT,GL_SPECULAR,mat);
  glMaterialf(GL_FRONT,GL_SHININESS,shine*128.0F);
  auxSolidTeapot(1.0);
  glPopMatrix();
}

void CALLBACK display(void) {
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  renderTeapot(2.0F,17.0F,0.0215F,0.1745F,0.0215F,0.07568F,0.61424F,0.07568F,0.633F,0.727811F,0.633F,0.6F);
  renderTeapot(2.0F,14.0F,0.135F,0.2225F,0.1575F,0.54F,0.89F,0.63F,0.316228F,0.316228F,0.316228F,0.1F);
  renderTeapot(2.0F,11.0F,0.05375F,0.05F,0.06625F,0.18275F,0.17F,0.22525F,0.332741F,0.328634F,0.346435F,0.3F);
  renderTeapot(2.0F,8.0F,0.25F,0.20725F,0.20725F,1.0F,0.829F,0.829F,0.296648F,0.296648F,0.296648F,0.088F);
  renderTeapot(2.0F,5.0F,0.1745F,0.01175F,0.01175F,0.61424F,0.04136F,0.04136F,0.727811F,0.626959F,0.626959F,0.6F);
  renderTeapot(2.0F,2.0F,0.1F,0.18725F,0.1745F,0.396F,0.74151F,0.69102F,0.297254F,0.30829F,0.306678F,0.1F);
  renderTeapot(6.0F,17.0F,0.329412F,0.223529F,0.027451F,0.780392F,0.568627F,0.113725F,0.992157F,0.941176F,0.807843F,0.21794872F);
  renderTeapot(6.0F,14.0F,0.2125F,0.1275F,0.054F,0.714F,0.4284F,0.18144F,0.393548F,0.271906F,0.166721F,0.2F);
  renderTeapot(6.0F,11.0F,0.25F,0.25F,0.25F,0.4F,0.4F,0.4F,0.774597F,0.774597F,0.774597F,0.6F);
  renderTeapot(6.0F,8.0F,0.19125F,0.0735F,0.0225F,0.7038F,0.27048F,0.0828F,0.256777F,0.137622F,0.086014F,0.1F);
  renderTeapot(6.0F,5.0F,0.24725F,0.1995F,0.0745F,0.75164F,0.60648F,0.22648F,0.628281F,0.555802F,0.366065F,0.4F);
  renderTeapot(6.0F,2.0F,0.19225F,0.19225F,0.19225F,0.50754F,0.50754F,0.50754F,0.508273F,0.508273F,0.508273F,0.4F);
  renderTeapot(10.0F,17.0F,0.0F,0.0F,0.0F,0.01F,0.01F,0.01F,0.50F,0.50F,0.50F,.25F);
  renderTeapot(10.0F,14.0F,0.0F,0.1F,0.06F,0.0F,0.50980392F,0.50980392F,0.50196078F,0.50196078F,0.50196078F,.25F);
  renderTeapot(10.0F,11.0F,0.0F,0.0F,0.0F,0.1F,0.35F,0.1F,0.45F,0.55F,0.45F,.25F);
  renderTeapot(10.0F,8.0F,0.0F,0.0F,0.0F,0.5F,0.0F,0.0F,0.7F,0.6F,0.6F,.25F);
  renderTeapot(10.0F,5.0F,0.0F,0.0F,0.0F,0.55F,0.55F,0.55F,0.70F,0.70F,0.70F,.25F);
  renderTeapot(10.0F,2.0F,0.0F,0.0F,0.0F,0.5F,0.5F,0.0F,0.60F,0.60F,0.50F,.25F);
  renderTeapot(14.0F,17.0F,0.02F,0.02F,0.02F,0.01F,0.01F,0.01F,0.4F,0.4F,0.4F,.078125F);
  renderTeapot(14.0F,14.0F,0.0F,0.05F,0.05F,0.4F,0.5F,0.5F,0.04F,0.7F,0.7F,.078125F);
  renderTeapot(14.0F,11.0F,0.0F,0.05F,0.0F,0.4F,0.5F,0.4F,0.04F,0.7F,0.04F,.078125F);
  renderTeapot(14.0F,8.0F,0.05F,0.0F,0.0F,0.5F,0.4F,0.4F,0.7F,0.04F,0.04F,.078125F);
  renderTeapot(14.0F,5.0F,0.05F,0.05F,0.05F,0.5F,0.5F,0.5F,0.7F,0.7F,0.7F,.078125F);
  renderTeapot(14.0F,2.0F,0.05F,0.05F,0.0F,0.5F,0.5F,0.4F,0.7F,0.7F,0.04F,.078125F);
  glFlush();
}

void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if (w <= h)
      glOrtho(0.0,16.0,0.0,16.0*(GLfloat)h/(GLfloat)w,-10.0,10.0);
    else
      glOrtho(0.0,16.0*(GLfloat)w/(GLfloat)h,0.0,16.0,-10.0,10.0);
  glMatrixMode(GL_MODELVIEW);
}

void main(void) {
  auxInitDisplayMode(AUX_SINGLE|AUX_RGB|AUX_DEPTH);
  auxInitPosition(0,0,300,360);
  auxInitWindow("Teapots");
  myinit();
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Deux réflexions spéculaires sur une sphère

L'exécutable

  • une sphère blanche avec deux réflexions spéculaires
  • s et S pour jouer sur l'importance du phénomène
  • up, down, left et right pour faire tourner la sphère

Le source: SphereSpeculaire.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

static float anglex = 0.0F ;
static float angley = 0.0F ;
static GLfloat shininess = 100.0F ;

void myinit(void) {
  GLfloat gris[] = { 0.4F,0.4F,0.4F,1.0F };
  GLfloat specular[] = { 1.0F,1.0F,1.0F,1.0F };
  GLfloat bleu[] = { 0.0F,0.0F,1.0F,1.0F };
  GLfloat vert[] = { 0.0F,1.0F,0.0F,1.0F };
  GLfloat l_pos0[] = { 1.0F,1.0F,1.0F,0.0F };
  GLfloat l_pos1[] = { -1.0F,0.0F,1.0F,0.0F };
  glClearColor(0.5F,0.5F,1.0F,1.0F) ;
  glMaterialfv(GL_FRONT,GL_SPECULAR,specular);
  glLightfv(GL_LIGHT0,GL_DIFFUSE,gris);
  glLightfv(GL_LIGHT0,GL_POSITION,l_pos0);
  glLightfv(GL_LIGHT0,GL_SPECULAR,bleu);
  glLightfv(GL_LIGHT1,GL_DIFFUSE,gris);
  glLightfv(GL_LIGHT1,GL_POSITION,l_pos1);
  glLightfv(GL_LIGHT1,GL_SPECULAR,vert);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glEnable(GL_LIGHT1);
  glDepthFunc(GL_LESS);
  glEnable(GL_DEPTH_TEST);
}

void CALLBACK display(void) {
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  glRotatef(angley,0.0F,1.0F,0.0F) ;
  glRotatef(anglex,1.0F,0.0F,0.0F) ;
  glMaterialf(GL_FRONT,GL_SHININESS,shininess);
  auxSolidSphere(1.0);
  glPopMatrix();
  glFlush();
  auxSwapBuffers();
}

void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if (w <= h)
    glOrtho(-1.5,1.5,-1.5*(float)h/(float)w,1.5*(float)h/(float)w,-10.,10.);
    else
    glOrtho(-1.5*(float)w/(float)h,1.5*(float)w/(float)h,-1.5,1.5,-10.,10.);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void CALLBACK up(void) {
  anglex-- ;
}

void CALLBACK down(void) {
  anglex++ ;
}

void CALLBACK left(void) {
  angley-- ;
}

void CALLBACK right(void) {
  angley++ ;
}

void CALLBACK keys(void) {
  shininess *= 1.03F ;
}

void CALLBACK keyS(void) {
  shininess /= 1.03F ;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGB|AUX_DEPTH);
  auxInitPosition(0,0,250,250);
  auxInitWindow("2 Lumières avec réflexion spéculaire");
  myinit();
  auxKeyFunc(AUX_UP,up) ;
  auxKeyFunc(AUX_DOWN,down) ;
  auxKeyFunc(AUX_LEFT,left) ;
  auxKeyFunc(AUX_RIGHT,right) ;
  auxKeyFunc(AUX_s,keys) ;
  auxKeyFunc(AUX_S,keyS) ;
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Elimination des parties cachées

L'exécutable

  • Espace pour switcher entre avec et sans élimination des parties cachées.
  • x, X, y, Y, z et Z pour faire tourner les triangles sur eux-mêmes.

Le source: EliminationPartiesCachees.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <stdio.h>
#include <stdlib.h>

static int mode = 0 ;
static float rotx = 0.0f ;
static float roty = 0.0f ;
static float rotz = 0.0f ;

void CALLBACK display(void) {
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  glRotatef(rotx,1.0f,0.0f,0.0f);
  glRotatef(roty,0.0f,1.0f,0.0f);
  glRotatef(rotz,0.0f,0.0f,1.0f);
  if ( mode )
    glEnable(GL_DEPTH_TEST) ;
  glBegin(GL_TRIANGLES) ;
  glColor3f(0.0F,0.0F,1.0F) ;
  glVertex3f(1.0F,-1.5F,-0.5F) ;
  glVertex3f(1.5F,1.5F,0.5F) ;
  glVertex3f(-1.1F,-0.2F,0.2F) ;
  glColor3f(0.0F,1.0F,0.0F) ;
  glVertex3f(-1.0F,-1.5F,-0.5F) ;
  glVertex3f(-1.5F,1.5F,0.5F) ;
  glVertex3f(0.5F,-0.5F,0.2F) ;
  glColor3f(1.0F,0.0F,0.0F) ;
  glVertex3f(-1.2F,1.3F,-0.5F) ;
  glVertex3f(1.2F,1.1F,0.5F) ;
  glVertex3f(0.1F,-0.5F,0.3F) ;
  glEnd() ;
  glDisable(GL_DEPTH_TEST) ;
  glPopMatrix();
  glFlush();
  auxSwapBuffers();
}

void myinit (void) {
  glShadeModel(GL_SMOOTH);
  glClearColor(0.0,0.0,0.0,0.0);
}

void CALLBACK myReshape(int w, int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(-2.0,2.0,-2.0/w*h,2.0/w*h,-5.0,5.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void CALLBACK auxSpace(void) {
  mode = 1 - mode ;
}

void CALLBACK auxX(void) {
  rotx += 2 ;
}

void CALLBACK auxx(void) {
  rotx -= 2 ;
}

void CALLBACK auxY(void) {
  roty += 2 ;
}

void CALLBACK auxy(void) {
  roty -= 2 ;
}

void CALLBACK auxZ(void) {
  rotz += 2 ;
}

void CALLBACK auxz(void) {
  rotz -= 2 ;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGBA|AUX_DEPTH);
  auxInitPosition (0,0,280,280);
  auxInitWindow("Elimination des parties cachées") ;
  myinit();
  auxKeyFunc(AUX_SPACE,auxSpace) ;
  auxKeyFunc(AUX_X,auxX) ;
  auxKeyFunc(AUX_x,auxx) ;
  auxKeyFunc(AUX_Y,auxY) ;
  auxKeyFunc(AUX_y,auxy) ;
  auxKeyFunc(AUX_Z,auxZ) ;
  auxKeyFunc(AUX_z,auxz) ;
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Illustration de l'emploi d'une fonction idle pour réaliser une animation

L'exécutable

  • une sphère blanche se déplaçant toute seule et rebondissant contre les bords de la fenêtre d'affichage.

Le source: Idle.cpp

#include "windows.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <stdio.h>

static float x = 0.0F ;
static float y = 20.0F ;
static float dx = 1.13F ;
static float dy = 0.97F ;
static float w = 0.0F ;
static float h = 0.0F ;

void myinit(void) {
  GLfloat light_ambient[] = { 0.0F,0.0F,0.0F,1.0F };
  GLfloat light_diffuse[] = { 1.0F,1.0F,1.0F,1.0F };
  GLfloat light_specular[] = { 1.0F,1.0F,1.0F,1.0F };
  GLfloat light_position[] = { 1.0F,1.0F,1.0F,0.0F };
  glLightfv(GL_LIGHT0,GL_AMBIENT,light_ambient);
  glLightfv(GL_LIGHT0,GL_DIFFUSE,light_diffuse);
  glLightfv(GL_LIGHT0,GL_SPECULAR,light_specular);
  glLightfv(GL_LIGHT0,GL_POSITION,light_position);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glDepthFunc(GL_LESS);
  glEnable(GL_DEPTH_TEST);
}

void CALLBACK display(void) {
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  glTranslatef(x,y,0.0F);
  auxSolidSphere(10.0);
  glPopMatrix();
  glFlush();
  auxSwapBuffers();
}

void CALLBACK myReshape(int ww,int hh) {
  w =(float) ww ;
  h =(float) hh ;
  glViewport(0,0,ww,hh);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(-w/2,w/2,-h/2,h/2,-10.0,10.0);
  glMatrixMode(GL_MODELVIEW);
}

void CALLBACK idle(void) {
  x += dx ;
  y += dy ;
  if ( x < -w/2+10 )
    dx = -dx ;
  if ( y < -h/2+10 )
    dy = -dy ;
  if ( x >= w/2-10 )
    dx = -dx ;
  if ( y >= h/2-10 )
    dy = -dy ;
  display() ;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGB|AUX_DEPTH);
  auxInitPosition(0,0,300,300);
  auxInitWindow("Une sphère se déplaçant toute seule");
  myinit();
  auxIdleFunc(idle);
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Illustration de l'emploi de la souris

L'exécutable

  • La sphère est immobile et rejoint la souris lorsque l'on clique dans la fenêtre.

Le source: Mouse.cpp

#include "windows.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <stdio.h>

static float x = 0.0F ;
static float y = 0.0F ;
static float w = 0.0F ;
static float h = 0.0F ;

void myinit(void) {
  GLfloat light_ambient[] = { 0.0F,0.0F,0.0F,1.0F };
  GLfloat light_diffuse[] = { 1.0F,1.0F,1.0F,1.0F };
  GLfloat light_specular[] = { 1.0F,1.0F,1.0F,1.0F };
  GLfloat light_position[] = { 1.0F,1.0F,1.0F,0.0F };
  glLightfv(GL_LIGHT0,GL_AMBIENT,light_ambient);
  glLightfv(GL_LIGHT0,GL_DIFFUSE,light_diffuse);
  glLightfv(GL_LIGHT0,GL_SPECULAR,light_specular);
  glLightfv(GL_LIGHT0,GL_POSITION,light_position);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glDepthFunc(GL_LESS);
  glEnable(GL_DEPTH_TEST);
}

void CALLBACK display(void) {
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  glTranslatef(x,y,0.0F);
  auxSolidSphere(10.0);
  glPopMatrix();
  glFlush();
  auxSwapBuffers();
}

void CALLBACK myReshape(int ww,int hh) {
  w =(float) ww ;
  h =(float) hh ;
  glViewport(0,0,ww,hh);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(-w/2,w/2,-h/2,h/2,-10.0,10.0);
  glMatrixMode(GL_MODELVIEW);
}

void CALLBACK mouseUp(AUX_EVENTREC *event) {
  x =(float) event->data[AUX_MOUSEX] - w/2 ;
  y =(float) h/2 - event->data[AUX_MOUSEY] ;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGB|AUX_DEPTH);
  auxInitPosition(0,0,300,300);
  auxInitWindow("Une sphère déplacée à la souris");
  myinit();
  auxMouseFunc(AUX_LEFTBUTTON,AUX_MOUSEUP,mouseUp);
  auxMouseFunc(AUX_RIGHTBUTTON,AUX_MOUSEUP,mouseUp);
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Illustration du reshape homothétique

L'exécutable

p et P pour activer/désactiver la fonctionnalité

Le source: Reshape.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <stdio.h>
#include <stdlib.h>

static int proj = 0 ;
static int ww ;
static int hh ;

void CALLBACK display(void) {
  glClear(GL_COLOR_BUFFER_BIT);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if ( proj )
    glOrtho(-10.0,10.0,-10.0,10.0,-10.0,10.0);
    else
    glOrtho(-10.0,10.0,-10.0*(double) hh/ww,10.0*(double) hh/ww,-10.0,10.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glPushMatrix();
  auxWireSphere(7.0) ;
  glPopMatrix();
  glFlush();
}

void myinit (void) {
  glClearColor(0.0,0.0,0.0,1.0);
}

void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  ww = w ;
  hh = h ;
}

void CALLBACK keyP(void) {
  proj++;
  proj %= 2;
}

void CALLBACK keyp(void) {
  proj++;
  proj %= 2;
}

void main(void) {
  auxInitDisplayMode(AUX_SINGLE|AUX_RGB);
  auxInitPosition(0,0,200,200);
  auxInitWindow("Reshape homothétique");
  myinit();
  auxKeyFunc(AUX_P,keyP);
  auxKeyFunc(AUX_p,keyp);
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


La sélection d'objets

L'exécutable

Le source: Selection.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <stdio.h>

#define BUFSIZE 512

void drawTriangle(GLfloat x1,GLfloat y1,GLfloat x2,GLfloat y2,GLfloat x3,GLfloat y3,GLfloat z) {
  glBegin(GL_TRIANGLES);
  glVertex3f(x1,y1,z);
  glVertex3f(x2,y2,z);
  glVertex3f(x3,y3,z);
  glEnd();
}

void drawViewVol(GLfloat x1,GLfloat x2,GLfloat y1,GLfloat y2,GLfloat z1,GLfloat z2) {
  glColor3f(1.0,1.0,1.0);
  glBegin(GL_LINE_LOOP);
  glVertex3f(x1,y1,-z1);
  glVertex3f(x2,y1,-z1);
  glVertex3f(x2,y2,-z1);
  glVertex3f(x1,y2,-z1);
  glEnd();
  glBegin(GL_LINE_LOOP);
  glVertex3f(x1,y1,-z2);
  glVertex3f(x2,y1,-z2);
  glVertex3f(x2,y2,-z2);
  glVertex3f(x1,y2,-z2);
  glEnd();
  glBegin(GL_LINES);
  glVertex3f(x1,y1,-z1);
  glVertex3f(x1,y1,-z2);
  glVertex3f(x1,y2,-z1);
  glVertex3f(x1,y2,-z2);
  glVertex3f(x2,y1,-z1);
  glVertex3f(x2,y1,-z2);
  glVertex3f(x2,y2,-z1);
  glVertex3f(x2,y2,-z2);
  glEnd();
}

void drawScene(void) {
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(40.0,1.333,0.01,100.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluLookAt(7.5,7.5,12.5,2.5,2.5,-5.0,0.0,1.0,0.0);
  glColor3f(0.0,1.0,0.0);
  drawTriangle(2.0,2.0,3.0,2.0,2.5,3.0,-5.0);
  glColor3f(1.0,0.0,0.0);
  drawTriangle(2.0,7.0,3.0,7.0,2.5,8.0,-5.0);
  glColor3f(1.0,1.0,0.0);
  drawTriangle(2.0,2.0,3.0,2.0,2.5,3.0,0.0);
  drawTriangle(2.0,2.0,3.0,2.0,2.5,3.0,-10.);
  drawViewVol(0.0,5.0,0.0,5.0,0.0,10.0);
}

void processHits(GLint hits,GLuint buffer[]) {
  int i,j;
  GLuint names,*ptr;
  printf("hits = %d\n",hits);
  ptr =(GLuint *) buffer;
  for ( i = 0 ; i < hits ; i++ ) {
    names = *ptr;
    printf("number of names=%d\n",names);
    ptr++;
    printf(" z1 is %u;",*ptr);
    ptr++;
    printf(" z2 is %u\n",*ptr);
    ptr++;
    printf(" the name is ");
    for( j = 0 ; j < (int) names ; j++ )
      printf("%d ",*ptr);
    ptr++;
    printf("\n"); }
}

void myinit(void) {
  glDepthFunc(GL_LESS);
  glEnable(GL_DEPTH_TEST);
  glShadeModel(GL_FLAT);
}

void selectObjects(void) {
  GLuint selectBuf[BUFSIZE];
  GLint hits;
  glSelectBuffer(BUFSIZE,selectBuf);
  glRenderMode(GL_SELECT);
  glInitNames();
  glPushName(-1);
  glPushMatrix();
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(0.0,5.0,0.0,5.0,0.0,10.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glLoadName(1);
  drawTriangle(2.0,2.0,3.0,2.0,2.5,3.0,-5.0);
  glLoadName(2);
  drawTriangle(2.0,7.0,3.0,7.0,2.5,8.0,-5.0);
  glLoadName(3);
  drawTriangle(2.0,2.0,3.0,2.0,2.5,3.0,0.0);
  drawTriangle(2.0,2.0,3.0,2.0,2.5,3.0,-10.);
  glPopMatrix();
  glFlush();
  hits = glRenderMode(GL_RENDER);
  processHits(hits,selectBuf);
}

void CALLBACK display(void) {
  glClearColor(0.0,0.0,0.0,0.0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  drawScene();
  selectObjects();
  glFlush();
}

void main(void) {
  auxInitDisplayMode(AUX_SINGLE|AUX_RGB|AUX_DEPTH);
  auxInitPosition(0,0,200,200);
  auxInitWindow("Sélection d'objets");
  myinit();
  auxMainLoop(display);
}
   RETOUR


Le brouillard

L'exécutable

Le source: Fog.cpp

#include "windows.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <stdio.h>

static GLint fogMode;
static float anglex = 0.0F ;
static float angley = 0.0F ;

void CALLBACK cycleFog(AUX_EVENTREC *event) {
  if(fogMode == GL_EXP) {
    fogMode = GL_EXP2;
    printf("Fog mode is GL_EXP2\n"); }
    else
    if(fogMode == GL_EXP2) {
      fogMode = GL_LINEAR;
      printf("Fog mode is GL_LINEAR\n");
      glFogf(GL_FOG_START,1.0);
      glFogf(GL_FOG_END,5.0); }
      else
      if(fogMode == GL_LINEAR) {
        fogMode = GL_EXP;
        printf("Fog mode is GL_EXP\n"); }
  glFogi(GL_FOG_MODE,fogMode);
}

void myinit(void) {
  GLfloat position[] = { 0.0,3.0,3.0,0.0 };
  GLfloat local_view[] = { 0.0 };
  glEnable(GL_DEPTH_TEST);
  glDepthFunc(GL_LESS);
  glLightfv(GL_LIGHT0,GL_POSITION,position);
  glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER,local_view);
  glFrontFace(GL_CW);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glEnable(GL_AUTO_NORMAL);
  glEnable(GL_NORMALIZE);
  glEnable(GL_FOG);
  GLfloat fogColor[4] = {0.5,0.5,0.5,1.0};
  fogMode = GL_EXP;
  glFogi(GL_FOG_MODE,fogMode);
  glFogfv(GL_FOG_COLOR,fogColor);
  glFogf(GL_FOG_DENSITY,0.35F);
  glHint(GL_FOG_HINT,GL_DONT_CARE);
  glClearColor(0.5,0.5,0.5,1.0);
}

void renderRedTeapot(GLfloat x,GLfloat y,GLfloat z) {
  float mat[4];
  glPushMatrix();
  glTranslatef(x,y,z);
  mat[0] = 0.1745F;
  mat[1] = 0.01175F;
  mat[2] = 0.01175F;
  mat[3] = 1.0F;
  glMaterialfv(GL_FRONT,GL_AMBIENT,mat);
  mat[0] = 0.61424F;
  mat[1] = 0.04136F;
  mat[2] = 0.04136F;
  glMaterialfv(GL_FRONT,GL_DIFFUSE,mat);
  mat[0] = 0.727811F;
  mat[1] = 0.626959F;
  mat[2] = 0.626959F;
  glMaterialfv(GL_FRONT,GL_SPECULAR,mat);
  glMaterialf(GL_FRONT,GL_SHININESS,0.6F*128.0F);
  glColor3f(1,1,1);
  auxSolidTeapot(1.0);
  glPopMatrix();
}

void CALLBACK display(void) {
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  glRotatef(angley,0.0F,1.0F,0.0F) ;
  glRotatef(anglex,1.0F,0.0F,0.0F) ;
  renderRedTeapot(-4.0,-0.5,-1.0);
  renderRedTeapot(-2.0,-0.5,-2.0);
  renderRedTeapot(0.0,-0.5,-3.0);
  renderRedTeapot(2.0,-0.5,-4.0);
  renderRedTeapot(4.0,-0.5,-5.0);
  glPopMatrix();
  glFlush();
  auxSwapBuffers() ;
}

void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if ( w <=(h*3) )
    glOrtho(-6.0,6.0,-2.0*((GLfloat) h*3)/(GLfloat) w,2.0*((GLfloat) h*3)/(GLfloat) w,-10.0,10.0);
    else
    glOrtho(-6.0*(GLfloat) w/((GLfloat) h*3),6.0*(GLfloat) w/((GLfloat) h*3),-2.0,2.0,-10.0,10.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void CALLBACK up(void) {
  anglex -= 2 ;
}

void CALLBACK down(void) {
  anglex += 2 ;
}

void CALLBACK left(void) {
  angley -= 2 ;
}

void CALLBACK right(void) {
  angley += 2 ;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGB|AUX_DEPTH);
  auxInitPosition(0,0,450,150);
  auxInitWindow("Brouillard");
  myinit();
  auxMouseFunc(AUX_LEFTBUTTON,AUX_MOUSEDOWN,cycleFog);
  auxKeyFunc(AUX_UP,up) ;
  auxKeyFunc(AUX_DOWN,down) ;
  auxKeyFunc(AUX_LEFT,left) ;
  auxKeyFunc(AUX_RIGHT,right) ;
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}

RETOUR


Le survol d'un ensemble de sphères

L'exécutable

Le source: Survol.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

static float z = -15.0F ;
static float dz = 1.0F ;

void myinit(void) {
  GLfloat specular[] = { 1.0,1.0,1.0,1.0 };
  GLfloat shinines[] = { 50.0 };
  GLfloat l_pos[] = { 1.0,1.0,1.0,0.0 };
  glClearColor(0.5,0.5,1.0,1.0) ;
  glMaterialfv(GL_FRONT,GL_SPECULAR,specular);
  glMaterialfv(GL_FRONT,GL_SHININESS,shinines);
  glLightfv(GL_LIGHT0,GL_POSITION,l_pos);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glDepthFunc(GL_LESS);
  glEnable(GL_DEPTH_TEST);
}

void ligne(void) {
  glPushMatrix() ;
  auxSolidSphere(1.0) ;
  for ( int x = 0 ; x < 4 ; x++ ) {
    glTranslatef(5.0F,0.0F,0.0F) ;
    auxSolidSphere(1.0) ; }
  glPopMatrix() ;
}

void CALLBACK display(void) {
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  glTranslatef(0.0F,-5.0F,z) ;
  for ( int z = 0 ; z < 20 ; z++ ) {
    glPushMatrix();
    glTranslatef(-10.0F,0.0F,-5.0F*z) ;
    ligne() ;
    glPopMatrix(); }
  glPopMatrix();
  glFlush();
  auxSwapBuffers();
}

void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glFrustum(-2.5,2.5,-2.5*(float)h/(float)w,2.5*(float)h/(float)w,3.,110.);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void CALLBACK idle(void) {
  z+= dz ;
  if ( ( z == 100 ) || ( z == -15 ) )
    dz = -dz ;
  display() ;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGB|AUX_DEPTH);
  auxInitPosition(0,0,250,250);
  auxInitWindow("Sphères blanches en survol");
  myinit();
  auxIdleFunc(idle);
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}

RETOUR


Le système Terre-Lune

L'exécutable

Le source: TerreLune.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <stdio.h>
#include <stdlib.h>

static int year = 0, day = 0;

void CALLBACK dayAdd(void) {
  day = (day + 10) % 360;
}

void CALLBACK daySubtract(void) {
  day = (day - 10) % 360;
}

void CALLBACK yearAdd(void) {
  year = (year + 5) % 360;
}

void CALLBACK yearSubtract(void) {
  year = (year - 5) % 360;
}

void CALLBACK display(void) {
  glClear(GL_COLOR_BUFFER_BIT);
  glColor3f(0.0F,0.0F,0.0F);
  glPushMatrix();
  auxWireSphere(1.0);
  glRotatef((GLfloat) year,0.0,1.0,0.0);
  glTranslatef(2.0,0.0,0.0);
  glRotatef((GLfloat) day,0.0,1.0,0.0);
  auxWireSphere(0.2);
  glPopMatrix();
  glFlush();
  auxSwapBuffers();
}

void myinit(void) {
  glClearColor(1.0,1.0,1.0,1.0);
}

void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(36.0,(float) w/(float) h,1.0,20.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef(0.0,0.0,-5.0);
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGB);
  auxInitPosition(0,0,300,200);
  auxInitWindow("Système Terre-Lune");
  myinit();
  auxKeyFunc(AUX_LEFT,yearSubtract);
  auxKeyFunc(AUX_RIGHT,yearAdd);
  auxKeyFunc(AUX_UP,dayAdd);
  auxKeyFunc(AUX_DOWN,daySubtract);
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Les matériaux obtenus par paramètrage

L'exécutable

  • Les matériaux

Le source: Material.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void) {
  GLfloat ambient[] = {0.0F,0.0F,0.0F,1.0F};
  GLfloat diffuse[] = {1.0F,1.0F,1.0F,1.0F};
  GLfloat specular[] = {1.0F,1.0F,1.0F,1.0F};
  GLfloat position[] = {0.0F,3.0F,2.0F,0.0F};
  GLfloat mod_ambt[] = {0.4F,0.4F,0.4F,1.0F};
  GLfloat local_view[] = {0.0F};
  glEnable(GL_DEPTH_TEST);
  glDepthFunc(GL_LESS);
  glLightfv(GL_LIGHT0,GL_AMBIENT,ambient);
  glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuse);
  glLightfv(GL_LIGHT0,GL_POSITION,position);
  glLightModelfv(GL_LIGHT_MODEL_AMBIENT,mod_ambt);
  glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER,local_view);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glClearColor(0.0F,0.1F,0.1F,0.0F);
}

void CALLBACK display(void) {
  GLfloat no_mat[] = { 0.0F,0.0F,0.0F,1.0F };
  GLfloat mat_ambient[] = { 0.7F,0.7F,0.7F,1.0F };
  GLfloat mat_ambient_color[] = { 0.8F,0.8F,0.2F,1.0F };
  GLfloat mat_diffuse[] = { 0.1F,0.5F,0.8F,1.0F };
  GLfloat mat_specular[] = { 1.0F,1.0F,1.0F,1.0F };
  GLfloat no_shininess[] = { 0.0F };
  GLfloat low_shininess[] = { 5.0F };
  GLfloat high_shininess[] = { 100.0F };
  GLfloat mat_emission[] = {0.3F,0.2F,0.2F,0.0F};
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  glTranslatef(-3.75F,3.0F,0.0F);
  glMaterialfv(GL_FRONT,GL_AMBIENT,no_mat);
  glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
  glMaterialfv(GL_FRONT,GL_SPECULAR,no_mat);
  glMaterialfv(GL_FRONT,GL_SHININESS,no_shininess);
  glMaterialfv(GL_FRONT,GL_EMISSION,no_mat);
  auxSolidSphere(1.0);
  glPopMatrix();
  glPushMatrix();
  glTranslatef(-1.25F,3.0F,0.0F);
  glMaterialfv(GL_FRONT,GL_AMBIENT,no_mat);
  glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
  glMaterialfv(GL_FRONT,GL_SPECULAR,mat_specular);
  glMaterialfv(GL_FRONT,GL_SHININESS,low_shininess);
  glMaterialfv(GL_FRONT,GL_EMISSION,no_mat);
  auxSolidSphere(1.0);
  glPopMatrix();
  glPushMatrix();
  glTranslatef(1.25F,3.0F,0.0F);
  glMaterialfv(GL_FRONT,GL_AMBIENT,no_mat);
  glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
  glMaterialfv(GL_FRONT,GL_SPECULAR,mat_specular);
  glMaterialfv(GL_FRONT,GL_SHININESS,high_shininess);
  glMaterialfv(GL_FRONT,GL_EMISSION,no_mat);
  auxSolidSphere(1.0);
  glPopMatrix();
  glPushMatrix();
  glTranslatef(3.75F,3.0F,0.0F);
  glMaterialfv(GL_FRONT,GL_AMBIENT,no_mat);
  glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
  glMaterialfv(GL_FRONT,GL_SPECULAR,no_mat);
  glMaterialfv(GL_FRONT,GL_SHININESS,no_shininess);
  glMaterialfv(GL_FRONT,GL_EMISSION,mat_emission);
  auxSolidSphere(1.0);
  glPopMatrix();
  glPushMatrix();
  glTranslatef(-3.75F,0.0F,0.0F);
  glMaterialfv(GL_FRONT,GL_AMBIENT,mat_ambient);
  glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
  glMaterialfv(GL_FRONT,GL_SPECULAR,no_mat);
  glMaterialfv(GL_FRONT,GL_SHININESS,no_shininess);
  glMaterialfv(GL_FRONT,GL_EMISSION,no_mat);
  auxSolidSphere(1.0);
  glPopMatrix();
  glPushMatrix();
  glTranslatef(-1.25F,0.0F,0.0F);
  glMaterialfv(GL_FRONT,GL_AMBIENT,mat_ambient);
  glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
  glMaterialfv(GL_FRONT,GL_SPECULAR,mat_specular);
  glMaterialfv(GL_FRONT,GL_SHININESS,low_shininess);
  glMaterialfv(GL_FRONT,GL_EMISSION,no_mat);
  auxSolidSphere(1.0);
  glPopMatrix();
  glPushMatrix();
  glTranslatef(1.25F,0.0F,0.0F);
  glMaterialfv(GL_FRONT,GL_AMBIENT,mat_ambient);
  glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
  glMaterialfv(GL_FRONT,GL_SPECULAR,mat_specular);
  glMaterialfv(GL_FRONT,GL_SHININESS,high_shininess);
  glMaterialfv(GL_FRONT,GL_EMISSION,no_mat);
  auxSolidSphere(1.0);
  glPopMatrix();
  glPushMatrix();
  glTranslatef(3.75F,0.0F,0.0F);
  glMaterialfv(GL_FRONT,GL_AMBIENT,mat_ambient);
  glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
  glMaterialfv(GL_FRONT,GL_SPECULAR,no_mat);
  glMaterialfv(GL_FRONT,GL_SHININESS,no_shininess);
  glMaterialfv(GL_FRONT,GL_EMISSION,mat_emission);
  auxSolidSphere(1.0);
  glPopMatrix();
  glPushMatrix();
  glTranslatef(-3.75F,-3.0F,0.0F);
  glMaterialfv(GL_FRONT,GL_AMBIENT,mat_ambient_color);
  glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
  glMaterialfv(GL_FRONT,GL_SPECULAR,no_mat);
  glMaterialfv(GL_FRONT,GL_SHININESS,no_shininess);
  glMaterialfv(GL_FRONT,GL_EMISSION,no_mat);
  auxSolidSphere(1.0);
  glPopMatrix();
  glPushMatrix();
  glTranslatef(-1.25F,-3.0F,0.0F);
  glMaterialfv(GL_FRONT,GL_AMBIENT,mat_ambient_color);
  glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
  glMaterialfv(GL_FRONT,GL_SPECULAR,mat_specular);
  glMaterialfv(GL_FRONT,GL_SHININESS,low_shininess);
  glMaterialfv(GL_FRONT,GL_EMISSION,no_mat);
  auxSolidSphere(1.0);
  glPopMatrix();
  glPushMatrix();
  glTranslatef(1.25F,-3.0F,0.0F);
  glMaterialfv(GL_FRONT,GL_AMBIENT,mat_ambient_color);
  glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
  glMaterialfv(GL_FRONT,GL_SPECULAR,mat_specular);
  glMaterialfv(GL_FRONT,GL_SHININESS,high_shininess);
  glMaterialfv(GL_FRONT,GL_EMISSION,no_mat);
  auxSolidSphere(1.0);
  glPopMatrix();
  glPushMatrix();
  glTranslatef(3.75F,-3.0F,0.0F);
  glMaterialfv(GL_FRONT,GL_AMBIENT,mat_ambient_color);
  glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
  glMaterialfv(GL_FRONT,GL_SPECULAR,no_mat);
  glMaterialfv(GL_FRONT,GL_SHININESS,no_shininess);
  glMaterialfv(GL_FRONT,GL_EMISSION,mat_emission);
  auxSolidSphere(1.0);
  glPopMatrix();
  glFlush();
}

void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if( w <=(h * 2) )
    glOrtho(-6.,6.,-3.*((float)h*2)/(float)w,3.0*((float)h*2)/(float)w,-10.0,10.0);
    else
    glOrtho(-6.0*(float)w/((float)h*2),6.0*(float)w/((float)h*2),-3.0,3.0,-10.0,10.0);
  glMatrixMode(GL_MODELVIEW);
}

void main(void) {
  auxInitDisplayMode(AUX_SINGLE|AUX_RGB|AUX_DEPTH);
  auxInitPosition(0,0,340,250);
  auxInitWindow("Material");
  myinit();
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Lumière et matériel : une sphère blanche

L'exécutable

Une sphère blanche illustrant:

  • l'utilisation du auxMainLoop,
  • les lumières et les matériaux.

Le source: SphereBlanche.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

void myinit(void) {
  GLfloat specular[] = { 1.0,1.0,1.0,1.0 };
  GLfloat shinines[] = { 50.0 };
  GLfloat l_pos[] = { 1.0,1.0,1.0,0.0 };
  glClearColor(0.5,0.5,1.0,1.0) ;
  glMaterialfv(GL_FRONT,GL_SPECULAR,specular);
  glMaterialfv(GL_FRONT,GL_SHININESS,shinines);
  glLightfv(GL_LIGHT0,GL_POSITION,l_pos);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glDepthFunc(GL_LESS);
  glEnable(GL_DEPTH_TEST);
}

void CALLBACK display(void) {
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  auxSolidSphere(1.0);
  glFlush();
}

void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if (w <= h)
    glOrtho(-1.5,1.5,-1.5*(float)h/(float)w,1.5*(float)h/(float)w,-10.,10.);
    else
    glOrtho(-1.5*(float)w/(float)h,1.5*(float)w/(float)h,-1.5,1.5,-10.,10.);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void main(void) {
  auxInitDisplayMode(AUX_SINGLE|AUX_RGB|AUX_DEPTH);
  auxInitPosition(0,0,250,250);
  auxInitWindow("Sphère blanche");
  myinit();
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Objets définis dans l'Auxiliary library

L'exécutable

  • Espace pour passer d'un objet à un autre
  • Up, down, right et left pour tourner les objets

Le source: ObjetsAuxiliary.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <stdio.h>
#include <stdlib.h>

static float anglex = 0.0F ;
static float angley = 0.0F ;
static int obj = 0 ;

void CALLBACK display(void) {
  glClearColor (1.0,1.0,1.0,1.0);
  glClear(GL_COLOR_BUFFER_BIT);
  glColor3f(0.0F,0.0F,0.0F);
  glPushMatrix();
  glRotatef(angley,0.0F,1.0F,0.0F) ;
  glRotatef(anglex,1.0F,0.0F,0.0F) ;
  switch ( obj ) {
    case 0  : auxWireSphere(7.0) ;
              break ;
    case 1  : auxWireCube(11.0) ;
              break ;
    case 2  : auxWireCone(6.0,9.0) ;
              break ;
    case 3  : auxWireCylinder(3.0,9.0) ;
              break ;
    case 4  : auxWireBox(4.0,7.0,12.0) ;
              break ;
    case 5  : auxWireTorus(2.0,6.0) ;
              break ;
    case 6  : auxWireTetrahedron(9.0) ;
              break ;
    case 7  : auxWireOctahedron(9.0) ;
              break ;
    case 8  : auxWireDodecahedron(9.0) ;
              break ;
    case 9  : auxWireIcosahedron(9.0) ;
              break ;
    case 10 : auxWireTeapot(7.0) ; }
  glPopMatrix();
  glFlush();
  auxSwapBuffers();
}

void myinit (void) {
}

void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(-10.0,10.0,-10.0*h/w,10.0*h/w,-10.0,10.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void CALLBACK up(void) {
  anglex++ ;
}

void CALLBACK down(void) {
  anglex-- ;
}

void CALLBACK left(void) {
  angley++ ;
}

void CALLBACK right(void) {
  angley-- ;
}

void CALLBACK space(void) {
  obj++ ;
  if ( obj == 11 )
    obj = 0 ;
}

void main(void) {
  auxInitDisplayMode (AUX_DOUBLE | AUX_RGBA);
  auxInitPosition (0, 0, 500, 500);
  auxInitWindow ("Objets");
  myinit ();
  auxKeyFunc(AUX_UP,up) ;
  auxKeyFunc(AUX_DOWN,down) ;
  auxKeyFunc(AUX_LEFT,left) ;
  auxKeyFunc(AUX_RIGHT,right) ;
  auxKeyFunc(AUX_SPACE,space) ;
  auxReshapeFunc (myReshape);
  auxMainLoop(display);
}
   RETOUR


Ombrages flat et smooth

L'exécutable

  • space pour switcher entre les deux modes d'ombrage
  • return pour switcher entre filaire et solide

Le source: FlatEtSmooth.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <stdio.h>
#include <stdlib.h>

static int mode = 0 ;

void CALLBACK display(void) {
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  if ( mode )
    glShadeModel(GL_SMOOTH);
    else
    glShadeModel(GL_FLAT);
  glPushMatrix() ;
  glTranslatef(-0.8F,0.0F,0.0F);
  glRotatef(50.0F,1.0F,1.0F,1.0F);
  auxSolidSphere(1.1) ;
  glPopMatrix() ;
  glPushMatrix() ;
  glTranslatef(0.9F,-1.0F,0.0F);
  glRotatef(-70.0F,1.0F,0.0F,0.0F);
  auxSolidCone(0.9,2.0) ;
  glPopMatrix() ;
  glPushMatrix() ;
  glTranslatef(0.8F,0.1F,0.0F);
  glRotatef(-70.0F,1.0F,0.0F,0.0F);
  auxSolidTorus(0.35,0.8) ;
  glPopMatrix() ;
  glFlush();
  auxSwapBuffers();
}

void myinit (void) {
  glClearColor(1.0,1.0,1.0,1.0);
  glEnable(GL_DEPTH_TEST) ;
  glEnable(GL_LIGHTING) ;
  glEnable(GL_LIGHT0) ;
  float dir[] = {0.1F,0.8F,1.6F,0.0F} ;
  glLightfv(GL_LIGHT0,GL_POSITION,dir) ;
}

void CALLBACK myReshape(int w, int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(-2.0,2.0,-2.0/w*h,2.0/w*h,-2.0,2.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void CALLBACK auxSpace(void) {
  mode = 1 - mode ;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGBA|AUX_DEPTH);
  auxInitPosition(0,0,500,500);
  auxInitWindow("Affichages flat et smooth") ;
  myinit();
  auxKeyFunc(AUX_SPACE,auxSpace) ;
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Primitives graphiques

L'exécutable

Le source: Primitives.cpp

#include "windows.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <stdio.h>
#include <stdlib.h>

static float anglex = 0.0F ;
static float angley = 0.0F ;
static int obj = 0 ;

void CALLBACK display(void) {
  glClearColor (1.0,1.0,1.0,1.0);
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glColor3f(0.0F,0.0F,0.0F);
  glPushMatrix();
  glRotatef(angley,0.0F,1.0F,0.0F) ;
  glRotatef(anglex,1.0F,0.0F,0.0F) ;
  switch ( obj ) {
    case 0  : glBegin(GL_POINTS) ;
              glVertex3f(-7.0F,4.0F,1.0F) ;
              glVertex3f(8.0F,-1.0F,-2.0F) ;
              glVertex3f(-3.0F,7.0F,3.0F) ;
              glVertex3f(-1.0F,-8.0F,0.0F) ;
              glVertex3f(2.0F,-4.0F,5.0F) ;
              glVertex3f(5.0F,8.0F,1.0F) ;
              glEnd() ;
              break ;
    case 1  : glBegin(GL_LINES) ;
              glVertex3f(-7.0F,4.0F,-1.0F) ;
              glVertex3f(8.0F,-1.0F,2.0F) ;
              glVertex3f(-3.0F,7.0F,0.0F) ;
              glVertex3f(-1.0F,-8.0F,4.0F) ;
              glVertex3f(2.0F,-4.0F,-2.0F) ;
              glVertex3f(5.0F,8.0F,3.0F) ;
              glEnd() ;
              break ;
    case 2  : glBegin(GL_LINE_STRIP) ;
              glVertex3f(-7.0F,4.0F,-1.0F) ;
              glVertex3f(8.0F,-1.0F,2.0F) ;
              glVertex3f(-3.0F,7.0F,0.0F) ;
              glVertex3f(-1.0F,-8.0F,4.0F) ;
              glVertex3f(2.0F,-4.0F,-2.0F) ;
              glVertex3f(5.0F,8.0F,3.0F) ;
              glEnd() ;
              break ;
    case 3  : glBegin(GL_LINE_LOOP) ;
              glVertex3f(-7.0F,4.0F,-1.0F) ;
              glVertex3f(8.0F,-1.0F,2.0F) ;
              glVertex3f(-3.0F,7.0F,0.0F) ;
              glVertex3f(-1.0F,-8.0F,4.0F) ;
              glVertex3f(2.0F,-4.0F,-2.0F) ;
              glVertex3f(5.0F,8.0F,3.0F) ;
              glEnd() ;
              break ;
    case 4  : glBegin(GL_POLYGON) ;
              glColor3f(0.0F,0.0F,1.0F) ;
              glVertex3f(-7.0F,4.0F,0.0F) ;
              glVertex3f(-3.0F,7.0F,0.0F) ;
              glVertex3f(5.0F,8.0F,0.0F) ;
              glVertex3f(8.0F,-1.0F,0.0F) ;
              glVertex3f(2.0F,-4.0F,0.0F) ;
              glVertex3f(-1.0F,-8.0F,0.0F) ;
              glEnd() ;
              break ;
    case 5  : glBegin(GL_QUADS) ;
              glColor3f(0.0F,0.0F,1.0F) ;
              glVertex3f(-7.0F,2.0F,1.0F) ;
              glVertex3f(-1.0F,7.0F,1.0F) ;
              glVertex3f(2.0F,6.0F,1.0F) ;
              glVertex3f(-6.0F,-3.0F,1.0F) ;
              glColor3f(1.0F,0.0F,0.0F) ;
              glVertex3f(3.0F,-8.0F,-2.0F) ;
              glVertex3f(7.0F,-5.0F,-2.0F) ;
              glVertex3f(9.0F,2.0F,-2.0F) ;
              glVertex3f(1.0F,-3.0F,-2.0F) ;
              glEnd() ;
              break ;
    case 6  : glBegin(GL_QUAD_STRIP) ;
              glColor3f(0.0F,0.0F,1.0F) ;
              glVertex3f(-7.0F,5.0F,0.0F) ;
              glVertex3f(-3.0F,8.0F,0.0F) ;
              glColor3f(1.0F,0.0F,0.0F) ;
              glVertex3f(-4.0F,-2.0F,0.0F) ;
              glVertex3f(0.0F,3.0F,0.0F) ;
              glColor3f(0.0F,1.0F,0.0F) ;
              glVertex3f(2.0F,-2.5F,0.0F) ;
              glVertex3f(3.0F,5.0F,0.0F) ;
              glColor3f(0.0F,1.0F,1.0F) ;
              glVertex3f(8.5F,0.0F,0.0F) ;
              glVertex3f(8.0F,2.0F,0.0F) ;
              glEnd() ;
              break ;
    case 7  : glBegin(GL_TRIANGLES) ;
              glColor3f(0.0F,0.0F,1.0F) ;
              glVertex3f(-7.0F,-6.0F,2.0F) ;
              glVertex3f(-5.0F,7.0F,-4.0F) ;
              glVertex3f(2.0F,-5.0F,-1.0F) ;
              glColor3f(0.0F,1.0F,0.0F) ;
              glVertex3f(-3.0F,8.0F,-5.0F) ;
              glVertex3f(7.0F,-5.0F,3.0F) ;
              glVertex3f(9.0F,7.0F,2.0F) ;
              glEnd() ;
              break ;
    case 8  : glBegin(GL_TRIANGLE_STRIP) ;
              glColor3f(0.0F,0.0F,1.0F) ;
              glVertex3f(-7.0F,5.0F,3.0F) ;
              glVertex3f(-3.0F,8.0F,2.0F) ;
              glColor3f(1.0F,0.0F,0.0F) ;
              glVertex3f(-4.0F,-2.0F,-1.0F) ;
              glVertex3f(0.0F,3.0F,0.0F) ;
              glColor3f(0.0F,1.0F,0.0F) ;
              glVertex3f(2.0F,-2.5F,5.0F) ;
              glVertex3f(3.0F,5.0F,3.0F) ;
              glColor3f(0.0F,1.0F,1.0F) ;
              glVertex3f(8.5F,0.0F,-1.0F) ;
              glVertex3f(8.0F,2.0F,-2.0F) ;
              glEnd() ;
              break ;
    case 9  : glBegin(GL_TRIANGLE_FAN) ;
              glColor3f(0.0F,0.0F,1.0F) ;
              glVertex3f(0.0F,7.0F,3.0F) ;
              glColor3f(1.0F,0.0F,0.0F) ;
              glVertex3f(-7.0F,-2.0F,2.0F) ;
              glColor3f(0.0F,1.0F,0.0F) ;
              glVertex3f(-4.0F,-6.0F,-1.0F) ;
              glColor3f(1.0F,0.0F,0.0F) ;
              glVertex3f(-1.0F,-8.0F,0.0F) ;
              glColor3f(0.0F,1.0F,0.0F) ;
              glVertex3f(1.0F,-1.0F,5.0F) ;
              glColor3f(1.0F,0.0F,0.0F) ;
              glVertex3f(3.0F,-4.0F,3.0F) ;
              glColor3f(0.0F,1.0F,0.0F) ;
              glVertex3f(6.0F,1.0F,-1.0F) ;
              glColor3f(1.0F,0.0F,0.0F) ;
              glVertex3f(8.0F,5.0F,-2.0F) ;
              glEnd() ; }
  glPopMatrix();
  glFlush();
  auxSwapBuffers();
}

void myinit (void) {
  glEnable(GL_DEPTH_TEST) ;
}

void CALLBACK myReshape(int w, int h) {
  glViewport(0, 0, w, h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(-10.0,10.0,-10.0/w*h,10.0/w*h,-20.0,20.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity ();
}

void CALLBACK up(void) {
  anglex++ ;
}

void CALLBACK down(void) {
  anglex-- ;
}

void CALLBACK left(void) {
  angley++ ;
}

void CALLBACK right(void) {
  angley-- ;
}

void CALLBACK space(void) {
  obj++ ;
  if ( obj == 10 )
    obj = 0 ;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGBA|AUX_DEPTH);
  auxInitPosition(0,0,200,200);
  auxInitWindow("Primitives graphiques");
  myinit();
  auxKeyFunc(AUX_UP,up) ;
  auxKeyFunc(AUX_DOWN,down) ;
  auxKeyFunc(AUX_LEFT,left) ;
  auxKeyFunc(AUX_RIGHT,right) ;
  auxKeyFunc(AUX_SPACE,space) ;
  auxReshapeFunc (myReshape);
  auxMainLoop(display);
}
   RETOUR


Problème possible lié à l'utilisation de normales moyennes

L'exécutable

Le source: ProblemeGouraud.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

static float anglex = 0.0F ;
static float angley = 0.0F ;

void initlights(void) {
  GLfloat ambient[] = { 0.0F,0.0F,0.0F,1.0F };
  GLfloat diffuse[] = { 1.0F,0.7F,0.4F,1.0F };
  GLfloat spec[] = { 0.0F,0.0F,0.0F,1.0F };
  GLfloat shininess[] = { 0.0F };
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glLightfv(GL_LIGHT0,GL_AMBIENT,ambient);
  glMaterialfv(GL_FRONT,GL_DIFFUSE,diffuse);
  glMaterialfv(GL_FRONT,GL_SPECULAR,spec);
  glMaterialfv(GL_FRONT,GL_SHININESS,shininess);
}

void CALLBACK display(void) {
  GLfloat pos[] = { -1.0F,0.0F,1.0F,0.0F };
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  glRotatef(angley,0.0F,1.0F,0.0F) ;
  glRotatef(anglex,1.0F,0.0F,0.0F) ;
  glLightfv(GL_LIGHT0,GL_POSITION,pos);
  glBegin(GL_QUAD_STRIP) ;
  glNormal3f(-0.707F,0.0F,0.707F) ;
  glVertex3f(-3.0F,2.0F,-1.0F) ;
  glVertex3f(-3.0F,-2.0F,-1.0F) ;
  glNormal3f(0.0F,0.0F,1.0F) ;
  glVertex3f(-2.0F,2.0F,1.0F) ;
  glVertex3f(-2.0F,-2.0F,1.0F) ;
  glVertex3f(-1.0F,2.0F,-1.0F) ;
  glVertex3f(-1.0F,-2.0F,-1.0F) ;
  glVertex3f(0.0F,2.0F,1.0F) ;
  glVertex3f(0.0F,-2.0F,1.0F) ;
  glVertex3f(1.0F,2.0F,-1.0F) ;
  glVertex3f(1.0F,-2.0F,-1.0F) ;
  glVertex3f(2.0F,2.0F,1.0F) ;
  glVertex3f(2.0F,-2.0F,1.0F) ;
  glNormal3f(0.707F,0.0F,0.707F) ;
  glVertex3f(3.0F,2.0F,-1.0F) ;
  glVertex3f(3.0F,-2.0F,-1.0F) ;
  glEnd() ;
  glPopMatrix();
  glFlush();
  auxSwapBuffers();
}

void myinit(void) {
  glClearColor(0.8F,0.8F,1.0F,1.0F);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_AUTO_NORMAL);
  glEnable(GL_NORMALIZE);
  initlights();
}

void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if ( w <= h )
    glOrtho(-3.2,3.2,-3.2*(float)h/(float)w,3.2*(float)h/(float)w,-4.0,4.0);
    else
    glOrtho(-3.2*(float)w/(float)h,3.2*(float)w/(float)h,-3.2,3.2,-4.0,4.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void CALLBACK up(void) {
  anglex-- ;
}

void CALLBACK down(void) {
  anglex++ ;
}

void CALLBACK left(void) {
  angley-- ;
}

void CALLBACK right(void) {
  angley++ ;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGB|AUX_DEPTH);
  auxInitPosition(0,0,300,200);
  auxInitWindow("Problème avec Gouraud");
  myinit();
  auxKeyFunc(AUX_UP,up) ;
  auxKeyFunc(AUX_DOWN,down) ;
  auxKeyFunc(AUX_LEFT,left) ;
  auxKeyFunc(AUX_RIGHT,right) ;
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}

RETOUR


Programme illustrant le processus de visualisation

L'exécutable

Le source: Visualisation.cpp

#include "windows.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <stdio.h>
#include <stdlib.h>

static float rotx = 0.0f ;
static float roty = 0.0f ;
static float rotz = 0.0f ;

void myinit(void) {
  glShadeModel(GL_FLAT);
}

void CALLBACK myReshape(int w, int h) {
  glViewport(0,0,w,h);             /*(4)*/
  glMatrixMode(GL_PROJECTION);     /*(3)*/
  glLoadIdentity();                /*(3)*/
  glFrustum(-1.,1.,-1.,1.,1.5,20.);/*(3)*/
  glMatrixMode(GL_MODELVIEW);      /*(2-1)*/
}

void CALLBACK display(void) {
  glClear(GL_COLOR_BUFFER_BIT);
  glColor3f(1.0,1.0,1.0);
  glLoadIdentity();                /*(2)*/
  glTranslatef(0.0,0.0,-5.0);      /*(2)*/
  glRotatef(rotx,1.0f,0.0f,0.0f);  /*(2)*/
  glRotatef(roty,0.0f,1.0f,0.0f);  /*(2)*/
  glRotatef(rotz,0.0f,0.0f,1.0f);  /*(2)*/
  glScalef(1.0,2.0,3.0);           /*(1)*/
  auxWireCube(1.0);                /*(1)*/
  glFlush();
}

void CALLBACK auxX(void) {
  rotx += 2 ;
}

void CALLBACK auxx(void) {
  rotx -= 2 ;
}

void CALLBACK auxY(void) {
  roty += 2 ;
}

void CALLBACK auxy(void) {
  roty -= 2 ;
}

void CALLBACK auxZ(void) {
  rotz += 2 ;
}

void CALLBACK auxz(void) {
  rotz -= 2 ;
}

void main() {
  auxInitDisplayMode(AUX_SINGLE|AUX_RGB);
  auxInitPosition(0,0,200,200);
  auxInitWindow("Visualisation");
  myinit();
  auxKeyFunc(AUX_X,auxX) ;
  auxKeyFunc(AUX_x,auxx) ;
  auxKeyFunc(AUX_Y,auxY) ;
  auxKeyFunc(AUX_y,auxy) ;
  auxKeyFunc(AUX_Z,auxZ) ;
  auxKeyFunc(AUX_z,auxz) ;
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Projections de visualisation

L'exécutable

  • z et Z pour régler le zoom
  • o et O pour régler l'ouverture en perspective
  • p et P pour passer de la perspective à la projection orthographique
  • up, down, left et right pour les contrôles d'orientation

Projections orthographique et en perspective

Projection en perspective avec rapprochement de la scène

Projection en perspective : très grosses déformations

Le source: Projections.cpp

#include <windows.h>

#include <stdio.h>
#include <stdlib.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include "GL/glaux.h"

static int proj = 0 ;
static int ww ;
static int hh ;
static float anglex = 0.0F ;
static float angley = 0.0F ;
static float zoomPersp = 1.0F ;
static float zoomParal = 1.0F ;
static float ouverture = 1.0F ;

void projection(void) {
  if ( proj ) {
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0*ouverture,(double) ww/hh,0.5,200.0);
    glMatrixMode(GL_MODELVIEW);
    glTranslatef(0.0F,0.0F,-5.0F*zoomPersp); }
    else {
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-2.5*zoomParal,2.5*zoomParal,-2.5*zoomParal*(double) hh/ww,2.5*zoomParal*(double) hh/ww,-25,25);
    glMatrixMode(GL_MODELVIEW);
    glTranslatef(0.0F,0.0F,-5.0F); }
}

void CALLBACK display(void) {
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  glColor3f(1.0F,1.0F,1.0F);
  projection();
  glPushMatrix();
  glRotatef(anglex,1.0F,0.0F,0.0F);
  glRotatef(angley,0.0F,1.0F,0.0F);
  glTranslatef(-1.0F,0.0F,0.0F) ;
  float jaune[] = { 1.0F,1.0F,0.0F };
  glMaterialfv(GL_FRONT,GL_DIFFUSE,jaune);
  auxSolidCube(2.0);
  glTranslatef(2.0F,0.0F,0.0F) ;
  float bleu[] = { 0.0F,0.0F,1.0F };
  glMaterialfv(GL_FRONT,GL_DIFFUSE,bleu);
  auxSolidSphere(1.3);
  glPopMatrix();
  glPopMatrix();
  glFlush();
  auxSwapBuffers() ;
}

void CALLBACK myinit(void) {
  glShadeModel(GL_SMOOTH);
  glDepthFunc(GL_LESS) ;
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
}

void CALLBACK myReshape(int w, int h) {
  glViewport(0,0,w,h);
  ww = w ;
  hh = h ;
}

void CALLBACK keyo(void) {
  ouverture *= 1.01F ;
  ouverture *= 1.01F ;
}

void CALLBACK keyO(void) {
  ouverture /= 1.01F ;
  ouverture /= 1.01F ;
}

void CALLBACK keyz(void) {
  zoomPersp *= 1.01F ;
  zoomParal *= 1.01F ;
}

void CALLBACK keyZ(void) {
  zoomPersp /= 1.01F ;
  zoomParal /= 1.01F ;
}

void CALLBACK keyP(void) {
  proj++;
  proj %= 2;
}

void CALLBACK keyp(void) {
  proj++;
  proj %= 2;
}

void CALLBACK up(void) {
  anglex++ ;
}

void CALLBACK down(void) {
  anglex-- ;
}

void CALLBACK left(void) {
  angley++ ;
}

void CALLBACK right(void) {
  angley-- ;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGBA|AUX_DEPTH);
  auxInitPosition(0,0,200,200);
  auxInitWindow("Visualisations");
  myinit();
  auxKeyFunc(AUX_O,keyO);
  auxKeyFunc(AUX_o,keyo);
  auxKeyFunc(AUX_P,keyP);
  auxKeyFunc(AUX_p,keyp);
  auxKeyFunc(AUX_Z,keyZ);
  auxKeyFunc(AUX_z,keyz);
  auxKeyFunc(AUX_UP,up) ;
  auxKeyFunc(AUX_DOWN,down) ;
  auxKeyFunc(AUX_LEFT,left) ;
  auxKeyFunc(AUX_RIGHT,right) ;
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Sauvegarde d'une image

L'exécutable

  • s ou S pour sauvegarder l'image dans un fichier RAW

Le source: Sauvegarde.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <stdio.h>

static int ww ;
static int hh ;

void myinit(void) {
  GLfloat specular[] = { 1.0,1.0,1.0,1.0 };
  GLfloat shinines[] = { 50.0 };
  GLfloat l_pos[] = { 1.0,1.0,1.0,0.0 };
  glClearColor(0.5,0.5,1.0,1.0) ;
  glMaterialfv(GL_FRONT,GL_SPECULAR,specular);
  glMaterialfv(GL_FRONT,GL_SHININESS,shinines);
  glLightfv(GL_LIGHT0,GL_POSITION,l_pos);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glDepthFunc(GL_LESS);
  glEnable(GL_DEPTH_TEST);
}

void CALLBACK display(void) {
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  auxSolidSphere(1.0);
  glFlush();
}

void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  ww = w ;
  hh = h ;
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if (w <= h)
    glOrtho(-1.5,1.5,-1.5*(float)h/(float)w,1.5*(float)h/(float)w,-10.,10.);
    else
    glOrtho(-1.5*(float)w/(float)h,1.5*(float)w/(float)h,-1.5,1.5,-10.,10.);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void sauve(char *nom,int px,int py,int dx,int dy) {
  GLubyte *image ;
  FILE *num ;
  int y,x ;
  image =(GLubyte *) calloc(dx*dy*3,sizeof(GLubyte));
  glReadPixels(px,py,dx,dy,GL_RGB,GL_UNSIGNED_BYTE,image);
  num = fopen(nom,"wb") ;
  if ( num ) {
    for ( y = dy-1 ; y >= 0 ; y-- ) {
      for ( x = 0 ; x < dx ; x++ ) {
        fwrite((void *) &image[(y*dx+x)*3],sizeof(GLubyte),3,num) ; } }
    fclose(num) ; }
  free(image) ;
}

void CALLBACK keyS(void) {
  sauve("image.raw",0,0,ww,hh) ;
}

void main(void) {
  auxInitDisplayMode(AUX_SINGLE|AUX_RGB|AUX_DEPTH);
  auxInitPosition(0,0,256,256);
  auxInitWindow("Sphère blanche");
  myinit();
  auxKeyFunc(AUX_S,keyS) ;
  auxKeyFunc(AUX_s,keyS) ;
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Un AT-AT de l'Empire

L'exécutable

Le fichier de définition

Le source (OpengGL 1.1 nécessaire)

   RETOUR


Un bras robot

L'exécutable

Le source: BrasRobot.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <stdio.h>
#include <stdlib.h>

static int shoulder = 0, elbow = 0;

void CALLBACK elbowAdd(void) {
  elbow = (elbow + 5) % 360;
}

void CALLBACK elbowSubtract(void) {
  elbow = (elbow - 5) % 360;
}

void CALLBACK shoulderAdd(void) {
  shoulder = (shoulder + 5) % 360;
}

void CALLBACK shoulderSubtract(void) {
  shoulder = (shoulder - 5) % 360;
}

void CALLBACK display(void) {
  glClear(GL_COLOR_BUFFER_BIT);
  glColor3f(0.0F,0.0F,0.0F);
  glPushMatrix();
  glTranslatef(-1.0,0.0,0.0);
  glRotatef((GLfloat) shoulder,0.0,0.0,1.0);
  glTranslatef(1.0,0.0,0.0);
  auxWireBox(2.0,0.4,1.0);
  glTranslatef(1.0,0.0,0.0);
  glRotatef((GLfloat) elbow,0.0,0.0,1.0);
  glTranslatef(1.0,0.0,0.0);
  auxWireBox(2.0,0.4,1.0);
  glPopMatrix();
  glFlush();
  auxSwapBuffers();
}

void myinit(void) {
  glClearColor(1.0,1.0,1.0,1.0);
}

void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(65.,(float)w/(float)h,1.0,20.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef(0.0,0.0,-5.0);
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGB);
  auxInitPosition(0,0,300,300);
  auxInitWindow("Bras robot");
  myinit();
  auxKeyFunc(AUX_LEFT,shoulderSubtract);
  auxKeyFunc(AUX_RIGHT,shoulderAdd);
  auxKeyFunc(AUX_UP,elbowAdd);
  auxKeyFunc(AUX_DOWN,elbowSubtract);
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Un carré blanc dessiné dans un programme OpenGL ultra simple

L'exécutable

Le source: CarreBlanc.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glaux.h>

void main(void) {
  auxInitDisplayMode(AUX_SINGLE|AUX_RGBA) ;
  auxInitPosition(10,10,200,200) ;
  auxInitWindow("Carré blanc") ;
  glClearColor(0.0F,0.0F,0.0F,0.0F) ;
  glClear(GL_COLOR_BUFFER_BIT) ;
  glColor3f(1.0F,1.0F,1.0F) ;
  glOrtho(-1.0,1.0,-1.0,1.0,-1.0,1.0) ;
  glBegin(GL_POLYGON) ;
  glVertex2f(-0.5F,-0.5F) ;
  glVertex2f(-0.5F,0.5F) ;
  glVertex2f(0.5F,0.5F) ;
  glVertex2f(0.5F,-0.5F) ;
  glEnd() ;
  glFlush() ;
  while ( 1 ) ;
}
   RETOUR


Un carré coloré

L'exécutable

Le source: CarreCouleur.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <stdio.h>
#include <stdlib.h>

static float rotx = 0.0f ;
static float roty = 0.0f ;
static float rotz = 0.0f ;

void CALLBACK display(void) {
  glClear(GL_COLOR_BUFFER_BIT);
  glPushMatrix();
  glRotatef(rotx,1.0f,0.0f,0.0f);
  glRotatef(roty,0.0f,1.0f,0.0f);
  glRotatef(rotz,0.0f,0.0f,1.0f);
  glBegin(GL_POLYGON) ;
  glColor3f(1.0F,0.0F,0.0F) ;
  glVertex2f(-0.5F,-0.5F) ;
  glColor3f(0.0F,1.0F,0.0F) ;
  glVertex2f(-0.5F,0.5F) ;
  glColor3f(1.0F,1.0F,1.0F) ;
  glVertex2f(0.5F,0.5F) ;
  glColor3f(0.0F,0.0F,1.0F) ;
  glVertex2f(0.5F,-0.5F) ;
  glEnd() ;
  glPopMatrix();
  glFlush();
  auxSwapBuffers();
}

void myinit (void) {
  glClearColor (0.0,0.0,0.0,0.0);
  glShadeModel(GL_SMOOTH);
}

void CALLBACK myReshape(int w, int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(-1.0,1.0,-1.0/w*h,1.0/w*h,-1.0,1.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void CALLBACK auxX(void) {
  rotx += 2 ;
}

void CALLBACK auxx(void) {
  rotx -= 2 ;
}

void CALLBACK auxY(void) {
  roty += 2 ;
}

void CALLBACK auxy(void) {
  roty -= 2 ;
}

void CALLBACK auxZ(void) {
  rotz += 2 ;
}

void CALLBACK auxz(void) {
  rotz -= 2 ;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGBA);
  auxInitPosition(0,0,300,220);
  auxInitWindow("Carré coloré") ;
  myinit();
  auxKeyFunc(AUX_X,auxX) ;
  auxKeyFunc(AUX_x,auxx) ;
  auxKeyFunc(AUX_Y,auxY) ;
  auxKeyFunc(AUX_y,auxy) ;
  auxKeyFunc(AUX_Z,auxZ) ;
  auxKeyFunc(AUX_z,auxz) ;
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Un ensemble de facettes

L'exécutable

Le source: EnsembleFacttes.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <stdio.h>
#include <stdlib.h>

#define X .525731112119133606
#define Z .850650808352039932

static float anglex = 0.0F ;
static float angley = 0.0F ;

static GLfloat vdata[12][3] = {
  {-X,0,Z},{X,0,Z},{-X,0,-Z},{X,0,-Z},
  {0,Z,X},{0,Z,-X},{0,-Z,X},{0,-Z,-X},
  {Z,X,0},{-Z,X,0},{Z,-X,0},{-Z,-X,0}} ;
static GLint tindices[20][3] = {
  {0,4,1},{0,9,4},{9,5,4},{4,5,8},{4,8,1},
  {8,10,1},{8,3,10},{5,3,8},{5,2,3},{2,7,3},
  {7,10,3},{7,6,10},{7,11,6},{9,2,5},{0,1,6},
  {6,1,10},{9,0,11},{9,11,2},{11,0,6},
  {7,2,11}};

void CALLBACK display(void) {
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  glRotatef(angley,0.0F,1.0F,0.0F) ;
  glRotatef(anglex,1.0F,0.0F,0.0F) ;
  for ( int i = 0 ; i < 20 ; i++ ) {
    glBegin(GL_TRIANGLES) ;
    glNormal3fv(&vdata[tindices[i][0]][0]);
    glVertex3fv(&vdata[tindices[i][0]][0]);
    glNormal3fv(&vdata[tindices[i][1]][0]);
    glVertex3fv(&vdata[tindices[i][1]][0]);
    glNormal3fv(&vdata[tindices[i][2]][0]);
    glVertex3fv(&vdata[tindices[i][2]][0]);
    glEnd() ; }
  glPopMatrix();
  glFlush();
  auxSwapBuffers();
}

void myinit (void) {
  glClearColor(0.6F,0.6F,0.3F,0.0F);
  glShadeModel(GL_SMOOTH);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
}

void CALLBACK myReshape(int w, int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(-1.3,1.3,-1.3/w*h,1.3/w*h,-1.3,1.3);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void CALLBACK up(void) {
  anglex++ ;
}

void CALLBACK down(void) {
  anglex-- ;
}

void CALLBACK left(void) {
  angley++ ;
}

void CALLBACK right(void) {
  angley-- ;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGBA|AUX_DEPTH);
  auxInitPosition(0,0,300,250);
  auxInitWindow("Ensemble de facettes") ;
  myinit();
  auxKeyFunc(AUX_UP,up) ;
  auxKeyFunc(AUX_DOWN,down) ;
  auxKeyFunc(AUX_LEFT,left) ;
  auxKeyFunc(AUX_RIGHT,right) ;
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Un exemple d'utilisation des listes d'affichage

L'exécutable

  • l et L pour utiliser ou non une liste d'affichage
  • Le temps d'affichage en milli-seconde est affiché dans l'invite de commande

Le source: ListeAffichage.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <sys/timeb.h>

#define PI 3.14159

static int l = 0 ;

void cercle() {
  GLint i ;
  GLfloat cosinus,sinus ;
  glBegin(GL_POLYGON) ;
  for ( i = 0 ; i < 100 ; i++ ) {
    cosinus =(float) cos(i*2*PI/100.0);
    sinus =(float) sin(i*2*PI/100.0);
    glVertex2f(cosinus,sinus) ; }
  glEnd() ;
}

void CALLBACK display(void) {
  glClear(GL_COLOR_BUFFER_BIT);
  glColor3f(0.0F,0.0F,0.0F);
  struct timeb t1 ;
  struct timeb t2 ;
  ftime(&t1);
  for ( int k = 0 ; k < 3000 ; k++ )
    for ( int j = 0 ; j < 3 ; j++ )
      for ( int i = 0 ; i < 3 ; i++ ) {
        glPushMatrix();
        glTranslatef(-3.0F+3*i,-3.0F+3*j,(float) -k) ;
        if ( l )
          cercle();
          else
          glCallList(1) ;
        glPopMatrix(); }
  ftime(&t2);
  int milli = ((int) t2.time - (int) t1.time)*1000 + (int) t2.millitm - (int) t1.millitm ;
  printf("%d millisecondes\n",milli) ;
  glFlush();
  auxSwapBuffers();
}

void cercleListe() {
  GLint i ;
  GLfloat cosinus,sinus ;
  glNewList(1,GL_COMPILE) ;
  glBegin(GL_POLYGON) ;
  for ( i = 0 ; i < 100 ; i++ ) {
    cosinus =(float) cos(i*2*PI/100.0);
    sinus =(float) sin(i*2*PI/100.0);
    glVertex2f(cosinus,sinus) ; }
  glEnd() ;
  glEndList() ;
}

void myinit(void) {
  glClearColor(1.0,1.0,1.0,1.0);
  cercleListe() ;
}

void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(85.,(float)w/(float)h,1.0,20.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef(0.0,0.0,-5.0);
}

void CALLBACK keyL(void) {
  l++;
  l %= 2;
}

void CALLBACK keyl(void) {
  l++;
  l %= 2;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGB);
  auxInitPosition(0,0,200,200);
  auxInitWindow("Liste d'affichage");
  myinit();
  auxKeyFunc(AUX_L,keyL);
  auxKeyFunc(AUX_l,keyl);
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Un nouvel exemple d'utilisation des listes d'affichage

L'exécutable

Le source: ListeAffichage2.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

GLuint listName = 1;

void myinit(void) {
  glNewList(listName,GL_COMPILE);
  glColor3f(1.0,0.0,0.0);
  glBegin(GL_TRIANGLES);
  glVertex2f(0.0,0.0);
  glVertex2f(1.0,0.0);
  glVertex2f(0.0,1.0);
  glEnd();
  glTranslatef(1.5,0.0,0.0);
  glEndList();
  glShadeModel(GL_FLAT);
}

void drawLine(void) {
  glBegin(GL_LINES);
  glVertex2f(0.0,0.5);
  glVertex2f(15.0,0.5);
  glEnd();
}

void CALLBACK display(void) {
  GLuint i;
  glClear(GL_COLOR_BUFFER_BIT);
  glColor3f(0.0,1.0,0.0);
  for ( i = 0 ; i < 10 ; i++ )
    glCallList(listName);
  drawLine();
  glFlush();
}

void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if ( w <= h )
    gluOrtho2D(0.,2.,-0.5*(float)h/(float)w,1.5*(float)h/(float)w);
    else
    gluOrtho2D(0.,2.*(float)w/(float)h,-.5,1.5);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void main(void) {
  auxInitDisplayMode(AUX_SINGLE|AUX_RGB);
  auxInitPosition(0,0,400,50);
  auxInitWindow("Liste d'affichage");
  myinit();
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Un objet en déformation

L'exécutable

Le source: Morph3D.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

static float Scale = 0.3F ;
static int tetradivisions = 23 ;
static int cubedivisions = 20 ;
static int octadivisions = 21 ;
static int dodecadivisions = 10 ;
static int icodivisions = 15 ;
static float tetraangle = 109.47122063449069174F ;
static float cubeangle = 90.000000000000000000F ;
static float octaangle = 109.47122063449069174F ;
static float dodecaangle = 63.434948822922009981F ;
static float icoangle = 41.810314895778596167F ;
static float Pi = 3.1415926535897932385F ;
static float sqrT2 = 1.4142135623730951455F ;
static float sqrT3 = 1.7320508075688771932F ;
static float sqrT5 = 2.2360679774997898051F ;
static float sqrT6 = 2.4494897427831778813F ;
static float sqrT15 = 3.8729833462074170214F ;
static float cossec36_2 = 0.8506508083520399322F ;
static float cos72 = 0.3090169943749474241F ;
static float sin72 = 0.9510565162951535721F ;
static float cos36 = 0.8090169943749474241F ;
static float sin36 = 0.5877852522924731292F ;

float VectMulx(float X1,float Y1,float Z1,float X2,float Y2,float Z2) {
    return((Y1)*(Z2)-(Z1)*(Y2)) ;
}

float VectMuly(float X1,float Y1,float Z1,float X2,float Y2,float Z2) {
    return((Z1)*(X2)-(X1)*(Z2)) ;
}

float VectMulz(float X1,float Y1,float Z1,float X2,float Y2,float Z2) {
    return((X1)*(Y2)-(Y1)*(X2)) ;
}

float sqr(float a) {
    return(a*a) ;
}

/*************************************************************************/

static int mono=0 ;
static int smooth=1 ;
static int WindH,WindW ;
static float step=0 ;
static float seno ;
static int object = 1 ;
static int edgedivisions ;
static float Magnitude ;
static float *MaterialColor[20] ;

static float front_shininess[] = { 60.0F } ;
static float front_specular[] = { 0.7F,0.7F,0.7F,1.0F } ;
static float ambient[] = { 0.0F,0.0F,0.0F,1.0F } ;
static float diffuse[] = { 1.0F,1.0F,1.0F,1.0F } ;
static float position0[] = { 1.0F,1.0F,1.0F,0.0F } ;
static float position1[] = { -1.0F,-1.0F,1.0F,0.0F } ;
static float lmodel_ambient[] = { 0.5F,0.5F,0.5F,1.0F } ;
static float lmodel_twoside[] = { 0.0F } ;

static float MaterialRed[] = { 0.7F,0.0F,0.0F,1.0F } ;
static float MaterialGreen[] = { 0.1F,0.5F,0.2F,1.0F } ;
static float MaterialBlue[] = { 0.0F,0.0F,0.7F,1.0F } ;
static float MaterialCyan[] = { 0.2F,0.5F,0.7F,1.0F } ;
static float MaterialYellow[] = { 0.7F,0.7F,0.0F,1.0F } ;
static float MaterialMagenta[] = { 0.6F,0.2F,0.5F,1.0F } ;
static float MaterialWhite[] = { 0.7F,0.7F,0.7F,1.0F } ;
static float MaterialGray[] = { 0.2F,0.2F,0.2F,1.0F } ;

void TRIANGLE(float Edge,float Amp,int Divisions,float Z) {
  float Xf,Yf,Xa,Yb,Xf2,Yf2;
  float Factor,Factor1,Factor2;
  float VertX,VertY,VertZ,NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ;
  float Ax,Ay,Bx;
  int Ri,Ti;
  float Vr=(Edge)*sqrT3/3;
  float AmpVr2=(Amp)/sqr(Vr);
  float Zf=(Edge)*(Z);
  Ax=(Edge)*(+0.5F/(Divisions));
  Ay=(Edge)*(-sqrT3/(2*Divisions));
  Bx=(Edge)*(-0.5F/(Divisions));
  for (Ri=1; Ri<=(Divisions); Ri++) {
    glBegin(GL_TRIANGLE_STRIP);
    for (Ti=0; Ti<Ri; Ti++) {
      Xf=(float)(Ri-Ti)*Ax + (float)Ti*Bx;
      Yf=Vr+(float)(Ri-Ti)*Ay + (float)Ti*Ay;
      Xa=Xf+0.001F;
      Yb=Yf+0.001F;
      Factor=1-(((Xf2=sqr(Xf))+(Yf2=sqr(Yf)))*AmpVr2);
      Factor1=1-((sqr(Xa)+Yf2)*AmpVr2);
      Factor2=1-((Xf2+sqr(Yb))*AmpVr2);
      VertX=Factor*Xf;
      VertY=Factor*Yf;
      VertZ=Factor*Zf;
      NeiAX=Factor1*Xa-VertX;
      NeiAY=Factor1*Yf-VertY;
      NeiAZ=Factor1*Zf-VertZ;
      NeiBX=Factor2*Xf-VertX;
      NeiBY=Factor2*Yb-VertY;
      NeiBZ=Factor2*Zf-VertZ;
      glNormal3f(VectMulx(NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ),VectMuly(NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ),VectMulz(NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ));
      glVertex3f(VertX,VertY,VertZ);
      Xf=(float)(Ri-Ti-1)*Ax + (float)Ti*Bx;
      Yf=Vr+(float)(Ri-Ti-1)*Ay + (float)Ti*Ay;
      Xa=Xf+0.001F;
      Yb=Yf+0.001F;
      Factor=1-(((Xf2=sqr(Xf))+(Yf2=sqr(Yf)))*AmpVr2);
      Factor1=1-((sqr(Xa)+Yf2)*AmpVr2);
      Factor2=1-((Xf2+sqr(Yb))*AmpVr2);
      VertX=Factor*Xf;
      VertY=Factor*Yf;
      VertZ=Factor*Zf;
      NeiAX=Factor1*Xa-VertX;
      NeiAY=Factor1*Yf-VertY;
      NeiAZ=Factor1*Zf-VertZ;
      NeiBX=Factor2*Xf-VertX;
      NeiBY=Factor2*Yb-VertY;
      NeiBZ=Factor2*Zf-VertZ;
      glNormal3f(VectMulx(NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ),VectMuly(NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ),VectMulz(NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ));
      glVertex3f(VertX,VertY,VertZ); }
    Xf=(float)Ri*Bx;
    Yf=Vr+(float)Ri*Ay;
    Xa=Xf+0.001F;
    Yb=Yf+0.001F;
    Factor=1-(((Xf2=sqr(Xf))+(Yf2=sqr(Yf)))*AmpVr2);
    Factor1=1-((sqr(Xa)+Yf2)*AmpVr2);
    Factor2=1-((Xf2+sqr(Yb))*AmpVr2);
    VertX=Factor*Xf;
    VertY=Factor*Yf;
    VertZ=Factor*Zf;
    NeiAX=Factor1*Xa-VertX;
    NeiAY=Factor1*Yf-VertY;
    NeiAZ=Factor1*Zf-VertZ;
    NeiBX=Factor2*Xf-VertX;
    NeiBY=Factor2*Yb-VertY;
    NeiBZ=Factor2*Zf-VertZ;
    glNormal3f(VectMulx(NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ),VectMuly(NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ),VectMulz(NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ));
    glVertex3f(VertX,VertY,VertZ);
    glEnd(); }
}

void SQUARE(int Edge,float Amp,int Divisions,float Z) {
  int Xi,Yi;
  float Xf,Yf,Y,Xf2,Yf2,Y2,Xa,Yb;
  float Factor,Factor1,Factor2;
  float VertX,VertY,VertZ,NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ;
  float Zf=(Edge)*(Z);
  float AmpVr2=(Amp)/sqr((Edge)*sqrT2/2);
  for (Yi=0; Yi<(Divisions); Yi++) {
    Yf=-((Edge)/2.0F) + ((float)Yi)/(Divisions)*(Edge);
    Yf2=sqr(Yf);
    Y=Yf+1.0F/(Divisions)*(Edge);
    Y2=sqr(Y);
    glBegin(GL_QUAD_STRIP);
    for (Xi=0; Xi<=(Divisions); Xi++) {
      Xf=-((Edge)/2.0F) + ((float)Xi)/(Divisions)*(Edge);
      Xf2=sqr(Xf);
      Xa=Xf+0.001F;
      Yb=Y+0.001F;
      Factor=1-((Xf2+Y2)*AmpVr2);
      Factor1=1-((sqr(Xa)+Y2)*AmpVr2);
      Factor2=1-((Xf2+sqr(Yb))*AmpVr2);
      VertX=Factor*Xf;
      VertY=Factor*Y;
      VertZ=Factor*Zf;
      NeiAX=Factor1*Xa-VertX;
      NeiAY=Factor1*Y-VertY;
      NeiAZ=Factor1*Zf-VertZ;
      NeiBX=Factor2*Xf-VertX;
      NeiBY=Factor2*Yb-VertY;
      NeiBZ=Factor2*Zf-VertZ;
      glNormal3f(VectMulx(NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ),VectMuly(NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ),VectMulz(NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ));
      glVertex3f(VertX,VertY,VertZ);
      Xa=Xf+0.001F;
      Yb=Yf+0.001F;
      Factor=1-((Xf2+Yf2)*AmpVr2);
      Factor1=1-((sqr(Xa)+Yf2)*AmpVr2);
      Factor2=1-((Xf2+sqr(Yb))*AmpVr2);
      VertX=Factor*Xf;
      VertY=Factor*Yf;
      VertZ=Factor*Zf;
      NeiAX=Factor1*Xa-VertX;
      NeiAY=Factor1*Yf-VertY;
      NeiAZ=Factor1*Zf-VertZ;
      NeiBX=Factor2*Xf-VertX;
      NeiBY=Factor2*Yb-VertY;
      NeiBZ=Factor2*Zf-VertZ;
      glNormal3f(VectMulx(NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ),VectMuly(NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ),VectMulz(NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ));
      glVertex3f(VertX,VertY,VertZ); }
    glEnd(); }
}

void PENTAGON(int Edge,float Amp,int Divisions,float Z) {
  int Ri,Ti,Fi;
  float Xf,Yf,Xa,Yb,Xf2,Yf2;
  float x[6] ;
  float y[6] ;
  float Factor,Factor1,Factor2;
  float VertX,VertY,VertZ,NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ;
  float Zf=(Edge)*(Z);
  float AmpVr2=(Amp)/sqr((Edge)*cossec36_2);
  for(Fi=0;Fi<6;Fi++) {
    x[Fi]=(float) (-cos(Fi*2*Pi/5 + Pi/10 )/(Divisions)*cossec36_2*(Edge));
    y[Fi]=(float) (sin(Fi*2*Pi/5 + Pi/10 )/(Divisions)*cossec36_2*(Edge)); }
    for (Ri=1; Ri<=(Divisions); Ri++) {
      for (Fi=0; Fi<5; Fi++) {
      glBegin(GL_TRIANGLE_STRIP);
      for (Ti=0; Ti<Ri; Ti++) {
        Xf=(float)(Ri-Ti)*x[Fi] + (float)Ti*x[Fi+1];
        Yf=(float)(Ri-Ti)*y[Fi] + (float)Ti*y[Fi+1];
        Xa=Xf+0.001F;
        Yb=Yf+0.001F;
        Factor=1-(((Xf2=sqr(Xf))+(Yf2=sqr(Yf)))*AmpVr2);
        Factor1=1-((sqr(Xa)+Yf2)*AmpVr2);
        Factor2=1-((Xf2+sqr(Yb))*AmpVr2);
        VertX=Factor*Xf;
        VertY=Factor*Yf;
        VertZ=Factor*Zf;
        NeiAX=Factor1*Xa-VertX;
        NeiAY=Factor1*Yf-VertY;
        NeiAZ=Factor1*Zf-VertZ;
        NeiBX=Factor2*Xf-VertX;
        NeiBY=Factor2*Yb-VertY;
        NeiBZ=Factor2*Zf-VertZ;
        glNormal3f(VectMulx(NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ),VectMuly(NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ),VectMulz(NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ));
        glVertex3f(VertX,VertY,VertZ);
        Xf=(float)(Ri-Ti-1)*x[Fi] + (float)Ti*x[Fi+1];
        Yf=(float)(Ri-Ti-1)*y[Fi] + (float)Ti*y[Fi+1];
        Xa=Xf+0.001F;
        Yb=Yf+0.001F;
        Factor=1-(((Xf2=sqr(Xf))+(Yf2=sqr(Yf)))*AmpVr2);
        Factor1=1-((sqr(Xa)+Yf2)*AmpVr2);
        Factor2=1-((Xf2+sqr(Yb))*AmpVr2);
        VertX=Factor*Xf;
        VertY=Factor*Yf;
        VertZ=Factor*Zf;
        NeiAX=Factor1*Xa-VertX;
        NeiAY=Factor1*Yf-VertY;
        NeiAZ=Factor1*Zf-VertZ;
        NeiBX=Factor2*Xf-VertX;
        NeiBY=Factor2*Yb-VertY;
        NeiBZ=Factor2*Zf-VertZ;
        glNormal3f(VectMulx(NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ),VectMuly(NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ),VectMulz(NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ));
        glVertex3f(VertX,VertY,VertZ); }
      Xf=(float)Ri*x[Fi+1];
      Yf=(float)Ri*y[Fi+1];
      Xa=Xf+0.001F;
      Yb=Yf+0.001F;
      Factor=1-(((Xf2=sqr(Xf))+(Yf2=sqr(Yf)))*AmpVr2);
      Factor1=1-((sqr(Xa)+Yf2)*AmpVr2);
      Factor2=1-((Xf2+sqr(Yb))*AmpVr2);
      VertX=Factor*Xf;
      VertY=Factor*Yf;
      VertZ=Factor*Zf;
      NeiAX=Factor1*Xa-VertX;
      NeiAY=Factor1*Yf-VertY;
      NeiAZ=Factor1*Zf-VertZ;
      NeiBX=Factor2*Xf-VertX;
      NeiBY=Factor2*Yb-VertY;
      NeiBZ=Factor2*Zf-VertZ;
      glNormal3f(VectMulx(NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ),VectMuly(NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ),VectMulz(NeiAX,NeiAY,NeiAZ,NeiBX,NeiBY,NeiBZ));
      glVertex3f(VertX,VertY,VertZ);
      glEnd(); } }
}

void draw_tetra() {
  int list;
  list = glGenLists(1);
  glNewList(list,GL_COMPILE);
  TRIANGLE(2,seno,edgedivisions,0.5F/sqrT6);
  glEndList();
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[0]);
  glCallList(list);
  glPushMatrix();
  glRotatef(180,0,0,1);
  glRotatef(-tetraangle,1,0,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[1]);
  glCallList(list);
  glPopMatrix();
  glPushMatrix();
  glRotatef(180,0,1,0);
  glRotatef(-180+tetraangle,0.5F,sqrT3/2,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[2]);
  glCallList(list);
  glPopMatrix();
  glRotatef(180,0,1,0);
  glRotatef(-180+tetraangle,0.5F,-sqrT3/2,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[3]);
  glCallList(list);
  glDeleteLists(list,1);
}

void draw_cube() {
  int list;
  list = glGenLists(1);
  glNewList(list,GL_COMPILE);
  SQUARE(2,seno,edgedivisions,0.5F) ;
  glEndList();
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[0]);
  glCallList(list);
  glRotatef(cubeangle,1,0,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[1]);
  glCallList(list);
  glRotatef(cubeangle,1,0,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[2]);
  glCallList(list);
  glRotatef(cubeangle,1,0,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[3]);
  glCallList(list);
  glRotatef(cubeangle,0,1,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[4]);
  glCallList(list);
  glRotatef(2*cubeangle,0,1,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[5]);
  glCallList(list);
  glDeleteLists(list,1);
}

void draw_octa() {
  int list;
  list = glGenLists(1);
  glNewList(list,GL_COMPILE);
  TRIANGLE(2,seno,edgedivisions,1/sqrT6);
  glEndList();
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[0]);
  glCallList(list);
  glPushMatrix();
  glRotatef(180,0,0,1);
  glRotatef(-180+octaangle,1,0,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[1]);
  glCallList(list);
  glPopMatrix();
  glPushMatrix();
  glRotatef(180,0,1,0);
  glRotatef(-octaangle,0.5F,sqrT3/2,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[2]);
  glCallList(list);
  glPopMatrix();
  glPushMatrix();
  glRotatef(180,0,1,0);
  glRotatef(-octaangle,0.5F,-sqrT3/2,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[3]);
  glCallList(list);
  glPopMatrix();
  glRotatef(180,1,0,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[4]);
  glCallList(list);
  glPushMatrix();
  glRotatef(180,0,0,1);
  glRotatef(-180+octaangle,1,0,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[5]);
  glCallList(list);
  glPopMatrix();
  glPushMatrix();
  glRotatef(180,0,1,0);
  glRotatef(-octaangle,0.5F,sqrT3/2,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[6]);
  glCallList(list);
  glPopMatrix();
  glRotatef(180,0,1,0);
  glRotatef(-octaangle,0.5F,-sqrT3/2,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[7]);
  glCallList(list);
  glDeleteLists(list,1);
}

void draw_dodeca() {
  int list;
  float TAU = ((sqrT5+1)/2) ;
  list = glGenLists(1);
  glNewList(list,GL_COMPILE);
  PENTAGON(1,seno,edgedivisions,(float) (sqr(TAU) * sqrt((TAU+2)/5) / 2));
  glEndList();
  glPushMatrix();
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[0]);
  glCallList(list);
  glRotatef(180,0,0,1);
  glPushMatrix();
  glRotatef(-dodecaangle,1,0,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[1]);
  glCallList(list);
  glPopMatrix();
  glPushMatrix();
  glRotatef(-dodecaangle,cos72,sin72,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[2]);
  glCallList(list);
  glPopMatrix();
  glPushMatrix();
  glRotatef(-dodecaangle,cos72,-sin72,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[3]);
  glCallList(list);
  glPopMatrix();
  glPushMatrix();
  glRotatef(dodecaangle,cos36,-sin36,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[4]);
  glCallList(list);
  glPopMatrix();
  glRotatef(dodecaangle,cos36,sin36,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[5]);
  glCallList(list);
  glPopMatrix();
  glRotatef(180,1,0,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[6]);
  glCallList(list);
  glRotatef(180,0,0,1);
  glPushMatrix();
  glRotatef(-dodecaangle,1,0,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[7]);
  glCallList(list);
  glPopMatrix();
  glPushMatrix();
  glRotatef(-dodecaangle,cos72,sin72,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[8]);
  glCallList(list);
  glPopMatrix();
  glPushMatrix();
  glRotatef(-dodecaangle,cos72,-sin72,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[9]);
  glCallList(list);
  glPopMatrix();
  glPushMatrix();
  glRotatef(dodecaangle,cos36,-sin36,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[10]);
  glCallList(list);
  glPopMatrix();
  glRotatef(dodecaangle,cos36,sin36,0);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[11]);
  glCallList(list);
  glDeleteLists(list,1);
}

void draw_ico() {
  int list;
  list = glGenLists(1);
  glNewList(list,GL_COMPILE);
  TRIANGLE(1.5F,seno,edgedivisions,(3*sqrT3+sqrT15)/12.0F);
  glEndList();
  glPushMatrix();
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[0]);
  glCallList(list);
  glPushMatrix();
  glRotatef(180,0.0F,0.0F,1.0F);
  glRotatef(-icoangle,1.0F,0.0F,0.0F);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[1]);
  glCallList(list);
  glPushMatrix();
  glRotatef(180.0F,0.0F,1.0F,0.0F);
  glRotatef(-180+icoangle,0.5F,sqrT3/2,0.0F);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[2]);
  glCallList(list);
  glPopMatrix();
  glRotatef(180.F,0.0F,1.0F,0.0F);
  glRotatef(-180+icoangle,0.5F,-sqrT3/2,0.0F);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[3]);
  glCallList(list);
  glPopMatrix();
  glPushMatrix();
  glRotatef(180.0F,0.0F,1.0F,0.0F);
  glRotatef(-180+icoangle,0.5F,sqrT3/2,0.0F);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[4]);
  glCallList(list);
  glPushMatrix();
  glRotatef(180.0F,0.0F,1.0F,0.0F);
  glRotatef(-180+icoangle,0.5F,sqrT3/2,0.0F);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[5]);
  glCallList(list);
  glPopMatrix();
  glRotatef(180.0F,0.0F,0.0F,1.0F);
  glRotatef(-icoangle,1.0F,0.0F,0.0F);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[6]);
  glCallList(list);
  glPopMatrix();
  glRotatef(180.0F,0.0F,1.0F,0.0F);
  glRotatef(-180+icoangle,0.5F,-sqrT3/2,0.0F);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[7]);
  glCallList(list);
  glPushMatrix();
  glRotatef(180.0F,0.0F,1.0F,0.0F);
  glRotatef(-180+icoangle,0.5F,-sqrT3/2,0.0F);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[8]);
  glCallList(list);
  glPopMatrix();
  glRotatef(180.0F,0.0F,0.0F,1.0F);
  glRotatef(-icoangle,1.0F,0.0F,0.0F);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[9]);
  glCallList(list);
  glPopMatrix();
  glRotatef(180.0F,1.0F,0.0F,0.0F);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[10]);
  glCallList(list);
  glPushMatrix();
  glRotatef(180.0F,0.0F,0.0F,1.0F);
  glRotatef(-icoangle,1.0F,0.0F,0.0F);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[11]);
  glCallList(list);
  glPushMatrix();
  glRotatef(180.0F,0.0F,1.0F,0.0F);
  glRotatef(-180+icoangle,0.5F,sqrT3/2,0.0F);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[12]);
  glCallList(list);
  glPopMatrix();
  glRotatef(180.0F,0.0F,1.0F,0.0F);
  glRotatef(-180+icoangle,0.5F,-sqrT3/2,0.0F);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[13]);
  glCallList(list);
  glPopMatrix();
  glPushMatrix();
  glRotatef(180.0F,0.0F,1.0F,0.0F);
  glRotatef(-180+icoangle,0.5F,sqrT3/2,0.0F);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[14]);
  glCallList(list);
  glPushMatrix();
  glRotatef(180.0F,0.0F,1.0F,0.0F);
  glRotatef(-180+icoangle,0.5F,sqrT3/2,0.0F);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[15]);
  glCallList(list);
  glPopMatrix();
  glRotatef(180.0F,0.0F,0.0F,1.0F);
  glRotatef(-icoangle,1.0F,0.0F,0.0F);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[16]);
  glCallList(list);
  glPopMatrix();
  glRotatef(180.0F,0.0F,1.0F,0.0F);
  glRotatef(-180+icoangle,0.5F,-sqrT3/2,0.0F);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[17]);
  glCallList(list);
  glPushMatrix();
  glRotatef(180.0F,0.0F,1.0F,0.0F);
  glRotatef(-180+icoangle,0.5F,-sqrT3/2,0.0F);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[18]);
  glCallList(list);
  glPopMatrix();
  glRotatef(180.0F,0.0F,0.0F,1.0F);
  glRotatef(-icoangle,1.0F,0.0F,0.0F);
  glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,MaterialColor[19]);
  glCallList(list);
  glDeleteLists(list,1);
}

void CALLBACK display() {
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  glTranslatef(0.0F,0.0F,-10.0F);
  glScalef(Scale*WindH/WindW,Scale,Scale);
  glTranslatef((float) (2.5*WindW/WindH*sin(step*1.11)),(float) (2.5*cos(step*1.25*1.11)),0.0F);
  glRotatef(step*100,1.0F,0.0F,0.0F);
  glRotatef(step*95,0.0F,1.0F,0.0F);
  glRotatef(step*90,0.0F,0.0F,1.0F);
  seno=(float) ((sin(step)+1.0/3.0)*(4.0/5.0)*Magnitude);
  switch(object) {
    case 1 : draw_tetra() ;
             break ;
    case 2 : draw_cube() ;
             break ;
    case 3 : draw_octa() ;
             break ;
    case 4 : draw_dodeca() ;
             break ;
    case 5 : draw_ico() ; }
  glPopMatrix();
  glFlush();
  auxSwapBuffers();
  step+=0.05F;
}

void CALLBACK idle() {
   display();
}

void CALLBACK reshape(int width,int height ) {
  glViewport(0,0,WindW=width,WindH=height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glFrustum(-1.0,1.0,-1.0,1.0,5.0,15.0);
  glMatrixMode(GL_MODELVIEW);
}

void pinit() {
  switch(object) {
    case 1: MaterialColor[0]=MaterialRed;
            MaterialColor[1]=MaterialGreen;
            MaterialColor[2]=MaterialBlue;
            MaterialColor[3]=MaterialWhite;
            edgedivisions=tetradivisions;
            Magnitude=2.5F;
            break;
    case 2: MaterialColor[0]=MaterialRed;
            MaterialColor[1]=MaterialGreen;
            MaterialColor[2]=MaterialCyan;
            MaterialColor[3]=MaterialMagenta;
            MaterialColor[4]=MaterialYellow;
            MaterialColor[5]=MaterialBlue;
            edgedivisions=cubedivisions;
            Magnitude=2.0F;
            break;
    case 3: MaterialColor[0]=MaterialRed;
            MaterialColor[1]=MaterialGreen;
            MaterialColor[2]=MaterialBlue;
            MaterialColor[3]=MaterialWhite;
            MaterialColor[4]=MaterialCyan;
            MaterialColor[5]=MaterialMagenta;
            MaterialColor[6]=MaterialGray;
            MaterialColor[7]=MaterialYellow;
            edgedivisions=octadivisions;
            Magnitude=2.5F;
            break;
    case 4: MaterialColor[0]=MaterialRed;
            MaterialColor[1]=MaterialGreen;
            MaterialColor[2]=MaterialCyan;
            MaterialColor[3]=MaterialBlue;
            MaterialColor[4]=MaterialMagenta;
            MaterialColor[5]=MaterialYellow;
            MaterialColor[6]=MaterialGreen;
            MaterialColor[7]=MaterialCyan;
            MaterialColor[8]=MaterialRed;
            MaterialColor[9]=MaterialMagenta;
            MaterialColor[10]=MaterialBlue;
            MaterialColor[11]=MaterialYellow;
            edgedivisions=dodecadivisions;
            Magnitude=2.0F;
            break;
    case 5: MaterialColor[0]=MaterialRed;
            MaterialColor[1]=MaterialGreen;
            MaterialColor[2]=MaterialBlue;
            MaterialColor[3]=MaterialCyan;
            MaterialColor[4]=MaterialYellow;
            MaterialColor[5]=MaterialMagenta;
            MaterialColor[6]=MaterialRed;
            MaterialColor[7]=MaterialGreen;
            MaterialColor[8]=MaterialBlue;
            MaterialColor[9]=MaterialWhite;
            MaterialColor[10]=MaterialCyan;
            MaterialColor[11]=MaterialYellow;
            MaterialColor[12]=MaterialMagenta;
            MaterialColor[13]=MaterialRed;
            MaterialColor[14]=MaterialGreen;
            MaterialColor[15]=MaterialBlue;
            MaterialColor[16]=MaterialCyan;
            MaterialColor[17]=MaterialYellow;
            MaterialColor[18]=MaterialMagenta;
            MaterialColor[19]=MaterialGray;
            edgedivisions=icodivisions;
            Magnitude=2.5F;
            break; }
  if (mono != 0) {
    int loop;
    for (loop=0; loop<20; loop++) MaterialColor[loop]=MaterialGray; }
  if (smooth != 0) {
    glShadeModel(GL_SMOOTH); }
    else {
    glShadeModel(GL_FLAT); }
}

void CALLBACK auxKey1() {
  object = 1 ;
  pinit() ;
}

void CALLBACK auxKey2() {
  object = 2 ;
  pinit() ;
}

void CALLBACK auxKey3() {
  object = 3 ;
  pinit() ;
}

void CALLBACK auxKey4() {
  object = 4 ;
  pinit() ;
}

void CALLBACK auxKey5() {
  object = 5 ;
  pinit() ;
}

void CALLBACK auxKeySpace() {
  mono ^= 1 ;
  pinit() ;
}

void CALLBACK auxKeyReturn() {
  smooth ^= 1 ;
  pinit() ;
}

void initialise() {
  object=1;
  glClearDepth(1.0);
  glClearColor(0.0F,0.0F,0.0F,1.0F);
  glColor3f(1.0F,1.0F,1.0F);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glFlush();
  glLightfv(GL_LIGHT0,GL_AMBIENT,ambient);
  glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuse);
  glLightfv(GL_LIGHT0,GL_POSITION,position0);
  glLightfv(GL_LIGHT1,GL_AMBIENT,ambient);
  glLightfv(GL_LIGHT1,GL_DIFFUSE,diffuse);
  glLightfv(GL_LIGHT1,GL_POSITION,position1);
  glLightModelfv(GL_LIGHT_MODEL_AMBIENT,lmodel_ambient);
  glLightModelfv(GL_LIGHT_MODEL_TWO_SIDE,lmodel_twoside);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glEnable(GL_LIGHT1);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_NORMALIZE);
  glMaterialfv(GL_FRONT_AND_BACK,GL_SHININESS,front_shininess);
  glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,front_specular);
  glHint(GL_FOG_HINT,GL_FASTEST);
  glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_FASTEST);
  glHint(GL_POLYGON_SMOOTH_HINT,GL_FASTEST);
  pinit();
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGBA|AUX_DEPTH);
  auxInitPosition (0,0,300,300);
  auxInitWindow("Morph3D") ;
  initialise();
  auxReshapeFunc(reshape);
  auxKeyFunc(AUX_1,auxKey1) ;
  auxKeyFunc(AUX_2,auxKey2) ;
  auxKeyFunc(AUX_3,auxKey3) ;
  auxKeyFunc(AUX_4,auxKey4) ;
  auxKeyFunc(AUX_5,auxKey5) ;
  auxKeyFunc(AUX_SPACE,auxKeySpace) ;
  auxKeyFunc(AUX_RETURN,auxKeyReturn) ;
  auxIdleFunc(idle);
  auxMainLoop(display);
}
   RETOUR


Un oiseau de guerre Klingon

L'exécutable

Le fichier de définition

Le source (OpengGL 1.1 nécessaire)

RETOUR


Un oiseau de guerre Romulien

L'exécutable

Le fichier de définition

Le source (OpengGL 1.1 nécessaire)

   RETOUR


Un premier exemple de plaçage de texture : les échiquiers

L'exécutable

Le source: Texture1.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#define LI 64
#define LH 64
GLubyte image[LI][LH][3];

void makeImage(void) {
  int i,j,c;
  for( i = 0 ; i < LI ; i++ ) {
    for( j = 0 ; j < LH ; j++ ) {
      c =(((i&0x8)==0)^((j&0x8)==0))*255;
      image[i][j][0] =(GLubyte) c;
      image[i][j][1] =(GLubyte) c;
      image[i][j][2] =(GLubyte) c; } }
}

void myinit(void) {
  glClearColor(0.0,0.0,0.0,0.0);
  glEnable(GL_DEPTH_TEST);
  glDepthFunc(GL_LESS);
  makeImage();
  glPixelStorei(GL_UNPACK_ALIGNMENT,1);
  glTexImage2D(GL_TEXTURE_2D,0,3,LI,LH,0,GL_RGB,GL_UNSIGNED_BYTE,&image[0][0][0]);
  glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
  glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);
  glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
  glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
  glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);
  glEnable(GL_TEXTURE_2D);
  glShadeModel(GL_FLAT);
}

void CALLBACK display(void) {
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glBegin(GL_QUADS);
  glTexCoord2f(0.0F,0.0F);
  glVertex3f(-2.0F,-1.0F,0.0F);
  glTexCoord2f(0.0F,1.0F);
  glVertex3f(-2.0F,1.0F,0.0F);
  glTexCoord2f(1.0F,1.0F);
  glVertex3f(0.0F,1.0F,0.0F);
  glTexCoord2f(1.0F,0.0F);
  glVertex3f(0.0F,-1.0F,0.0F);
  glTexCoord2f(0.0F,0.0F);
  glVertex3f(1.0F,-1.0F,0.0F);
  glTexCoord2f(0.0F,1.0F);
  glVertex3f(1.0F,1.0F,0.0F);
  glTexCoord2f(1.0F,1.0F);
  glVertex3f(2.41421F,1.0F,-1.41421F);
  glTexCoord2f(1.0F,0.0F);
  glVertex3f(2.41421F,-1.0F,-1.41421F);
  glEnd();
  glFlush();
}

void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(43.0,1.0*(float)w/(float)h,1.0,30.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef(0.0F,0.0F,-3.6F);
}

void main(void) {
  auxInitDisplayMode(AUX_SINGLE|AUX_RGB|AUX_DEPTH);
  auxInitPosition(0,0,300,200);
  auxInitWindow("Les échiquiers");
  myinit();
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Un Tie Bomber

L'exécutable

Le fichier de définition

Le source (OpengGL 1.1 nécessaire)

   RETOUR


Un Tie Raptor

L'exécutable

Le fichier de définition

Le source (OpengGL 1.1 nécessaire)

   RETOUR


Une boule bondissante

L'exécutable

Le source: Bounce.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define COS(X) cos((X)*3.14159/180.0)
#define SIN(X) sin((X)*3.14159/180.0)

GLuint Ball;
GLenum Mode;
GLfloat Zrot = 0.0F,Zstep = 6.0F;
GLfloat Xpos = 0.0F,Ypos = 1.0F;
GLfloat Xvel = 0.2F,Yvel = 0.0F;
GLfloat Xmin=-4.0F,Xmax=4.0F;
GLfloat Ymin=-3.8F,Ymax=4.0F;
GLfloat G = -0.1F;

GLuint make_ball(void) {
  GLuint list;
  GLfloat a,b;
  GLfloat da = 18.0,db = 18.0;
  GLfloat radius = 1.0;
  GLuint color;
  GLfloat x,y,z;
  list = glGenLists(1);
  glNewList(list,GL_COMPILE);
  color = 0;
  for(a=-90.0;a+da<=90.0;a+=da) {
    glBegin(GL_QUAD_STRIP);
    for(b=0.0;b<=360.0;b+=db) {
      if(color) {
        GLfloat red[] = {1.0F,0.0F,0.0F,1.0F};
        glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,red) ;}
        else {
        GLfloat white[] = {1.0F,1.0F,1.0F,1.0F};
        glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,white); }
        x =(float)(COS(b) * COS(a));
        y =(float)(SIN(b) * COS(a));
        z =(float) SIN(a);
        glVertex3f(x,y,z);
        glNormal3f(x,y,z);
        x =(float)(radius * COS(b) * COS(a+da));
        y =(float)(radius * SIN(b) * COS(a+da));
        z =(float)(radius * SIN(a+da));
        glVertex3f(x,y,z);
        glNormal3f(x,y,z);
        color = 1-color; }
    glEnd(); }
    glEndList();
  return list;
}

void CALLBACK reshape(int width,int height){
  glViewport(0,0,(GLint)width,(GLint)height);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(-6.0,6.0,-height*6.0/width,height*6.0/width,-6.0,6.0);
  glMatrixMode(GL_MODELVIEW);
}

void CALLBACK draw(void){
  GLint i,j;
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  GLfloat cyan[] = {0.0F,1.0F,1.0F,1.0F};
  glMaterialfv(GL_FRONT,GL_DIFFUSE,cyan) ;
  glBegin(GL_LINES);
  for(i=-5;i<=5;i++) {
    glVertex2i(i,-5);
    glVertex2i(i,5);}
  for(i=-5;i<=5;i++) {
    glVertex2i(-5,i);
    glVertex2i(5,i);}
  for(i=-5;i<=5;i++) {
    glVertex2i(i,-5);
    glVertex2f(i*1.15,-5.9);}
  glVertex2f(-5.3,-5.35);
  glVertex2f(5.3,-5.35);
  glVertex2f(-5.75,-5.9);
  glVertex2f(5.75,-5.9);
  glEnd();
  glPushMatrix();
  glTranslatef(Xpos,Ypos,0.0);
  glScalef(2.0,2.0,2.0);
  glRotatef(8.0,0.0,0.0,1.0);
  glRotatef(90.0,1.0,0.0,0.0);
  glRotatef(Zrot,0.0,0.0,1.0);
  glCallList(Ball);
  glPopMatrix();
  glFlush();
  auxSwapBuffers();
}

static void CALLBACK idle(void){
  static float vel0 = -100.0;
  Zrot += Zstep;
  Xpos += Xvel;
  if(Xpos>=Xmax) {
    Xpos = Xmax;
    Xvel = -Xvel;
    Zstep = -Zstep; }
  if(Xpos<=Xmin) {
    Xpos = Xmin;
    Xvel = -Xvel;
    Zstep = -Zstep; }
  Ypos += Yvel;
  Yvel += G;
  if(Ypos<Ymin) {
    Ypos = Ymin;
    if(vel0==-100.0)
      vel0 = fabs(Yvel);
    Yvel = vel0; }
  draw();
}

void init() {
  Ball = make_ball();
  glEnable(GL_LIGHTING) ;
  glEnable(GL_LIGHT0) ;
  glEnable(GL_DEPTH_TEST) ;
  glEnable(GL_NORMALIZE) ;
  glDepthFunc(GL_DEPTH) ;
  glShadeModel(GL_FLAT);
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGBA|AUX_DEPTH);
  auxInitPosition(0,0,250,250);
  auxInitWindow("Une balle rebondissante") ;
  init();
  auxIdleFunc(idle);
  auxReshapeFunc(reshape);
  auxMainLoop(draw);
}
   RETOUR


Une courbe de Bézier

L'exécutable

Le source: CourbeBezier.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

GLfloat points[4][3] = {
  {-4.0,-4.0,0.0},{-2.0,4.0,0.0},
  { 2.0,-4.0,0.0},{ 4.0,4.0,0.0}};

void CALLBACK display(void) {
  int i;
  glClear(GL_COLOR_BUFFER_BIT);
  glColor3f(1.0,1.0,1.0);
  glBegin(GL_LINE_STRIP);
  for( i = 0 ; i <= 30 ; i++ )
    glEvalCoord1f((GLfloat) i/30.0F);
  glEnd();
  glPointSize(5.0);
  glColor3f(1.0,1.0,0.0);
  glBegin(GL_POINTS);
  for( i = 0 ; i < 4 ; i++ )
    glVertex3fv(&points[i][0]);
  glEnd();
  glFlush();
}

void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if( w <= h )
    glOrtho(-5.,5.,-5.*(float)h/(float)w,5.*(float)h/(float)w,-5.,5.);
    else
    glOrtho(-5.*(float)w/(float)h,5.*(float)w/(float)h,-5.,5.,-5.,5.);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void myinit(void) {
  glClearColor(0.0,0.0,0.0,1.0);
  glMap1f(GL_MAP1_VERTEX_3,0.0,1.0,3,4,&points[0][0]);
  glEnable(GL_MAP1_VERTEX_3);
  glShadeModel(GL_FLAT);
}

void main(void) {
  auxInitDisplayMode(AUX_SINGLE|AUX_RGB);
  auxInitPosition(0,0,250,250);
  auxInitWindow("Courbe de Bezier");
  myinit();
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Une maillage de Bézier

L'exécutable

Le source: MaillageBezier.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

GLfloat pts[4][4][3] = {
  { {-1.5,-1.5,4.0},{-0.5,-1.5,2.0},
    {0.5,-1.5,-1.0},{1.5,-1.5,2.0} },
  { {-1.5,-0.5,1.0},{-0.5,-0.5,3.0},
    {0.5,-0.5,0.0},{1.5,-0.5,-1.0} },
  { {-1.5,0.5,4.0},{-0.5,0.5,0.0},
    {0.5,0.5,3.0},{1.5,0.5,4.0} },
  { {-1.5,1.5,-2.0},{-0.5,1.5,-2.0},
    {0.5,1.5,0.0},{1.5,1.5,-1.0} } };

void CALLBACK display(void) {
  int i,j;
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glColor3f(1.0F,1.0F,1.0F);
  glPushMatrix();
  glRotatef(85.0F,1.0F,1.0F,1.0F);
  for( j = 0 ; j <= 8 ; j++ ) {
    glBegin(GL_LINE_STRIP);
    for( i = 0 ; i <= 30 ; i++ )
      glEvalCoord2f((float)i/30.0F,(float)j/8.0F);
    glEnd();
    glBegin(GL_LINE_STRIP);
    for(i = 0; i <= 30; i++)
      glEvalCoord2f((float)j/8.0F,(float)i/30.0F);
    glEnd(); }
  glPopMatrix();
  glFlush();
}

void myinit(void) {
  glClearColor(0.0,0.0,0.0,1.0);
  glMap2f(GL_MAP2_VERTEX_3,0,1,3,4,0,1,12,4,&pts[0][0][0]);
  glEnable(GL_MAP2_VERTEX_3);
  glMapGrid2f(20,0.0,1.0,20,0.0,1.0);
  glEnable(GL_DEPTH_TEST);
  glShadeModel(GL_FLAT);
}

void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if ( w <= h )
    glOrtho(-3.2,3.2,-3.2*(float)h/(float)w,3.2*(float)h/(float)w,-4.0,4.0);
    else
    glOrtho(-3.2*(float)w/(float)h,3.2*(float)w/(float)h,-3.2,3.2,-4.0,4.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void main(void) {
  auxInitDisplayMode(AUX_SINGLE|AUX_RGB|AUX_DEPTH);
  auxInitPosition(0,0,300,230);
  auxInitWindow("Maillage de Bézier");
  myinit();
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Une NURBS

L'exécutable

Le source: Nurbs.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <math.h>

#define iw 64
#define ih 64

static float anglex = 0.0F ;
static float angley = 0.0F ;

GLfloat ctlpoints[4][4][3];
GLUnurbsObj *theNurb;

void CALLBACK display(void) {
  GLfloat knots[8] = {0.0,0.0,0.0,0.0,1.0,1.0,1.0,1.0};
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  glTranslatef(0.0F,1.0F,0.0F) ;
  glRotatef(anglex,1.0F,0.0F,0.0F) ;
  glRotatef(angley,0.0F,1.0F,0.0F) ;
  glRotatef(330.0,1.,0.,0.);
  glScalef(0.5,0.5,0.5);
  gluBeginSurface(theNurb);
  gluNurbsSurface(theNurb,8,knots,8,knots,4*3,3,&ctlpoints[0][0][0],4,4,GL_MAP2_VERTEX_3);
  gluEndSurface(theNurb);
  glPopMatrix();
  glFlush();
  auxSwapBuffers();
}

void init_surface(void) {
  int u,v;
  for( u = 0 ; u < 4 ; u++ ) {
    for( v = 0 ; v < 4 ; v++ ) {
      ctlpoints[u][v][0] = 3.0F*(u-1.5F);
      ctlpoints[u][v][1] = 2.0F*(v-1.5F);
      if ((u==1 || u==2)&&(v==1 || v==2))
        ctlpoints[u][v][2] = 3.0;
        else
        ctlpoints[u][v][2] = -3.0; } }
  }

void myinit(void) {
  GLfloat diffuse[] = { 0.7F,0.7F,0.7F,1.0F };
  GLfloat specular[] = { 1.0F,1.0F,1.0F,1.0F };
  GLfloat shininess[] = { 100.0F };
  glClearColor(0.9,0.8,0.6,1.0);
  glMaterialfv(GL_FRONT,GL_DIFFUSE,diffuse);
  glMaterialfv(GL_FRONT,GL_SPECULAR,specular);
  glMaterialfv(GL_FRONT,GL_SHININESS,shininess);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glDepthFunc(GL_LESS);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_AUTO_NORMAL);
  glEnable(GL_NORMALIZE);
  init_surface();
  theNurb = gluNewNurbsRenderer();
  gluNurbsProperty(theNurb,GLU_SAMPLING_TOLERANCE,25.0);
  gluNurbsProperty(theNurb,GLU_DISPLAY_MODE,GLU_FILL);
}

void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(45.0,(float)w/(float)h,3.0,8.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef(0.0,0.0,-5.0);
}

void CALLBACK up(void) {
  anglex-- ;
}

void CALLBACK down(void) {
  anglex++ ;
}

void CALLBACK left(void) {
  angley-- ;
}

void CALLBACK right(void) {
  angley++ ;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGB|AUX_DEPTH);
  auxInitPosition(0,0,300,230);
  auxInitWindow("NURB");
  myinit();
  auxKeyFunc(AUX_UP,up) ;
  auxKeyFunc(AUX_DOWN,down) ;
  auxKeyFunc(AUX_LEFT,left) ;
  auxKeyFunc(AUX_RIGHT,right) ;
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Une police de caractères bitmap

L'exécutable

Un module font.cpp

#include "windows.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

GLubyte rasters[][13] = {
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x18},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x36,0x36,0x36,0x36},
{0x00,0x00,0x00,0x66,0x66,0xff,0x66,0x66,0xff,0x66,0x66,0x00,0x00},
{0x00,0x00,0x18,0x7e,0xff,0x1b,0x1f,0x7e,0xf8,0xd8,0xff,0x7e,0x18},
{0x00,0x00,0x0e,0x1b,0xdb,0x6e,0x30,0x18,0x0c,0x76,0xdb,0xd8,0x70},
{0x00,0x00,0x7f,0xc6,0xcf,0xd8,0x70,0x70,0xd8,0xcc,0xcc,0x6c,0x38},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x1c,0x0c,0x0e},
{0x00,0x00,0x0c,0x18,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x18,0x0c},
{0x00,0x00,0x30,0x18,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x18,0x30},
{0x00,0x00,0x00,0x00,0x99,0x5a,0x3c,0xff,0x3c,0x5a,0x99,0x00,0x00},
{0x00,0x00,0x00,0x18,0x18,0x18,0xff,0xff,0x18,0x18,0x18,0x00,0x00},
{0x00,0x00,0x30,0x18,0x1c,0x1c,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x38,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x60,0x60,0x30,0x30,0x18,0x18,0x0c,0x0c,0x06,0x06,0x03,0x03},
{0x00,0x00,0x3c,0x66,0xc3,0xe3,0xf3,0xdb,0xcf,0xc7,0xc3,0x66,0x3c},
{0x00,0x00,0x7e,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x78,0x38,0x18},
{0x00,0x00,0xff,0xc0,0xc0,0x60,0x30,0x18,0x0c,0x06,0x03,0xe7,0x7e},
{0x00,0x00,0x7e,0xe7,0x03,0x03,0x07,0x7e,0x07,0x03,0x03,0xe7,0x7e},
{0x00,0x00,0x0c,0x0c,0x0c,0x0c,0x0c,0xff,0xcc,0x6c,0x3c,0x1c,0x0c},
{0x00,0x00,0x7e,0xe7,0x03,0x03,0x07,0xfe,0xc0,0xc0,0xc0,0xc0,0xff},
{0x00,0x00,0x7e,0xe7,0xc3,0xc3,0xc7,0xfe,0xc0,0xc0,0xc0,0xe7,0x7e},
{0x00,0x00,0x30,0x30,0x30,0x30,0x18,0x0c,0x06,0x03,0x03,0x03,0xff},
{0x00,0x00,0x7e,0xe7,0xc3,0xc3,0xe7,0x7e,0xe7,0xc3,0xc3,0xe7,0x7e},
{0x00,0x00,0x7e,0xe7,0x03,0x03,0x03,0x7f,0xe7,0xc3,0xc3,0xe7,0x7e},
{0x00,0x00,0x00,0x38,0x38,0x00,0x00,0x38,0x38,0x00,0x00,0x00,0x00},
{0x00,0x00,0x30,0x18,0x1c,0x1c,0x00,0x00,0x1c,0x1c,0x00,0x00,0x00},
{0x00,0x00,0x06,0x0c,0x18,0x30,0x60,0xc0,0x60,0x30,0x18,0x0c,0x06},
{0x00,0x00,0x00,0x00,0xff,0xff,0x00,0xff,0xff,0x00,0x00,0x00,0x00},
{0x00,0x00,0x60,0x30,0x18,0x0c,0x06,0x03,0x06,0x0c,0x18,0x30,0x60},
{0x00,0x00,0x18,0x00,0x00,0x18,0x18,0x0c,0x06,0x03,0xc3,0xc3,0x7e},
{0x00,0x00,0x3f,0x60,0xcf,0xdb,0xd3,0xdd,0xc3,0x7e,0x00,0x00,0x00},
{0x00,0x00,0xc3,0xc3,0xc3,0xc3,0xff,0xc3,0xc3,0xc3,0x66,0x3c,0x18},
{0x00,0x00,0xfe,0xc7,0xc3,0xc3,0xc7,0xfe,0xc7,0xc3,0xc3,0xc7,0xfe},
{0x00,0x00,0x7e,0xe7,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xe7,0x7e},
{0x00,0x00,0xfc,0xce,0xc7,0xc3,0xc3,0xc3,0xc3,0xc3,0xc7,0xce,0xfc},
{0x00,0x00,0xff,0xc0,0xc0,0xc0,0xc0,0xfc,0xc0,0xc0,0xc0,0xc0,0xff},
{0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xfc,0xc0,0xc0,0xc0,0xff},
{0x00,0x00,0x7e,0xe7,0xc3,0xc3,0xcf,0xc0,0xc0,0xc0,0xc0,0xe7,0x7e},
{0x00,0x00,0xc3,0xc3,0xc3,0xc3,0xc3,0xff,0xc3,0xc3,0xc3,0xc3,0xc3},
{0x00,0x00,0x7e,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x7e},
{0x00,0x00,0x7c,0xee,0xc6,0x06,0x06,0x06,0x06,0x06,0x06,0x06,0x06},
{0x00,0x00,0xc3,0xc6,0xcc,0xd8,0xf0,0xe0,0xf0,0xd8,0xcc,0xc6,0xc3},
{0x00,0x00,0xff,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0},
{0x00,0x00,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xdb,0xff,0xff,0xe7,0xc3},
{0x00,0x00,0xc7,0xc7,0xcf,0xcf,0xdf,0xdb,0xfb,0xf3,0xf3,0xe3,0xe3},
{0x00,0x00,0x7e,0xe7,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xe7,0x7e},
{0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xfe,0xc7,0xc3,0xc3,0xc7,0xfe},
{0x00,0x00,0x3f,0x6e,0xdf,0xdb,0xc3,0xc3,0xc3,0xc3,0xc3,0x66,0x3c},
{0x00,0x00,0xc3,0xc6,0xcc,0xd8,0xf0,0xfe,0xc7,0xc3,0xc3,0xc7,0xfe},
{0x00,0x00,0x7e,0xe7,0x03,0x03,0x07,0x7e,0xe0,0xc0,0xc0,0xe7,0x7e},
{0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xff},
{0x00,0x00,0x7e,0xe7,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3},
{0x00,0x00,0x18,0x3c,0x3c,0x66,0x66,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3},
{0x00,0x00,0xc3,0xe7,0xff,0xff,0xdb,0xdb,0xc3,0xc3,0xc3,0xc3,0xc3},
{0x00,0x00,0xc3,0x66,0x66,0x3c,0x3c,0x18,0x3c,0x3c,0x66,0x66,0xc3},
{0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x3c,0x3c,0x66,0x66,0xc3},
{0x00,0x00,0xff,0xc0,0xc0,0x60,0x30,0x7e,0x0c,0x06,0x03,0x03,0xff},
{0x00,0x00,0x3c,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x3c},
{0x00,0x03,0x03,0x06,0x06,0x0c,0x0c,0x18,0x18,0x30,0x30,0x60,0x60},
{0x00,0x00,0x3c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x3c},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc3,0x66,0x3c,0x18},
{0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x38,0x30,0x70},
{0x00,0x00,0x7f,0xc3,0xc3,0x7f,0x03,0xc3,0x7e,0x00,0x00,0x00,0x00},
{0x00,0x00,0xfe,0xc3,0xc3,0xc3,0xc3,0xfe,0xc0,0xc0,0xc0,0xc0,0xc0},
{0x00,0x00,0x7e,0xc3,0xc0,0xc0,0xc0,0xc3,0x7e,0x00,0x00,0x00,0x00},
{0x00,0x00,0x7f,0xc3,0xc3,0xc3,0xc3,0x7f,0x03,0x03,0x03,0x03,0x03},
{0x00,0x00,0x7f,0xc0,0xc0,0xfe,0xc3,0xc3,0x7e,0x00,0x00,0x00,0x00},
{0x00,0x00,0x30,0x30,0x30,0x30,0x30,0xfc,0x30,0x30,0x30,0x33,0x1e},
{0x7e,0xc3,0x03,0x03,0x7f,0xc3,0xc3,0xc3,0x7e,0x00,0x00,0x00,0x00},
{0x00,0x00,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xfe,0xc0,0xc0,0xc0,0xc0},
{0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x18,0x00},
{0x38,0x6c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x00,0x00,0x0c,0x00},
{0x00,0x00,0xc6,0xcc,0xf8,0xf0,0xd8,0xcc,0xc6,0xc0,0xc0,0xc0,0xc0},
{0x00,0x00,0x7e,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x78},
{0x00,0x00,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xfe,0x00,0x00,0x00,0x00},
{0x00,0x00,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0xfc,0x00,0x00,0x00,0x00},
{0x00,0x00,0x7c,0xc6,0xc6,0xc6,0xc6,0xc6,0x7c,0x00,0x00,0x00,0x00},
{0xc0,0xc0,0xc0,0xfe,0xc3,0xc3,0xc3,0xc3,0xfe,0x00,0x00,0x00,0x00},
{0x03,0x03,0x03,0x7f,0xc3,0xc3,0xc3,0xc3,0x7f,0x00,0x00,0x00,0x00},
{0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xe0,0xfe,0x00,0x00,0x00,0x00},
{0x00,0x00,0xfe,0x03,0x03,0x7e,0xc0,0xc0,0x7f,0x00,0x00,0x00,0x00},
{0x00,0x00,0x1c,0x36,0x30,0x30,0x30,0x30,0xfc,0x30,0x30,0x30,0x00},
{0x00,0x00,0x7e,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0x00,0x00,0x00,0x00},
{0x00,0x00,0x18,0x3c,0x3c,0x66,0x66,0xc3,0xc3,0x00,0x00,0x00,0x00},
{0x00,0x00,0xc3,0xe7,0xff,0xdb,0xc3,0xc3,0xc3,0x00,0x00,0x00,0x00},
{0x00,0x00,0xc3,0x66,0x3c,0x18,0x3c,0x66,0xc3,0x00,0x00,0x00,0x00},
{0xc0,0x60,0x60,0x30,0x18,0x3c,0x66,0x66,0xc3,0x00,0x00,0x00,0x00},
{0x00,0x00,0xff,0x60,0x30,0x18,0x0c,0x06,0xff,0x00,0x00,0x00,0x00},
{0x00,0x00,0x0f,0x18,0x18,0x18,0x38,0xf0,0x38,0x18,0x18,0x18,0x0f},
{0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18},
{0x00,0x00,0xf0,0x18,0x18,0x18,0x1c,0x0f,0x1c,0x18,0x18,0x18,0xf0},
{0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x8f,0xf1,0x60,0x00,0x00,0x00}
};

GLuint fontOffset;

void makeRasterFont(void) {
  GLuint i;
  glPixelStorei(GL_UNPACK_ALIGNMENT,1);
  fontOffset = glGenLists(128);
  for(i = 32; i < 127; i++) {
    glNewList(i+fontOffset,GL_COMPILE);
    glBitmap(8,13,0.0,2.0,10.0,0.0,rasters[i-32]);
    glEndList(); }
}

void myinit(void) {
  glShadeModel(GL_FLAT);
  makeRasterFont();
}

void printString(char *s) {
  glPushAttrib(GL_LIST_BIT);
  glListBase(fontOffset);
  glCallLists(strlen(s),GL_UNSIGNED_BYTE,(GLubyte *) s);
  glPopAttrib();
}

void CALLBACK display(void) {
  GLfloat white[3] = { 1.0,1.0,1.0 };
  int i,j;
  char teststring[33];
  glClear(GL_COLOR_BUFFER_BIT);
  glColor3fv(white);
  for(i = 32; i < 127; i += 32) {
      glRasterPos2i(20,150 - 18*i/32);
      for(j = 0; j < 32; j++)
        teststring[j] =(char)(i+j);
      teststring[32] = 0;
      printString(teststring); }
  glRasterPos2i(20,50);
  printString("The quick brown fox jumps");
  glRasterPos2i(20,32);
  printString("over a lazy dog.");
  glFlush();
}

void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glOrtho(0.0,w,0.0,h,-1.0,1.0);
  glMatrixMode(GL_MODELVIEW);
}

void main(void) {
  auxInitDisplayMode(AUX_SINGLE|AUX_RGB);
  auxInitPosition(0,0,380,170);
  auxInitWindow("Font");
  myinit();
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}

RETOUR


Une police de caractères vectorielle

L'exécutable

Le source: Font2.cpp

#include "windows.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#define PT 1
#define STROKE 2
#define END 3

typedef struct charpoint {
  GLfloat   x,y;
  int  type;
} CP;

CP Adata[] = {
  { 0,0,PT},{0,9,PT},{1,10,PT},{4,10,PT},
  {5,9,PT},{5,0,STROKE},{0,5,PT},{5,5,END}
};

CP Edata[] = {
  {5,0,PT},{0,0,PT},{0,10,PT},{5,10,STROKE},
  {0,5,PT},{4,5,END}
};

CP Pdata[] = {
  {0,0,PT},{0,10,PT}, {4,10,PT},{5,9,PT},{5,6,PT},
  {4,5,PT},{0,5,END}
};

CP Rdata[] = {
  {0,0,PT},{0,10,PT}, {4,10,PT},{5,9,PT},{5,6,PT},
  {4,5,PT},{0,5,STROKE},{3,5,PT},{5,0,END}
};

CP Sdata[] = {
  {0,1,PT},{1,0,PT},{4,0,PT},{5,1,PT},{5,4,PT},
  {4,5,PT},{1,5,PT},{0,6,PT},{0,9,PT},{1,10,PT},
  {4,10,PT},{5,9,END}
};

void drawLetter(CP *l) {
  glBegin(GL_LINE_STRIP);
  while(1) {
    switch(l->type) {
      case PT     : glVertex2fv(&l->x);
                    break;
      case STROKE : glVertex2fv(&l->x);
                    glEnd();
                    glBegin(GL_LINE_STRIP);
                    break;
      case END    : glVertex2fv(&l->x);
                    glEnd();
                    glTranslatef(8.0,0.0,0.0);
                    return; }
    l++; }
}

void myinit(void) {
  GLuint base;
  glShadeModel(GL_FLAT);
  base = glGenLists(128);
  glListBase(base);
  glNewList(base+'A',GL_COMPILE);
  drawLetter(Adata);
  glEndList();
  glNewList(base+'E',GL_COMPILE);
  drawLetter(Edata);
  glEndList();
  glNewList(base+'P',GL_COMPILE);
  drawLetter(Pdata);
  glEndList();
  glNewList(base+'R',GL_COMPILE);
  drawLetter(Rdata);
  glEndList();
  glNewList(base+'S',GL_COMPILE);
  drawLetter(Sdata);
  glEndList();
  glNewList(base+' ',GL_COMPILE);
  glTranslatef(8.0,0.0,0.0);
  glEndList();
}

char *test1 = "A SPARE SERAPE APPEARS AS";
char *test2 = "APES PREPARE RARE PEPPERS";

void printStrokedString(char *s) {
  GLsizei len = strlen(s);
  glCallLists(len,GL_BYTE,(GLbyte *)s);
}

void CALLBACK display(void) {
  glClear(GL_COLOR_BUFFER_BIT);
  glColor3f(1.0,1.0,1.0);
  glPushMatrix();
  glScalef(2.0,2.0,2.0);
  glTranslatef(5.0,30.0,0.0);
  printStrokedString(test1);
  glPopMatrix();
  glPushMatrix();
  glScalef(2.0,2.0,2.0);
  glTranslatef(5.0,13.0,0.0);
  printStrokedString(test2);
  glPopMatrix();
  glFlush();
}

int main(int argc,char** argv) {
  auxInitDisplayMode(AUX_SINGLE|AUX_RGB);
  auxInitPosition(0,0,415,120);
  auxInitWindow(argv[0]);
  myinit();
  auxMainLoop(display);
}

RETOUR


Une surface de Bézier texturée

L'exécutable

Le source: TextSurfBezier.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <math.h>

#define iw 64
#define ih 64

static float anglex = 0.0F ;
static float angley = 0.0F ;

GLubyte im[3*iw*ih];
GLfloat pts[4][4][3] = {
  { {-1.5,-1.5,4.0},{-0.5,-1.5,2.0},
    {0.5,-1.5,-1.0},{1.5,-1.5,2.0}},
  { {-1.5,-0.5,1.0},{-0.5,-0.5,3.0},
    {0.5,-0.5,0.0},{1.5,-0.5,-1.0}},
  { {-1.5,0.5,4.0},{-0.5,0.5,0.0},
    {0.5,0.5,3.0},{1.5,0.5,4.0}},
  { {-1.5,1.5,-2.0},{-0.5,1.5,-2.0},
    {0.5,1.5,0.0},{1.5,1.5,-1.0}}};
GLfloat texpts[2][2][2] = {
  {{0.0,0.0},{0.0,1.0}},
  {{1.0,0.0},{1.0,1.0}}};

void makeImage(void) {
  int i,j;
  float ti,tj;
  for( i = 0 ; i < iw ; i++ ) {
    ti = 2.0F*3.14159265F*i/iw;
    for( j = 0 ; j < ih ; j++ ) {
      tj = 2.0F*3.14159265F*j/ih;
      im[3*(ih*i+j)]=(unsigned char) (127*(1+sin(ti)));
      im[3*(ih*i+j)+1]=(unsigned char) (127*(1+cos(2*tj)));
      im[3*(ih*i+j)+2]=(unsigned char) (127*(1+cos(ti+tj)));
    }
  }
}

void myinit(void) {
  glMap2f(GL_MAP2_VERTEX_3,0,1,3,4,0,1,12,4,&pts[0][0][0]);
  glMap2f(GL_MAP2_TEXTURE_COORD_2,0,1,2,2,0,1,4,2,&texpts[0][0][0]);
  glEnable(GL_MAP2_TEXTURE_COORD_2);
  glEnable(GL_MAP2_VERTEX_3);
  glMapGrid2f(20,0.0,1.0,20,0.0,1.0);
  makeImage();
  glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);
  glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
  glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
  glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
  glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
  glTexImage2D(GL_TEXTURE_2D,0,3,iw,ih,0,
  GL_RGB,GL_UNSIGNED_BYTE,im);
  glEnable(GL_TEXTURE_2D);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_NORMALIZE);
  glShadeModel(GL_FLAT);
}

void CALLBACK display(void) {
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  glRotatef(angley,0.0F,1.0F,0.0F) ;
  glRotatef(anglex,1.0F,0.0F,0.0F) ;
  glRotatef(85.0,1.0,1.0,1.0);
  glEvalMesh2(GL_FILL,0,20,0,20);
  glPopMatrix();
  glFlush();
  auxSwapBuffers();
}

void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if ( w <= h )
    glOrtho(-2.5,2.5,-2.5*(float)h/(float)w,2.5*(float)h/(float)w,-4.0,4.0);
    else
    glOrtho(-2.5*(float)w/(float)h,2.5*(float)w/(float)h,-2.5,2.5,-4.0,4.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void CALLBACK up(void) {
  anglex-- ;
}

void CALLBACK down(void) {
  anglex++ ;
}

void CALLBACK left(void) {
  angley-- ;
}

void CALLBACK right(void) {
  angley++ ;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGB|AUX_DEPTH);
  auxInitPosition(0,0,300,230);
  auxInitWindow("Surface de Bézier");
  myinit();
  auxKeyFunc(AUX_UP,up) ;
  auxKeyFunc(AUX_DOWN,down) ;
  auxKeyFunc(AUX_LEFT,left) ;
  auxKeyFunc(AUX_RIGHT,right) ;
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Une surface de Bézier

L'exécutable

Le source: SurfaceBezier.cpp

#include <windows.h>

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

GLfloat pts[4][4][3] = {
  { {-1.5,-1.5,4.0},{-0.5,-1.5,2.0},
    {0.5,-1.5,-1.0},{1.5,-1.5,2.0}},
  { {-1.5,-0.5,1.0},{-0.5,-0.5,3.0},
    {0.5,-0.5,0.0},{1.5,-0.5,-1.0}},
  { {-1.5,0.5,4.0},{-0.5,0.5,0.0},
    {0.5,0.5,3.0},{1.5,0.5,4.0}},
  { {-1.5,1.5,-2.0},{-0.5,1.5,-2.0},
    {0.5,1.5,0.0},{1.5,1.5,-1.0}}};
static float anglex = 0.0F ;
static float angley = 0.0F ;

void initlights(void) {
  GLfloat ambient[] = { 0.2F,0.2F,0.2F,1.0F };
  GLfloat pos[] = { 0.0F,0.0F,2.0F,1.0F };
  GLfloat diffuse[] = { 0.6F,0.6F,0.6F,1.0F };
  GLfloat spec[] = { 1.0F,1.0F,1.0F,1.0F };
  GLfloat shininess[] = { 50.0F };
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glLightfv(GL_LIGHT0,GL_AMBIENT,ambient);
  glLightfv(GL_LIGHT0,GL_POSITION,pos);
  glMaterialfv(GL_FRONT,GL_DIFFUSE,diffuse);
  glMaterialfv(GL_FRONT,GL_SPECULAR,spec);
  glMaterialfv(GL_FRONT,GL_SHININESS,shininess);
}

void CALLBACK display(void) {
  glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
  glPushMatrix();
  glRotatef(angley,0.0F,1.0F,0.0F) ;
  glRotatef(anglex,1.0F,0.0F,0.0F) ;
  glRotatef(85.0,1.0,1.0,1.0);
  glEvalMesh2(GL_FILL,0,20,0,20);
  glPopMatrix();
  glFlush();
  auxSwapBuffers();
}

void myinit(void) {
  glClearColor(0.8,0.8,1.0,1.0);
  glEnable(GL_DEPTH_TEST);
  glMap2f(GL_MAP2_VERTEX_3,0,1,3,4,0,1,12,4,&pts[0][0][0]);
  glEnable(GL_MAP2_VERTEX_3);
  glEnable(GL_AUTO_NORMAL);
  glEnable(GL_NORMALIZE);
  glMapGrid2f(20,0.0,1.0,20,0.0,1.0);
  initlights();
}

void CALLBACK myReshape(int w,int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if ( w <= h )
    glOrtho(-3.2,3.2,-3.2*(float)h/(float)w,3.2*(float)h/(float)w,-4.0,4.0);
    else
    glOrtho(-3.2*(float)w/(float)h,3.2*(float)w/(float)h,-3.2,3.2,-4.0,4.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void CALLBACK up(void) {
  anglex-- ;
}

void CALLBACK down(void) {
  anglex++ ;
}

void CALLBACK left(void) {
  angley-- ;
}

void CALLBACK right(void) {
  angley++ ;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGB|AUX_DEPTH);
  auxInitPosition(0,0,300,230);
  auxInitWindow("Surface de Bézier");
  myinit();
  auxKeyFunc(AUX_UP,up) ;
  auxKeyFunc(AUX_DOWN,down) ;
  auxKeyFunc(AUX_LEFT,left) ;
  auxKeyFunc(AUX_RIGHT,right) ;
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}
   RETOUR


Une X-Wing de la rebélion

L'exécutable

Le fichier de définition

Le source (OpengGL 1.1 nécessaire)

   RETOUR


Utilisation du buffer accumulation pour réaliser de l'anti-aliasing

L'exécutable

Le source: Accum.cpp

#include "windows.h"

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glaux.h>

#include <stdio.h>

static float anglex = 0.0F ;
static float angley = 0.0F ;

GLsizei width,height;

void myinit(void) {
  GLfloat ambient[] = { 0.4F,0.4F,0.4F,1.0F };
  GLfloat diffuse[] = { 1.0F,1.0F,1.0F,1.0F };
  GLfloat specular[] = { 1.0F,1.0F,1.0F,1.0F };
  GLfloat position[] = { 0.0F,2.0F,2.0F,0.0F };
  GLfloat mat_ambient[] = { 0.2F,0.2F,0.2F,1.0F };
  GLfloat mat_diffuse[] = { 0.7F,0.7F,0.7F,1.0F };
  GLfloat mat_specular[] = { 0.9F,0.9F,0.9F,1.0F };
  GLfloat mat_shininess[] = { 50.0F };
  GLfloat lmodel_ambient[] = { 0.2F,0.2F,0.2F,1.0F };
  glEnable(GL_DEPTH_TEST);
  glDepthFunc(GL_LESS);
  glLightfv(GL_LIGHT0,GL_AMBIENT,ambient);
  glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuse);
  glLightfv(GL_LIGHT0,GL_SPECULAR,specular);
  glLightfv(GL_LIGHT0,GL_POSITION,position);
  glMaterialfv(GL_FRONT,GL_AMBIENT,mat_ambient);
  glMaterialfv(GL_FRONT,GL_DIFFUSE,mat_diffuse);
  glMaterialfv(GL_FRONT,GL_SPECULAR,mat_specular);
  glMaterialfv(GL_FRONT,GL_SHININESS,mat_shininess);
  glLightModelfv(GL_LIGHT_MODEL_AMBIENT,lmodel_ambient);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glEnable(GL_AUTO_NORMAL);
  glEnable(GL_NORMALIZE);
  glEnable(GL_CULL_FACE);
  glCullFace(GL_FRONT);
  glClearAccum(0.0,0.0,0.0,0.0);
}

#define ACSIZE 16

GLfloat dx[ACSIZE],dy[ACSIZE];

GLfloat jitter3[3][2] = {
  {0.5F,0.5F},
  {1.35899e-05F,0.230369F},
  {0.000189185F,0.766878F},
};

GLfloat jitter11[11][2] = {
  {0.5F,0.5F},{0.406537F,0.135858F},
  {0.860325F,0.968558F},{0.680141F,0.232877F},
  {0.775694F,0.584871F},{0.963354F,0.309056F},
  {0.593493F,0.864072F},{0.224334F,0.415055F},
  {0.0366643F,0.690884F},{0.139685F,0.0313988F},
  {0.319861F,0.767097F},
};

GLfloat jitter16[16][2] = {
  {0.4375F,0.4375F},{0.1875F,0.5625F},
  {0.9375F,1.1875F},{0.4375F,0.9375F-1.0F},
  {0.6875F,0.5625F},{0.1875F,0.0625F},
  {0.6875F,0.3125F},{0.1875F,0.3125F},
  {0.4375F,0.1875F},{0.9375F-1.0F,0.4375F},
  {0.6875F,0.8125F},{0.4375F,0.6875F},
  {0.6875F,0.0625F},{0.9375F,0.9375F},
  {1.1875F,0.8125F},{0.9375F,0.6875F},
};

GLfloat jitter29[29][2] = {
  {0.5F,0.5F},{0.498126F,0.141363F},
  {0.217276F,0.651732F},{0.439503F,0.954859F},
  {0.734171F,0.836294F},{0.912454F,0.79952F},
  {0.406153F,0.671156F},{0.0163892F,0.631994F},
  {0.298064F,0.843476F},{0.312025F,0.0990405F},
  {0.98135F,0.965697F},{0.841999F,0.272378F},
  {0.559348F,0.32727F},{0.809331F,0.638901F},
  {0.632583F,0.994471F},{0.00588314F,0.146344F},
  {0.713365F,0.437896F},{0.185173F,0.246584F},
  {0.901735F,0.474544F},{0.366423F,0.296698F},
  {0.687032F,0.188184F},{0.313256F,0.472999F},
  {0.543195F,0.800044F},{0.629329F,0.631599F},
  {0.818263F,0.0439354F},{0.163978F,0.00621497F},
  {0.109533F,0.812811F},{0.131325F,0.471624F},
  {0.0196755F,0.331813F},
};

GLfloat jitter90[90][2] = {
  {0.5F,0.5F},{0.784289F,0.417355F},
  {0.608691F,0.678948F},{0.546538F,0.976002F},
  {0.972245F,0.270498F},{0.765121F,0.189392F},
  {0.513193F,0.743827F},{0.123709F,0.874866F},
  {0.991334F,0.745136F},{0.56342F,0.0925047F},
  {0.662226F,0.143317F},{0.444563F,0.928535F},
  {0.248017F,0.981655F},{0.100115F,0.771923F},
  {0.593937F,0.559383F},{0.392095F,0.225932F},
  {0.428776F,0.812094F},{0.510615F,0.633584F},
  {0.836431F,0.00343328F},{0.494037F,0.391771F},
  {0.617448F,0.792324F},{0.688599F,0.48914F},
  {0.530421F,0.859206F},{0.0742278F,0.665344F},
  {0.979388F,0.626835F},{0.183806F,0.479216F},
  {0.151222F,0.0803998F},{0.476489F,0.157863F},
  {0.792675F,0.653531F},{0.0990416F,0.267284F},
  {0.776667F,0.303894F},{0.312904F,0.296018F},
  {0.288777F,0.691008F},{0.460097F,0.0436075F},
  {0.594323F,0.440751F},{0.876296F,0.472043F},
  {0.0442623F,0.0693901F},{0.355476F,0.00442787F},
  {0.391763F,0.361327F},{0.406994F,0.696053F},
  {0.708393F,0.724992F},{0.925807F,0.933103F},
  {0.850618F,0.11774F},{0.867486F,0.233677F},
  {0.208805F,0.285484F},{0.572129F,0.211505F},
  {0.172931F,0.180455F},{0.327574F,0.598031F},
  {0.685187F,0.372379F},{0.23375F,0.878555F},
  {0.960657F,0.409561F},{0.371005F,0.113866F},
  {0.29471F,0.496941F},{0.748611F,0.0735321F},
  {0.878643F,0.34504F},{0.210987F,0.778228F},
  {0.692961F,0.606194F},{0.82152F,0.8893F},
  {0.0982095F,0.563104F},{0.214514F,0.581197F},
  {0.734262F,0.956545F},{0.881377F,0.583548F},
  {0.0560485F,0.174277F},{0.0729515F,0.458003F},
  {0.719604F,0.840564F},{0.325388F,0.7883F},
  {0.26136F,0.0848927F},{0.393754F,0.467505F},
  {0.425361F,0.577672F},{0.648594F,0.0248658F},
  {0.983843F,0.521048F},{0.272936F,0.395127F},
  {0.177695F,0.675733F},{0.89175F,0.700901F},
  {0.632301F,0.908259F},{0.782859F,0.53611F},
  {0.0141421F,0.855548F},{0.0437116F,0.351866F},
  {0.939604F,0.0450863F},{0.0320883F,0.962943F},
  {0.341155F,0.895317F},{0.952087F,0.158387F},
  {0.908415F,0.820054F},{0.481435F,0.281195F},
  {0.675525F,0.25699F},{0.585273F,0.324454F},
  {0.156488F,0.376783F},{0.140434F,0.977416F},
  {0.808155F,0.77305F},{0.282973F,0.188937F},
};

void loaddxdy(void) {
  long i;
  for (i = 0; i < ACSIZE; i++) {
    dx[i] = jitter16[i][0]*10/width;
    dy[i] = jitter16[i][1]*10/height; }
}

void CALLBACK display(void) {
  int i;
  glPushMatrix() ;
  glClear(GL_ACCUM_BUFFER_BIT);
  loaddxdy();
  for (i = 0; i < (ACSIZE); i++) {
    printf("Pass %d\n",i);
    glPushMatrix();
    glTranslatef(dx[i],dy[i],0.0);
    glRotatef(45.0,1.0,1.0,1.0);
    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
    glRotatef(angley,0.0F,1.0F,0.0F) ;
    glRotatef(anglex,1.0F,0.0F,0.0F) ;
    auxSolidTeapot(2.0);
    glPopMatrix();
    glAccum(GL_ACCUM,1.0/(ACSIZE)); }
  printf("final job\n");
  glAccum(GL_RETURN,1.0);
  printf("done\n");
  glPopMatrix() ;
  glFlush();
  auxSwapBuffers() ;
}

void CALLBACK myReshape(int w,int h) {
  width = w; height = h;
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  if (w <= h)
    glOrtho(-3.0,3.0,-3.0*(GLfloat) h/(GLfloat) w,3.0*(GLfloat) h/(GLfloat) w,-15.0,15.0);
    else
    glOrtho(-3.0*(GLfloat) w/(GLfloat) h,3.0*(GLfloat) w/(GLfloat) h,-3.0,3.0,-15.0,15.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void CALLBACK up(void) {
  anglex -= 5 ;
}

void CALLBACK down(void) {
  anglex += 5 ;
}

void CALLBACK left(void) {
  angley -= 5 ;
}

void CALLBACK right(void) {
  angley += 5 ;
}

void main(void) {
  auxInitDisplayMode(AUX_DOUBLE|AUX_RGB|AUX_ACCUM|AUX_DEPTH);
  auxInitPosition(0,0,300,300);
  auxInitWindow("Utilisation de du Tampon Accumulation : Antialiasing");
  myinit();
  auxKeyFunc(AUX_UP,up) ;
  auxKeyFunc(AUX_DOWN,down) ;
  auxKeyFunc(AUX_LEFT,left) ;
  auxKeyFunc(AUX_RIGHT,right) ;
  auxReshapeFunc(myReshape);
  auxMainLoop(display);
}

RETOUR


Lesson 01

; include file for Masm32 (and nasm/tasm)
; could be find at hardcode site :
; http://bizarrecreations.webjump.com

.586p
.MODEL FLAT,STDCALL
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\ogl\gl.def
include \masm32\include\ogl\glu.def
includelib \masm32\lib\opengl32.lib
includelib \masm32\lib\glu32.lib
include \masm32\include\ogl\kernel32.inc
include \masm32\include\ogl\user32.inc
include \masm32\include\ogl\gdi32.inc
include \masm32\include\ogl\WinExtra\winextra.def
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\gdi32.lib

CreateGLWindow  PROTO :DWORD,:DWORD,:DWORD,:DWORD,:BYTE
WinMain         PROTO :DWORD,:DWORD,:DWORD,:DWORD
WndProc         PROTO :DWORD,:DWORD,:DWORD,:DWORD
ReSizeGLScene   PROTO :DWORD,:DWORD
KillGLWindow    PROTO
InitGL          PROTO
DrawGLScene     PROTO

_glClearDepth MACRO t                   ;this is not defined in hardcode include files
    gl_dpush t                          ;so here it is.
    mov eax, eax
    mov ebx, ebx
    call glClearDepth
ENDM

_gluPerspective MACRO a,b,c,d
    gl_dpush d
    gl_dpush c
    gl_dpush b
    gl_dpush a
    mov eax, eax
    mov ebx, ebx
    call gluPerspective
ENDM

include include.def

.DATA
scalp           db "(scalp)",0
our_title       db "Masm32 conversion of Nehe's tuts, (s)",0
ClassName       db "asmOpenGL",0
error           db "ERROR",0
noDC_RC         db "Release of DC and RC failed.",0
RCrelease       db "Release rendering context failed.",0
DCrelease       db "Release device context failed.",0
hWndRelease     db "Could not release hWnd.",0
register_error  db "Failed to register the window class.",0
not_full        db "The requested fullscreen mode is not supported by",13,10
                db "your video card. Use windowed mode instead ?",0
create_device   db "Can't create a GL device context.",0
create_pixel    db "Can't find a suitable pixelformat.",0
close           db "Program will now close",0
create_error    db "Window creation error.",0
pixel_error     db "Can't set the pixelformat.",0
rend_error      db "Can't create a GL rendering context.",0
act_error       db "Can't activate the GL rendering context.",0
init_error      db "Initialization Failed.",0
start_full      db "Would you like to run in fullscreen mode ?",0
class_error     db "Could not unregister class.",0

.DATA?
hDC         HDC ?
hRC         HGLRC ?
hInstance   HANDLE ?
keys        db 256 dup (?)
active      db ?
fullscreen  db ?
hWnd        dd ?

.CODE
start:
    invoke GetModuleHandle, 0
    mov hInstance, eax
    invoke WinMain, eax, 0, 0, 0
    invoke ExitProcess, eax

WinMain proc hInst:HWND, hPrevInst:UINT, CmdLine:WPARAM, CmdShow:LPARAM
  LOCAL msg:MSG, done:DWORD
    invoke MessageBox, 0, ADDR start_full, ADDR our_title, MB_YESNO or MB_ICONQUESTION
    .IF eax == IDNO
        mov fullscreen, 0
    .ELSE
        mov fullscreen, 1
    .ENDIF
    invoke CreateGLWindow, ADDR our_title, 640, 480, 16, fullscreen
    .IF (!eax)
        ret
    .ENDIF
    mov done, 0
    .while (!done)
        invoke PeekMessage, ADDR msg,NULL,0,0,PM_REMOVE
        .IF (eax)
            .IF msg.message == WM_QUIT
                mov done, 1
            .ELSE
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
            .ENDIF
        .ELSE
            invoke DrawGLScene
            .IF ((active) && (!eax)) || (keys[VK_ESCAPE])
                mov done, 1
            .ELSE
                invoke SwapBuffers, hDC
            .ENDIF
            .IF (keys[VK_F1])
                mov keys[VK_F1], 0
                invoke KillGLWindow
                .IF (fullscreen)
                    mov fullscreen, 0
                .ELSE
                    mov fullscreen, 1
                .ENDIF
                invoke CreateGLWindow, ADDR our_title, 640,480,16,fullscreen
                .IF (!eax)
                    ret
                .ENDIF
            .ENDIF
        .ENDIF
    .endw
    invoke KillGLWindow
    mov eax, msg.wParam
    ret
WinMain endp

WndProc proc hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    .IF uMsg == WM_ACTIVATE
        mov eax, wParam
        .IF (!ah)
            mov active, 1
        .ELSE
            mov active, 0
        .ENDIF
    .ELSEIF uMsg == WM_SYSCOMMAND
        .IF (wParam == SC_SCREENSAVE) || (wParam == SC_MONITORPOWER)
            xor eax, eax
            ret
        .ENDIF
    .ELSEIF uMsg == WM_KEYDOWN
        mov eax, wParam
        mov keys[eax], 1
    .ELSEIF uMsg == WM_KEYUP
        mov eax, wParam
        mov keys[eax], 0
    .ELSEIF uMsg == WM_SIZE
        mov eax, lParam
        mov ebx, eax
        and eax, 0ffffh         ;eax=width=LOWORD
        shr ebx, 16             ;ebx=height=HIWORD
        invoke ReSizeGLScene, eax, ebx
    .ELSEIF uMsg == WM_CLOSE
        invoke PostQuitMessage, 0
        xor eax, eax
        ret
    .ENDIF
    invoke DefWindowProc, hWin, uMsg, wParam, lParam
    ret
WndProc endp

InitGL proc         ;opengl setup (like color, etc...)
    invoke glShadeModel, GL_SMOOTH              ;enable smooth shading
    _glClearDepth 1.0f                          ;depth buffer setup
    invoke glEnable, GL_DEPTH_TEST              ;enable depth testing
    invoke glDepthFunc, GL_LEQUAL               ;type of depth test
  ;set really nice perspective calculations
    invoke glHint, GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST
    mov eax, 1
    ret
InitGL endp

CreateGLWindow proc letitle:DWORD,w:DWORD,h:DWORD,bits:DWORD,flag:BYTE
  LOCAL PixelFormat:DWORD, wc:WNDCLASS
  LOCAL dwExStyle:DWORD, dwStyle:DWORD
  LOCAL rect:RECT, dmScreenSettings:DEVMODE
  LOCAL pfd:PIXELFORMATDESCRIPTOR
    mov rect.left, 0
    push w
    pop rect.right
    mov rect.top, 0
    push h
    pop rect.bottom
    mov al, flag
    mov fullscreen, al
    mov wc.style, CS_HREDRAW or CS_VREDRAW or CS_OWNDC
    mov wc.lpfnWndProc, offset WndProc
    mov wc.cbClsExtra, 0
    mov wc.cbWndExtra, 0
    push hInstance
    pop wc.hInstance
    mov wc.hIcon, 0
    invoke LoadCursor, 0, IDC_ARROW
    mov wc.hCursor, eax
    mov wc.lpszMenuName, 0
    mov wc.lpszClassName, offset ClassName
    mov wc.hbrBackground, 0
    invoke RegisterClass, ADDR wc
    .IF (!eax)
        invoke MessageBox, 0, ADDR register_error, ADDR error, MB_OK or MB_ICONEXCLAMATION
        xor eax, eax
        ret
    .ENDIF
    .IF (fullscreen)
        ZeroMemory &dmScreenSettings, sizeof(dmScreenSettings)
        mov dmScreenSettings.dmSize, sizeof dmScreenSettings
        push w
        pop dmScreenSettings.dmPelsWidth
        push h
        pop dmScreenSettings.dmPelsHeight
        push bits
        pop dmScreenSettings.dmBitsPerPel
        mov dmScreenSettings.dmFields, DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT
        invoke ChangeDisplaySettings, ADDR dmScreenSettings, CDS_FULLSCREEN
        .IF eax != DISP_CHANGE_SUCCESSFUL
            invoke MessageBox, 0, ADDR not_full, ADDR scalp, MB_YESNO or MB_ICONEXCLAMATION
            .IF eax == IDYES
                mov fullscreen, 0
            .ELSE
                invoke MessageBox, 0, ADDR close, ADDR error, MB_OK or MB_ICONSTOP
                xor eax, eax
                ret
            .ENDIF
        .ENDIF
    .ENDIF
    .IF (fullscreen)
        mov dwExStyle, WS_EX_APPWINDOW
        mov dwStyle, WS_POPUP or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
        invoke ShowCursor, 0
    .ELSE
        mov dwExStyle, WS_EX_APPWINDOW or WS_EX_WINDOWEDGE
        mov dwStyle, WS_OVERLAPPEDWINDOW or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
    .ENDIF
    invoke AdjustWindowRectEx, ADDR rect, dwStyle, 0, dwExStyle
    mov eax, rect.left
    sub rect.right, eax
    mov eax, rect.top
    sub rect.bottom, eax
    invoke CreateWindowEx, dwExStyle, ADDR ClassName, ADDR our_title, \
                           dwStyle,0,0, rect.right, rect.bottom, 0, 0, hInstance, 0
    mov hWnd, eax
    .IF (!eax)
        invoke KillGLWindow
        invoke MessageBox, 0, ADDR create_error, ADDR error, MB_OK or MB_ICONEXCLAMATION
        xor eax, eax
        ret
    .ENDIF
    ZeroMemory &pfd, sizeof(PIXELFORMATDESCRIPTOR)
    mov pfd.nSize, sizeof PIXELFORMATDESCRIPTOR
    mov pfd.nVersion, 1
    mov pfd.dwFlags, PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER
    mov pfd.iPixelType, PFD_TYPE_RGBA
    mov eax, bits
    mov pfd.cColorBits, al
    mov pfd.cDepthBits, 16
    mov pfd.dwLayerMask, PFD_MAIN_PLANE
    invoke GetDC, hWnd
    mov hDC, eax
    .IF (!eax)
        invoke KillGLWindow
        invoke MessageBox, 0, ADDR create_device, ADDR error, MB_OK or MB_ICONEXCLAMATION
        xor eax, eax
        ret
    .ENDIF
    invoke ChoosePixelFormat, hDC, ADDR pfd
    mov PixelFormat, eax
    .IF (!eax)
        invoke KillGLWindow
        invoke MessageBox, 0, ADDR create_pixel, ADDR error, MB_OK or MB_ICONEXCLAMATION
        xor eax, eax
        ret
    .ENDIF
    invoke SetPixelFormat, hDC, PixelFormat, ADDR pfd
    .IF (!eax)
        invoke KillGLWindow
        invoke MessageBox, 0, ADDR pixel_error, ADDR error, MB_OK or MB_ICONEXCLAMATION
        xor eax, eax
        ret
    .ENDIF
    invoke wglCreateContext, hDC
    mov hRC, eax
    .IF (!eax)
        invoke KillGLWindow
        invoke MessageBox, 0, ADDR rend_error, ADDR error, MB_OK or MB_ICONEXCLAMATION
        xor eax, eax
        ret
    .ENDIF
    invoke wglMakeCurrent, hDC, hRC
    .IF (!eax)
        invoke KillGLWindow
        invoke MessageBox, 0, ADDR act_error, ADDR error, MB_OK or MB_ICONEXCLAMATION
        xor eax, eax
        ret
    .ENDIF
    invoke ShowWindow, hWnd, SW_SHOW
    invoke SetForegroundWindow, hWnd
    invoke SetFocus, hWnd
    invoke ReSizeGLScene, w, h
    invoke InitGL
    .IF (!eax)
        invoke KillGLWindow
        invoke MessageBox, 0, ADDR init_error, ADDR error, MB_OK or MB_ICONEXCLAMATION
        xor eax, eax
        ret
    .ENDIF
    mov eax, 1
    ret
CreateGLWindow endp

DrawGLScene proc                                        ;drawing proc
  ;clear screen and depth buffer
    invoke glClear, GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT
    invoke glLoadIdentity                               ;reset modelview matrix
        ;for now we do nothing...
    mov eax, 1
    ret
DrawGLScene endp

ReSizeGLScene proc w:DWORD, h:DWORD             ;resize scene
  LOCAL ratio:GLdouble
    .IF h == 0
        mov h, 1                                ;prevent div by zero
    .ENDIF
    invoke glViewport, 0, 0, w, h               ;reset viewport
    invoke glMatrixMode, GL_PROJECTION          ;select projection matrix
    invoke glLoadIdentity                       ;reset it
    fild w
    fild h
    fdivp st(1), st ;width/height
    fstp ratio
    _gluPerspective 45.0f, ratio, 0.1f,100.0f
    invoke glMatrixMode, GL_MODELVIEW                   ;select modelview matrix
    invoke glLoadIdentity                               ;reset it
    ret
ReSizeGLScene endp

KillGLWindow proc
;here we do a lot of tests, but at least, you have to do
;wglMakeCurrent, wglDeleteContext and ReleaseDC.
    .IF (fullscreen)                                ;if fullscreen mode
        invoke ChangeDisplaySettings, 0, 0          ;get back to window
        invoke ShowCursor, 1                        ;and show mouse ptr
    .ENDIF
  ;do a lot of error checking before closing
    .IF (hRC)
        invoke wglMakeCurrent, 0, 0
        .IF (!eax)
            invoke MessageBox, 0, ADDR noDC_RC, ADDR error, MB_OK or MB_ICONINFORMATION
        .ENDIF
        invoke wglDeleteContext, hRC
        .IF (!eax)
            invoke MessageBox, 0, ADDR RCrelease, ADDR error, MB_OK or MB_ICONINFORMATION
        .ENDIF
        mov hRC, 0
    .ENDIF
    invoke ReleaseDC, hWnd, hDC
    .IF (hDC && (!eax))
        invoke MessageBox, 0, ADDR DCrelease, ADDR error, MB_OK or MB_ICONINFORMATION
        mov hDC, 0
    .ENDIF
    invoke DestroyWindow, hWnd
    .IF (hWnd && (!eax))
        invoke MessageBox, 0, ADDR hWndRelease, ADDR error, MB_OK or MB_ICONINFORMATION
        mov hWnd, 0
    .ENDIF
    invoke UnregisterClass, ADDR ClassName, hInstance
    .IF (!eax)
        invoke MessageBox, 0, ADDR class_error, ADDR error, MB_OK or MB_ICONINFORMATION
    .ENDIF
    ret
KillGLWindow endp

END start


Lesson 02

; include file for Masm32 (and nasm/tasm)
; could be find at hardcode site :
; http://bizarrecreations.webjump.com

.586p
.MODEL FLAT,STDCALL
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\ogl\gl.def
include \masm32\include\ogl\glu.def
includelib \masm32\lib\opengl32.lib
includelib \masm32\lib\glu32.lib
include \masm32\include\ogl\kernel32.inc
include \masm32\include\ogl\user32.inc
include \masm32\include\ogl\gdi32.inc
include \masm32\include\ogl\WinExtra\winextra.def
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\gdi32.lib

CreateGLWindow  PROTO :DWORD,:DWORD,:DWORD,:DWORD,:BYTE
WinMain         PROTO :DWORD,:DWORD,:DWORD,:DWORD
WndProc         PROTO :DWORD,:DWORD,:DWORD,:DWORD
ReSizeGLScene   PROTO :DWORD,:DWORD
KillGLWindow    PROTO
InitGL          PROTO
DrawGLScene     PROTO

_glClearDepth MACRO t                   ;this is not defined in hardcode include files
    gl_dpush t                          ;so here it is.
    mov eax, eax
    mov ebx, ebx
    call glClearDepth
ENDM

_gluPerspective MACRO a,b,c,d
    gl_dpush d
    gl_dpush c
    gl_dpush b
    gl_dpush a
    mov eax, eax
    mov ebx, ebx
    call gluPerspective
ENDM

include include.def

.DATA
scalp           db "(scalp)",0
our_title       db "Our first polygon, (s)",0
ClassName       db "asmOpenGL",0
error           db "ERROR",0
noDC_RC         db "Release of DC and RC failed.",0
RCrelease       db "Release rendering context failed.",0
DCrelease       db "Release device context failed.",0
hWndRelease     db "Could not release hWnd.",0
register_error  db "Failed to register the window class.",0
not_full        db "The requested fullscreen mode is not supported by",13,10
                db "your video card. Use windowed mode instead ?",0
create_device   db "Can't create a GL device context.",0
create_pixel    db "Can't find a suitable pixelformat.",0
close           db "Program will now close",0
create_error    db "Window creation error.",0
pixel_error     db "Can't set the pixelformat.",0
rend_error      db "Can't create a GL rendering context.",0
act_error       db "Can't activate the GL rendering context.",0
init_error      db "Initialization Failed.",0
start_full      db "Would you like to run in fullscreen mode ?",0
class_error     db "Could not unregister class.",0

.DATA?
hDC         HDC ?
hRC         HGLRC ?
hInstance   HANDLE ?
keys        db 256 dup (?)
active      db ?
fullscreen  db ?
hWnd        dd ?

.CODE
start:
    invoke GetModuleHandle, 0
    mov hInstance, eax
    invoke WinMain, eax, 0, 0, 0
    invoke ExitProcess, eax

WinMain proc hInst:HWND, hPrevInst:UINT, CmdLine:WPARAM, CmdShow:LPARAM
  LOCAL msg:MSG, done:DWORD
    invoke MessageBox, 0, ADDR start_full, ADDR our_title, MB_YESNO or MB_ICONQUESTION
    .IF eax == IDNO
        mov fullscreen, 0
    .ELSE
        mov fullscreen, 1
    .ENDIF
    invoke CreateGLWindow, ADDR our_title, 640, 480, 16, fullscreen
    .IF (!eax)
        ret
    .ENDIF
    mov done, 0
    .while (!done)
        invoke PeekMessage, ADDR msg,NULL,0,0,PM_REMOVE
        .IF (eax)
            .IF msg.message == WM_QUIT
                mov done, 1
            .ELSE
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
            .ENDIF
        .ELSE
            invoke DrawGLScene
            .IF ((active) && (!eax)) || (keys[VK_ESCAPE])
                mov done, 1
            .ELSE
                invoke SwapBuffers, hDC
            .ENDIF
            .IF (keys[VK_F1])
                mov keys[VK_F1], 0
                invoke KillGLWindow
                .IF (fullscreen)
                    mov fullscreen, 0
                .ELSE
                    mov fullscreen, 1
                .ENDIF
                invoke CreateGLWindow, ADDR our_title, 640,480,16,fullscreen
                .IF (!eax)
                    ret
                .ENDIF
            .ENDIF
        .ENDIF
    .endw
    invoke KillGLWindow
    mov eax, msg.wParam
    ret
WinMain endp

WndProc proc hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    .IF uMsg == WM_ACTIVATE
        mov eax, wParam
        .IF (!ah)
            mov active, 1
        .ELSE
            mov active, 0
        .ENDIF
    .ELSEIF uMsg == WM_SYSCOMMAND
        .IF (wParam == SC_SCREENSAVE) || (wParam == SC_MONITORPOWER)
            xor eax, eax
            ret
        .ENDIF
    .ELSEIF uMsg == WM_KEYDOWN
        mov eax, wParam
        mov keys[eax], 1
    .ELSEIF uMsg == WM_KEYUP
        mov eax, wParam
        mov keys[eax], 0
    .ELSEIF uMsg == WM_SIZE
        mov eax, lParam
        mov ebx, eax
        and eax, 0ffffh         ;eax=width=LOWORD
        shr ebx, 16             ;ebx=height=HIWORD
        invoke ReSizeGLScene, eax, ebx
    .ELSEIF uMsg == WM_CLOSE
        invoke PostQuitMessage, 0
        xor eax, eax
        ret
    .ENDIF
    invoke DefWindowProc, hWin, uMsg, wParam, lParam
    ret
WndProc endp

InitGL proc         ;opengl setup (like color, etc...)
    invoke glShadeModel, GL_SMOOTH              ;enable smooth shading
    _glClearDepth 1.0f                          ;depth buffer setup
    invoke glEnable, GL_DEPTH_TEST              ;enable depth testing
    invoke glDepthFunc, GL_LEQUAL               ;type of depth test
  ;set really nice perspective calculations
    invoke glHint, GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST
    mov eax, 1
    ret
InitGL endp

CreateGLWindow proc letitle:DWORD,w:DWORD,h:DWORD,bits:DWORD,flag:BYTE
  LOCAL PixelFormat:DWORD, wc:WNDCLASS
  LOCAL dwExStyle:DWORD, dwStyle:DWORD
  LOCAL rect:RECT, dmScreenSettings:DEVMODE
  LOCAL pfd:PIXELFORMATDESCRIPTOR
    mov rect.left, 0
    push w
    pop rect.right
    mov rect.top, 0
    push h
    pop rect.bottom
    mov al, flag
    mov fullscreen, al
    mov wc.style, CS_HREDRAW or CS_VREDRAW or CS_OWNDC
    mov wc.lpfnWndProc, offset WndProc
    mov wc.cbClsExtra, 0
    mov wc.cbWndExtra, 0
    push hInstance
    pop wc.hInstance
    mov wc.hIcon, 0
    invoke LoadCursor, 0, IDC_ARROW
    mov wc.hCursor, eax
    mov wc.lpszMenuName, 0
    mov wc.lpszClassName, offset ClassName
    mov wc.hbrBackground, 0
    invoke RegisterClass, ADDR wc
    .IF (!eax)
        invoke MessageBox, 0, ADDR register_error, ADDR error, MB_OK or MB_ICONEXCLAMATION
        xor eax, eax
        ret
    .ENDIF
    .IF (fullscreen)
        ZeroMemory &dmScreenSettings, sizeof(dmScreenSettings)
        mov dmScreenSettings.dmSize, sizeof dmScreenSettings
        push w
        pop dmScreenSettings.dmPelsWidth
        push h
        pop dmScreenSettings.dmPelsHeight
        push bits
        pop dmScreenSettings.dmBitsPerPel
        mov dmScreenSettings.dmFields, DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT
        invoke ChangeDisplaySettings, ADDR dmScreenSettings, CDS_FULLSCREEN
        .IF eax != DISP_CHANGE_SUCCESSFUL
            invoke MessageBox, 0, ADDR not_full, ADDR scalp, MB_YESNO or MB_ICONEXCLAMATION
            .IF eax == IDYES
                mov fullscreen, 0
            .ELSE
                invoke MessageBox, 0, ADDR close, ADDR error, MB_OK or MB_ICONSTOP
                xor eax, eax
                ret
            .ENDIF
        .ENDIF
    .ENDIF
    .IF (fullscreen)
        mov dwExStyle, WS_EX_APPWINDOW
        mov dwStyle, WS_POPUP or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
        invoke ShowCursor, 0
    .ELSE
        mov dwExStyle, WS_EX_APPWINDOW or WS_EX_WINDOWEDGE
        mov dwStyle, WS_OVERLAPPEDWINDOW or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
    .ENDIF
    invoke AdjustWindowRectEx, ADDR rect, dwStyle, 0, dwExStyle
    mov eax, rect.left
    sub rect.right, eax
    mov eax, rect.top
    sub rect.bottom, eax
    invoke CreateWindowEx, dwExStyle, ADDR ClassName, ADDR our_title, \
                           dwStyle,0,0, rect.right, rect.bottom, 0, 0, hInstance, 0
    mov hWnd, eax
    .IF (!eax)
        invoke KillGLWindow
        invoke MessageBox, 0, ADDR create_error, ADDR error, MB_OK or MB_ICONEXCLAMATION
        xor eax, eax
        ret
    .ENDIF
    ZeroMemory &pfd, sizeof(PIXELFORMATDESCRIPTOR)
    mov pfd.nSize, sizeof PIXELFORMATDESCRIPTOR
    mov pfd.nVersion, 1
    mov pfd.dwFlags, PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER
    mov pfd.iPixelType, PFD_TYPE_RGBA
    mov eax, bits
    mov pfd.cColorBits, al
    mov pfd.cDepthBits, 16
    mov pfd.dwLayerMask, PFD_MAIN_PLANE
    invoke GetDC, hWnd
    mov hDC, eax
    .IF (!eax)
        invoke KillGLWindow
        invoke MessageBox, 0, ADDR create_device, ADDR error, MB_OK or MB_ICONEXCLAMATION
        xor eax, eax
        ret
    .ENDIF
    invoke ChoosePixelFormat, hDC, ADDR pfd
    mov PixelFormat, eax
    .IF (!eax)
        invoke KillGLWindow
        invoke MessageBox, 0, ADDR create_pixel, ADDR error, MB_OK or MB_ICONEXCLAMATION
        xor eax, eax
        ret
    .ENDIF
    invoke SetPixelFormat, hDC, PixelFormat, ADDR pfd
    .IF (!eax)
        invoke KillGLWindow
        invoke MessageBox, 0, ADDR pixel_error, ADDR error, MB_OK or MB_ICONEXCLAMATION
        xor eax, eax
        ret
    .ENDIF
    invoke wglCreateContext, hDC
    mov hRC, eax
    .IF (!eax)
        invoke KillGLWindow
        invoke MessageBox, 0, ADDR rend_error, ADDR error, MB_OK or MB_ICONEXCLAMATION
        xor eax, eax
        ret
    .ENDIF
    invoke wglMakeCurrent, hDC, hRC
    .IF (!eax)
        invoke KillGLWindow
        invoke MessageBox, 0, ADDR act_error, ADDR error, MB_OK or MB_ICONEXCLAMATION
        xor eax, eax
        ret
    .ENDIF
    invoke ShowWindow, hWnd, SW_SHOW
    invoke SetForegroundWindow, hWnd
    invoke SetFocus, hWnd
    invoke ReSizeGLScene, w, h
    invoke InitGL
    .IF (!eax)
        invoke KillGLWindow
        invoke MessageBox, 0, ADDR init_error, ADDR error, MB_OK or MB_ICONEXCLAMATION
        xor eax, eax
        ret
    .ENDIF
    mov eax, 1
    ret
CreateGLWindow endp

DrawGLScene proc                                        ;drawing proc
  ;clear screen and depth buffer
    invoke glClear, GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT
    invoke glLoadIdentity                               ;reset modelview matrix
    _glTranslatef -1.5f, 0.0f, -6.0f
    invoke glBegin, GL_TRIANGLES                        ;drawing triangles
    _glVertex3f 0.0f,1.0f,0.0f
    _glVertex3f -1.0f,-1.0f,0.0f
    _glVertex3f 1.0f,-1.0f,0.0f
    invoke glEnd
    _glTranslatef 3.0f,0.0f,0.0f
    invoke glBegin, GL_QUADS                            ;and quads
    _glVertex3f -1.0f,1.0f,0.0f
    _glVertex3f 1.0f,1.0f,0.0f
    _glVertex3f 1.0f,-1.0f,0.0f
    _glVertex3f -1.0f,-1.0f,0.0f
    invoke glEnd
    mov eax, 1
    ret
DrawGLScene endp

ReSizeGLScene proc w:DWORD, h:DWORD             ;resize scene
  LOCAL ratio:GLdouble
    .IF h == 0
        mov h, 1                                ;prevent div by zero
    .ENDIF
    invoke glViewport, 0, 0, w, h               ;reset viewport
    invoke glMatrixMode, GL_PROJECTION          ;select projection matrix
    invoke glLoadIdentity                       ;reset it
    fild w
    fild h
    fdivp st(1), st(0)
    fstp ratio
    _gluPerspective 45.0f, ratio, 0.1f,100.0f
    invoke glMatrixMode, GL_MODELVIEW                   ;select modelview matrix
    invoke glLoadIdentity                               ;reset it
    ret
ReSizeGLScene endp

KillGLWindow proc
;here we do a lot of tests, but at least, you have to do
;wglMakeCurrent, wglDeleteContext and ReleaseDC.
    .IF (fullscreen)                                ;if fullscreen mode
        invoke ChangeDisplaySettings, 0, 0          ;get back to window
        invoke ShowCursor, 1                        ;and show mouse ptr
    .ENDIF
  ;do a lot of error checking before closing
    .IF (hRC)
        invoke wglMakeCurrent, 0, 0
        .IF (!eax)
            invoke MessageBox, 0, ADDR noDC_RC, ADDR error, MB_OK or MB_ICONINFORMATION
        .ENDIF
        invoke wglDeleteContext, hRC
        .IF (!eax)
            invoke MessageBox, 0, ADDR RCrelease, ADDR error, MB_OK or MB_ICONINFORMATION
        .ENDIF
        mov hRC, 0
    .ENDIF
    invoke ReleaseDC, hWnd, hDC
    .IF (hDC && (!eax))
        invoke MessageBox, 0, ADDR DCrelease, ADDR error, MB_OK or MB_ICONINFORMATION
        mov hDC, 0
    .ENDIF
    invoke DestroyWindow, hWnd
    .IF (hWnd && (!eax))
        invoke MessageBox, 0, ADDR hWndRelease, ADDR error, MB_OK or MB_ICONINFORMATION
        mov hWnd, 0
    .ENDIF
    invoke UnregisterClass, ADDR ClassName, hInstance
    .IF (!eax)
        invoke MessageBox, 0, ADDR class_error, ADDR error, MB_OK or MB_ICONINFORMATION
    .ENDIF
    ret
KillGLWindow endp

END start


Lesson 03

; include file for Masm32 (and nasm/tasm)
; could be find at hardcode site :
; http://bizarrecreations.webjump.com

.586p
.MODEL FLAT,STDCALL
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\ogl\gl.def
include \masm32\include\ogl\glu.def
includelib \masm32\lib\opengl32.lib
includelib \masm32\lib\glu32.lib
include \masm32\include\ogl\kernel32.inc
include \masm32\include\ogl\user32.inc
include \masm32\include\ogl\gdi32.inc
include \masm32\include\ogl\WinExtra\winextra.def
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\gdi32.lib

CreateGLWindow  PROTO :DWORD,:DWORD,:DWORD,:DWORD,:BYTE
WinMain         PROTO :DWORD,:DWORD,:DWORD,:DWORD
WndProc         PROTO :DWORD,:DWORD,:DWORD,:DWORD
ReSizeGLScene   PROTO :DWORD,:DWORD
KillGLWindow    PROTO
InitGL          PROTO
DrawGLScene     PROTO

_glClearDepth MACRO t                   ;this is not defined in hardcode include files
    gl_dpush t                          ;so here it is.
    mov eax, eax
    mov ecx, ecx                        ;without this crap, it fucks up
    call glClearDepth
ENDM

_gluPerspective MACRO a,b,c,d
    gl_dpush d
    gl_dpush c
    gl_dpush b
    gl_dpush a
    mov eax, eax                        ;without this crap, it fucks up
    mov ecx, ecx
    call gluPerspective
ENDM

include include.def

.DATA
scalp           db "(scalp)",0
our_title       db "Our first beautiful colors, (s)",0
ClassName       db "asmOpenGL",0
error           db "ERROR",0
not_full        db "The requested fullscreen mode is not supported by",13,10
                db "your video card. Use windowed mode instead ?",0
close           db "Program will now close",0
start_full      db "Would you like to run in fullscreen mode ?",0

.DATA?
hDC         HDC ?
hRC         HGLRC ?
hInstance   HANDLE ?
keys        db 256 dup (?)
active      db ?
fullscreen  db ?
hWnd        dd ?

.CODE
start:
    invoke GetModuleHandle, 0
    mov hInstance, eax
    invoke WinMain, eax, 0, 0, 0
    invoke ExitProcess, eax

WinMain proc hInst:HWND, hPrevInst:UINT, CmdLine:WPARAM, CmdShow:LPARAM
  LOCAL msg:MSG, done:DWORD
    invoke MessageBox, 0, ADDR start_full, ADDR our_title, MB_YESNO or MB_ICONQUESTION
    .IF eax == IDNO
        mov fullscreen, 0
    .ELSE
        mov fullscreen, 1
    .ENDIF
    invoke CreateGLWindow, ADDR our_title, 640, 480, 16, fullscreen
    .IF (!eax)
        ret
    .ENDIF
    mov done, 0
    .while (!done)
        invoke PeekMessage, ADDR msg,NULL,0,0,PM_REMOVE
        .IF (eax)
            .IF msg.message == WM_QUIT
                mov done, 1
            .ELSE
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
            .ENDIF
        .ELSE
            invoke DrawGLScene
            .IF ((active) && (!eax)) || (keys[VK_ESCAPE])
                mov done, 1
            .ELSE
                invoke SwapBuffers, hDC
            .ENDIF
            .IF (keys[VK_F1])
                mov keys[VK_F1], 0
                invoke KillGLWindow
                .IF (fullscreen)
                    mov fullscreen, 0
                .ELSE
                    mov fullscreen, 1
                .ENDIF
                invoke CreateGLWindow, ADDR our_title, 640,480,16,fullscreen
                .IF (!eax)
                    ret
                .ENDIF
            .ENDIF
        .ENDIF
    .endw
    invoke KillGLWindow
    mov eax, msg.wParam
    ret
WinMain endp

WndProc proc hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    .IF uMsg == WM_ACTIVATE
        mov eax, wParam
        .IF (!ah)
            mov active, 1
        .ELSE
            mov active, 0
        .ENDIF
    .ELSEIF uMsg == WM_SYSCOMMAND
        .IF (wParam == SC_SCREENSAVE) || (wParam == SC_MONITORPOWER)
            xor eax, eax
            ret
        .ENDIF
    .ELSEIF uMsg == WM_KEYDOWN
        mov eax, wParam
        mov keys[eax], 1
    .ELSEIF uMsg == WM_KEYUP
        mov eax, wParam
        mov keys[eax], 0
    .ELSEIF uMsg == WM_SIZE
        mov eax, lParam
        mov ebx, eax
        and eax, 0ffffh         ;eax=width=LOWORD
        shr ebx, 16             ;ebx=height=HIWORD
        invoke ReSizeGLScene, eax, ebx
    .ELSEIF uMsg == WM_CLOSE
        invoke PostQuitMessage, 0
        xor eax, eax
        ret
    .ENDIF
    invoke DefWindowProc, hWin, uMsg, wParam, lParam
    ret
WndProc endp

InitGL proc         ;opengl setup (like color, etc...)
    invoke glShadeModel, GL_SMOOTH              ;enable smooth shading
    _glClearDepth 1.0f                          ;depth buffer setup
    invoke glEnable, GL_DEPTH_TEST              ;enable depth testing
    invoke glDepthFunc, GL_LEQUAL               ;type of depth test
  ;set really nice perspective calculations
    invoke glHint, GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST
    mov eax, 1
    ret
InitGL endp

CreateGLWindow proc letitle:DWORD,w:DWORD,h:DWORD,bits:DWORD,flag:BYTE
  LOCAL PixelFormat:DWORD, wc:WNDCLASS
  LOCAL dwExStyle:DWORD, dwStyle:DWORD
  LOCAL rect:RECT, dmScreenSettings:DEVMODE
  LOCAL pfd:PIXELFORMATDESCRIPTOR
    mov rect.left, 0
    push w
    pop rect.right
    mov rect.top, 0
    push h
    pop rect.bottom
    mov al, flag
    mov fullscreen, al
    mov wc.style, CS_HREDRAW or CS_VREDRAW or CS_OWNDC
    mov wc.lpfnWndProc, offset WndProc
    mov wc.cbClsExtra, 0
    mov wc.cbWndExtra, 0
    push hInstance
    pop wc.hInstance
    mov wc.hIcon, 0
    invoke LoadCursor, 0, IDC_ARROW
    mov wc.hCursor, eax
    mov wc.lpszMenuName, 0
    mov wc.lpszClassName, offset ClassName
    mov wc.hbrBackground, 0
    invoke RegisterClass, ADDR wc
    .IF (!eax)
        xor eax, eax
        ret
    .ENDIF
    .IF (fullscreen)
        ZeroMemory &dmScreenSettings, sizeof(dmScreenSettings)
        mov dmScreenSettings.dmSize, sizeof dmScreenSettings
        push w
        pop dmScreenSettings.dmPelsWidth
        push h
        pop dmScreenSettings.dmPelsHeight
        push bits
        pop dmScreenSettings.dmBitsPerPel
        mov dmScreenSettings.dmFields, DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT
        invoke ChangeDisplaySettings, ADDR dmScreenSettings, CDS_FULLSCREEN
        .IF eax != DISP_CHANGE_SUCCESSFUL
            invoke MessageBox, 0, ADDR not_full, ADDR scalp, MB_YESNO or MB_ICONEXCLAMATION
            .IF eax == IDYES
                mov fullscreen, 0
            .ELSE
                invoke MessageBox, 0, ADDR close, ADDR error, MB_OK or MB_ICONSTOP
                xor eax, eax
                ret
            .ENDIF
        .ENDIF
    .ENDIF
    .IF (fullscreen)
        mov dwExStyle, WS_EX_APPWINDOW
        mov dwStyle, WS_POPUP or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
        invoke ShowCursor, 0
    .ELSE
        mov dwExStyle, WS_EX_APPWINDOW or WS_EX_WINDOWEDGE
        mov dwStyle, WS_OVERLAPPEDWINDOW or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
    .ENDIF
    invoke AdjustWindowRectEx, ADDR rect, dwStyle, 0, dwExStyle
    mov eax, rect.left
    sub rect.right, eax
    mov eax, rect.top
    sub rect.bottom, eax
    invoke CreateWindowEx, dwExStyle, ADDR ClassName, ADDR our_title, \
                           dwStyle,0,0, rect.right, rect.bottom, 0, 0, hInstance, 0
    mov hWnd, eax
    ZeroMemory &pfd, sizeof(PIXELFORMATDESCRIPTOR)
    mov pfd.nSize, sizeof PIXELFORMATDESCRIPTOR
    mov pfd.nVersion, 1
    mov pfd.dwFlags, PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER
    mov pfd.iPixelType, PFD_TYPE_RGBA
    mov eax, bits
    mov pfd.cColorBits, al
    mov pfd.cDepthBits, 16
    mov pfd.dwLayerMask, PFD_MAIN_PLANE
    invoke GetDC, hWnd
    mov hDC, eax
    invoke ChoosePixelFormat, hDC, ADDR pfd
    mov PixelFormat, eax
    invoke SetPixelFormat, hDC, PixelFormat, ADDR pfd
    invoke wglCreateContext, hDC
    mov hRC, eax
    invoke wglMakeCurrent, hDC, hRC
    invoke ShowWindow, hWnd, SW_SHOW
    invoke SetForegroundWindow, hWnd
    invoke SetFocus, hWnd
    invoke ReSizeGLScene, w, h
    invoke InitGL
    mov eax, 1
    ret
CreateGLWindow endp

DrawGLScene proc                                        ;drawing proc
  ;clear screen and depth buffer
    invoke glClear, GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT
    invoke glLoadIdentity                               ;reset modelview matrix
    _glTranslatef -1.5f, 0.0f, -6.0f
    invoke glBegin, GL_TRIANGLES                        ;drawing triangles
    _glColor3f 1.0f,0.0f,0.0f
    _glVertex3f 0.0f,1.0f,0.0f
    _glColor3f 0.0f,1.0f,0.0f
    _glVertex3f -1.0f,-1.0f,0.0f
    _glColor3f 0.0f,0.0f,1.0f
    _glVertex3f 1.0f,-1.0f,0.0f
    invoke glEnd
    _glTranslatef 3.0f,0.0f,0.0f
    _glColor3f 0.5f,0.5f,1.0f
    invoke glBegin, GL_QUADS                            ;and quads
    _glVertex3f -1.0f,1.0f,0.0f
    _glVertex3f 1.0f,1.0f,0.0f
    _glVertex3f 1.0f,-1.0f,0.0f
    _glVertex3f -1.0f,-1.0f,0.0f
    invoke glEnd
    mov eax, 1
    ret
DrawGLScene endp

ReSizeGLScene proc w:DWORD, h:DWORD             ;resize scene
  LOCAL ratio:GLdouble
    .IF h == 0
        mov h, 1                                ;prevent div by zero
    .ENDIF
    invoke glViewport, 0, 0, w, h               ;reset viewport
    invoke glMatrixMode, GL_PROJECTION          ;select projection matrix
    invoke glLoadIdentity                       ;reset it
    fild w
    fild h
    fdivp st(1), st(0)
    fstp ratio
    _gluPerspective 45.0f, ratio, 0.1f,100.0f
    invoke glMatrixMode, GL_MODELVIEW                   ;select modelview matrix
    invoke glLoadIdentity                               ;reset it
    ret
ReSizeGLScene endp

KillGLWindow proc
    .IF (fullscreen)                                ;if fullscreen mode
        invoke ChangeDisplaySettings, 0, 0          ;get back to window
        invoke ShowCursor, 1                        ;and show mouse ptr
    .ENDIF
    .IF (hRC)
        invoke wglMakeCurrent, 0, 0
        invoke wglDeleteContext, hRC
        mov hRC, 0
    .ENDIF
    invoke ReleaseDC, hWnd, hDC
    invoke DestroyWindow, hWnd
    invoke UnregisterClass, ADDR ClassName, hInstance
    ret
KillGLWindow endp

END start


Lesson 04

; include file for Masm32 (and nasm/tasm)
; could be find at hardcode site :
; http://bizarrecreations.webjump.com

.586p
.MODEL FLAT,STDCALL
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\ogl\gl.def
include \masm32\include\ogl\glu.def
includelib \masm32\lib\opengl32.lib
includelib \masm32\lib\glu32.lib
include \masm32\include\ogl\kernel32.inc
include \masm32\include\ogl\user32.inc
include \masm32\include\ogl\gdi32.inc
include \masm32\include\ogl\WinExtra\winextra.def
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\gdi32.lib

CreateGLWindow  PROTO :DWORD,:DWORD,:DWORD,:DWORD,:BYTE
WinMain         PROTO :DWORD,:DWORD,:DWORD,:DWORD
WndProc         PROTO :DWORD,:DWORD,:DWORD,:DWORD
ReSizeGLScene   PROTO :DWORD,:DWORD
KillGLWindow    PROTO
InitGL          PROTO
DrawGLScene     PROTO

_glClearDepth MACRO t                   ;this is not defined in hardcode include files
    gl_dpush t                          ;so here it is.
    mov eax, eax
    mov ebx, ebx
    call glClearDepth
ENDM

_gluPerspective MACRO a,b,c,d
    gl_dpush d
    gl_dpush c
    gl_dpush b
    gl_dpush a
    mov eax, eax
    mov ebx, ebx
    call gluPerspective
ENDM

include include.def

.DATA
t_rot_speed     GLfloat 0.7
q_rot_speed     GLfloat 0.5
scalp           db "(scalp)",0
our_title       db "Our first animation, (s)",0
ClassName       db "asmOpenGL",0
error           db "ERROR",0
not_full        db "The requested fullscreen mode is not supported by",13,10
                db "your video card. Use windowed mode instead ?",0
close           db "Program will now close",0
start_full      db "Would you like to run in fullscreen mode ?",0

.DATA?
hDC         HDC ?
hRC         HGLRC ?
hInstance   HANDLE ?
keys        db 256 dup (?)
active      db ?
fullscreen  db ?
hWnd        dd ?
rtri        GLfloat ?
rquad       GLfloat ?

.CODE
start:
    invoke GetModuleHandle, 0
    mov hInstance, eax
    invoke WinMain, eax, 0, 0, 0
    invoke ExitProcess, eax

WinMain proc hInst:HWND, hPrevInst:UINT, CmdLine:WPARAM, CmdShow:LPARAM
  LOCAL msg:MSG, done:DWORD
    invoke MessageBox, 0, ADDR start_full, ADDR our_title, MB_YESNO or MB_ICONQUESTION
    .IF eax == IDNO
        mov fullscreen, 0
    .ELSE
        mov fullscreen, 1
    .ENDIF
    invoke CreateGLWindow, ADDR our_title, 640, 480, 16, fullscreen
    .IF (!eax)
        ret
    .ENDIF
    mov done, 0
    .while (!done)
        invoke PeekMessage, ADDR msg,NULL,0,0,PM_REMOVE
        .IF (eax)
            .IF msg.message == WM_QUIT
                mov done, 1
            .ELSE
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
            .ENDIF
        .ELSE
            invoke DrawGLScene
            .IF ((active) && (!eax)) || (keys[VK_ESCAPE])
                mov done, 1
            .ELSE
                invoke SwapBuffers, hDC
            .ENDIF
            .IF (keys[VK_F1])
                mov keys[VK_F1], 0
                invoke KillGLWindow
                .IF (fullscreen)
                    mov fullscreen, 0
                .ELSE
                    mov fullscreen, 1
                .ENDIF
                invoke CreateGLWindow, ADDR our_title, 640,480,16,fullscreen
                .IF (!eax)
                    ret
                .ENDIF
            .ENDIF
        .ENDIF
    .endw
    invoke KillGLWindow
    mov eax, msg.wParam
    ret
WinMain endp

WndProc proc hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    .IF uMsg == WM_ACTIVATE
        mov eax, wParam
        .IF (!ah)
            mov active, 1
        .ELSE
            mov active, 0
        .ENDIF
    .ELSEIF uMsg == WM_SYSCOMMAND
        .IF (wParam == SC_SCREENSAVE) || (wParam == SC_MONITORPOWER)
            xor eax, eax
            ret
        .ENDIF
    .ELSEIF uMsg == WM_KEYDOWN
        mov eax, wParam
        mov keys[eax], 1
    .ELSEIF uMsg == WM_KEYUP
        mov eax, wParam
        mov keys[eax], 0
    .ELSEIF uMsg == WM_SIZE
        mov eax, lParam
        mov ebx, eax
        and eax, 0ffffh         ;eax=width=LOWORD
        shr ebx, 16             ;ebx=height=HIWORD
        invoke ReSizeGLScene, eax, ebx
    .ELSEIF uMsg == WM_CLOSE
        invoke PostQuitMessage, 0
        xor eax, eax
        ret
    .ENDIF
    invoke DefWindowProc, hWin, uMsg, wParam, lParam
    ret
WndProc endp

InitGL proc         ;opengl setup (like color, etc...)
    invoke glShadeModel, GL_SMOOTH              ;enable smooth shading
    _glClearDepth 1.0f                          ;depth buffer setup
    invoke glEnable, GL_DEPTH_TEST              ;enable depth testing
    invoke glDepthFunc, GL_LEQUAL               ;type of depth test
  ;set really nice perspective calculations
    invoke glHint, GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST
    mov eax, 1
    ret
InitGL endp

CreateGLWindow proc letitle:DWORD,w:DWORD,h:DWORD,bits:DWORD,flag:BYTE
  LOCAL PixelFormat:DWORD, wc:WNDCLASS
  LOCAL dwExStyle:DWORD, dwStyle:DWORD
  LOCAL rect:RECT, dmScreenSettings:DEVMODE
  LOCAL pfd:PIXELFORMATDESCRIPTOR
    mov rect.left, 0
    push w
    pop rect.right
    mov rect.top, 0
    push h
    pop rect.bottom
    mov al, flag
    mov fullscreen, al
    mov wc.style, CS_HREDRAW or CS_VREDRAW or CS_OWNDC
    mov wc.lpfnWndProc, offset WndProc
    mov wc.cbClsExtra, 0
    mov wc.cbWndExtra, 0
    push hInstance
    pop wc.hInstance
    mov wc.hIcon, 0
    invoke LoadCursor, 0, IDC_ARROW
    mov wc.hCursor, eax
    mov wc.lpszMenuName, 0
    mov wc.lpszClassName, offset ClassName
    mov wc.hbrBackground, 0
    invoke RegisterClass, ADDR wc
    .IF (!eax)
        xor eax, eax
        ret
    .ENDIF
    .IF (fullscreen)
        ZeroMemory &dmScreenSettings, sizeof(dmScreenSettings)
        mov dmScreenSettings.dmSize, sizeof dmScreenSettings
        push w
        pop dmScreenSettings.dmPelsWidth
        push h
        pop dmScreenSettings.dmPelsHeight
        push bits
        pop dmScreenSettings.dmBitsPerPel
        mov dmScreenSettings.dmFields, DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT
        invoke ChangeDisplaySettings, ADDR dmScreenSettings, CDS_FULLSCREEN
        .IF eax != DISP_CHANGE_SUCCESSFUL
            invoke MessageBox, 0, ADDR not_full, ADDR scalp, MB_YESNO or MB_ICONEXCLAMATION
            .IF eax == IDYES
                mov fullscreen, 0
            .ELSE
                invoke MessageBox, 0, ADDR close, ADDR error, MB_OK or MB_ICONSTOP
                xor eax, eax
                ret
            .ENDIF
        .ENDIF
    .ENDIF
    .IF (fullscreen)
        mov dwExStyle, WS_EX_APPWINDOW
        mov dwStyle, WS_POPUP or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
        invoke ShowCursor, 0
    .ELSE
        mov dwExStyle, WS_EX_APPWINDOW or WS_EX_WINDOWEDGE
        mov dwStyle, WS_OVERLAPPEDWINDOW or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
    .ENDIF
    invoke AdjustWindowRectEx, ADDR rect, dwStyle, 0, dwExStyle
    mov eax, rect.left
    sub rect.right, eax
    mov eax, rect.top
    sub rect.bottom, eax
    invoke CreateWindowEx, dwExStyle, ADDR ClassName, ADDR our_title, \
                           dwStyle,0,0, rect.right, rect.bottom, 0, 0, hInstance, 0
    mov hWnd, eax
    ZeroMemory &pfd, sizeof(PIXELFORMATDESCRIPTOR)
    mov pfd.nSize, sizeof PIXELFORMATDESCRIPTOR
    mov pfd.nVersion, 1
    mov pfd.dwFlags, PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER
    mov pfd.iPixelType, PFD_TYPE_RGBA
    mov eax, bits
    mov pfd.cColorBits, al
    mov pfd.cDepthBits, 16
    mov pfd.dwLayerMask, PFD_MAIN_PLANE
    invoke GetDC, hWnd
    mov hDC, eax
    invoke ChoosePixelFormat, hDC, ADDR pfd
    mov PixelFormat, eax
    invoke SetPixelFormat, hDC, PixelFormat, ADDR pfd
    invoke wglCreateContext, hDC
    mov hRC, eax
    invoke wglMakeCurrent, hDC, hRC
    invoke ShowWindow, hWnd, SW_SHOW
    invoke SetForegroundWindow, hWnd
    invoke SetFocus, hWnd
    invoke ReSizeGLScene, w, h
    invoke InitGL
    mov eax, 1
    ret
CreateGLWindow endp

DrawGLScene proc                                        ;drawing proc
  ;clear screen and depth buffer
    invoke glClear, GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT
    invoke glLoadIdentity                               ;reset modelview matrix
    _glTranslatef -1.5f, 0.0f, -6.0f
    _glRotatef rtri, 0.0f,1.0f,0.0f
    invoke glBegin, GL_TRIANGLES
    _glColor3f 1.0f,0.0f,0.0f
    _glVertex3f 0.0f,1.0f,0.0f
    _glColor3f 0.0f,1.0f,0.0f
    _glVertex3f -1.0f,-1.0f,0.0f
    _glColor3f 0.0f,0.0f,1.0f
    _glVertex3f 1.0f,-1.0f,0.0f
    invoke glEnd
    invoke glLoadIdentity
    _glTranslatef 1.5f,0.0f,-6.0f
    _glRotatef rquad,1.5f,0.0f,0.0f
    _glColor3f 0.5f,0.5f,1.0f
    invoke glBegin, GL_QUADS
    _glVertex3f -1.0f,1.0f,0.0f
    _glVertex3f 1.0f,1.0f,0.0f
    _glVertex3f 1.0f,-1.0f,0.0f
    _glVertex3f -1.0f,-1.0f,0.0f
    invoke glEnd
    fld rtri
    fadd t_rot_speed
    fstp rtri
    fld rquad
    fsub q_rot_speed
    fstp rquad
    mov eax, 1
    ret
DrawGLScene endp

ReSizeGLScene proc w:DWORD, h:DWORD             ;resize scene
  LOCAL ratio:GLdouble
    .IF h == 0
        mov h, 1                                ;prevent div by zero
    .ENDIF
    invoke glViewport, 0, 0, w, h               ;reset viewport
    invoke glMatrixMode, GL_PROJECTION          ;select projection matrix
    invoke glLoadIdentity                       ;reset it
    fild w
    fild h
    fdivp st(1), st(0)
    fstp ratio
    _gluPerspective 45.0f, ratio, 0.1f,100.0f
    invoke glMatrixMode, GL_MODELVIEW                   ;select modelview matrix
    invoke glLoadIdentity                               ;reset it
    ret
ReSizeGLScene endp

KillGLWindow proc
    .IF (fullscreen)                                ;if fullscreen mode
        invoke ChangeDisplaySettings, 0, 0          ;get back to window
        invoke ShowCursor, 1                        ;and show mouse ptr
    .ENDIF
    .IF (hRC)
        invoke wglMakeCurrent, 0, 0
        invoke wglDeleteContext, hRC
        mov hRC, 0
    .ENDIF
    invoke ReleaseDC, hWnd, hDC
    invoke DestroyWindow, hWnd
    invoke UnregisterClass, ADDR ClassName, hInstance
    ret
KillGLWindow endp

END start


Lesson 05

; include file for Masm32 (and nasm/tasm)
; could be find at hardcode site :
; http://bizarrecreations.webjump.com

.586p
.MODEL FLAT,STDCALL
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\ogl\gl.def
include \masm32\include\ogl\glu.def
includelib \masm32\lib\opengl32.lib
includelib \masm32\lib\glu32.lib
include \masm32\include\ogl\kernel32.inc
include \masm32\include\ogl\user32.inc
include \masm32\include\ogl\gdi32.inc
include \masm32\include\ogl\WinExtra\winextra.def
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\gdi32.lib

CreateGLWindow  PROTO :DWORD,:DWORD,:DWORD,:DWORD,:BYTE
WinMain         PROTO :DWORD,:DWORD,:DWORD,:DWORD
WndProc         PROTO :DWORD,:DWORD,:DWORD,:DWORD
ReSizeGLScene   PROTO :DWORD,:DWORD
KillGLWindow    PROTO
InitGL          PROTO
DrawGLScene     PROTO

_glClearDepth MACRO t                   ;this is not defined in hardcode include files
    gl_dpush t                          ;so here it is.
    mov eax, eax
    mov ebx, ebx
    call glClearDepth
ENDM

_gluPerspective MACRO a,b,c,d
    gl_dpush d
    gl_dpush c
    gl_dpush b
    gl_dpush a
    mov eax, eax
    mov ebx, ebx
    call gluPerspective
ENDM

include include.def

.DATA
t_rot_speed     GLfloat 0.7
q_rot_speed     GLfloat 0.5
scalp           db "(scalp)",0
our_title       db "Here it is : real 3D, (s)",0
ClassName       db "asmOpenGL",0
error           db "ERROR",0
not_full        db "The requested fullscreen mode is not supported by",13,10
                db "your video card. Use windowed mode instead ?",0
close           db "Program will now close",0
start_full      db "Would you like to run in fullscreen mode ?",0

.DATA?
hDC         HDC ?
hRC         HGLRC ?
hInstance   HANDLE ?
keys        db 256 dup (?)
active      db ?
fullscreen  db ?
hWnd        dd ?
rtri        GLfloat ?
rquad       GLfloat ?

.CODE
start:
    invoke GetModuleHandle, 0
    mov hInstance, eax
    invoke WinMain, eax, 0, 0, 0
    invoke ExitProcess, eax

WinMain proc hInst:HWND, hPrevInst:UINT, CmdLine:WPARAM, CmdShow:LPARAM
  LOCAL msg:MSG, done:DWORD
    invoke MessageBox, 0, ADDR start_full, ADDR our_title, MB_YESNO or MB_ICONQUESTION
    .IF eax == IDNO
        mov fullscreen, 0
    .ELSE
        mov fullscreen, 1
    .ENDIF
    invoke CreateGLWindow, ADDR our_title, 640, 480, 16, fullscreen
    .IF (!eax)
        ret
    .ENDIF
    mov done, 0
    .while (!done)
        invoke PeekMessage, ADDR msg,NULL,0,0,PM_REMOVE
        .IF (eax)
            .IF msg.message == WM_QUIT
                mov done, 1
            .ELSE
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
            .ENDIF
        .ELSE
            invoke DrawGLScene
            .IF ((active) && (!eax)) || (keys[VK_ESCAPE])
                mov done, 1
            .ELSE
                invoke SwapBuffers, hDC
            .ENDIF
            .IF (keys[VK_F1])
                mov keys[VK_F1], 0
                invoke KillGLWindow
                .IF (fullscreen)
                    mov fullscreen, 0
                .ELSE
                    mov fullscreen, 1
                .ENDIF
                invoke CreateGLWindow, ADDR our_title, 640,480,16,fullscreen
                .IF (!eax)
                    ret
                .ENDIF
            .ENDIF
        .ENDIF
    .endw
    invoke KillGLWindow
    mov eax, msg.wParam
    ret
WinMain endp

WndProc proc hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    .IF uMsg == WM_ACTIVATE
        mov eax, wParam
        .IF (!ah)
            mov active, 1
        .ELSE
            mov active, 0
        .ENDIF
    .ELSEIF uMsg == WM_SYSCOMMAND
        .IF (wParam == SC_SCREENSAVE) || (wParam == SC_MONITORPOWER)
            xor eax, eax
            ret
        .ENDIF
    .ELSEIF uMsg == WM_KEYDOWN
        mov eax, wParam
        mov keys[eax], 1
    .ELSEIF uMsg == WM_KEYUP
        mov eax, wParam
        mov keys[eax], 0
    .ELSEIF uMsg == WM_SIZE
        mov eax, lParam
        mov ebx, eax
        and eax, 0ffffh         ;eax=width=LOWORD
        shr ebx, 16             ;ebx=height=HIWORD
        invoke ReSizeGLScene, eax, ebx
    .ELSEIF uMsg == WM_CLOSE
        invoke PostQuitMessage, 0
        xor eax, eax
        ret
    .ENDIF
    invoke DefWindowProc, hWin, uMsg, wParam, lParam
    ret
WndProc endp

InitGL proc         ;opengl setup (like color, etc...)
    invoke glShadeModel, GL_SMOOTH              ;enable smooth shading
    _glClearDepth 1.0f                          ;depth buffer setup
    invoke glEnable, GL_DEPTH_TEST              ;enable depth testing
    invoke glDepthFunc, GL_LEQUAL               ;type of depth test
  ;set really nice perspective calculations
    invoke glHint, GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST
    mov eax, 1
    ret
InitGL endp

CreateGLWindow proc letitle:DWORD,w:DWORD,h:DWORD,bits:DWORD,flag:BYTE
  LOCAL PixelFormat:DWORD, wc:WNDCLASS
  LOCAL dwExStyle:DWORD, dwStyle:DWORD
  LOCAL rect:RECT, dmScreenSettings:DEVMODE
  LOCAL pfd:PIXELFORMATDESCRIPTOR
    mov rect.left, 0
    push w
    pop rect.right
    mov rect.top, 0
    push h
    pop rect.bottom
    mov al, flag
    mov fullscreen, al
    mov wc.style, CS_HREDRAW or CS_VREDRAW or CS_OWNDC
    mov wc.lpfnWndProc, offset WndProc
    mov wc.cbClsExtra, 0
    mov wc.cbWndExtra, 0
    push hInstance
    pop wc.hInstance
    mov wc.hIcon, 0
    invoke LoadCursor, 0, IDC_ARROW
    mov wc.hCursor, eax
    mov wc.lpszMenuName, 0
    mov wc.lpszClassName, offset ClassName
    mov wc.hbrBackground, 0
    invoke RegisterClass, ADDR wc
    .IF (!eax)
        xor eax, eax
        ret
    .ENDIF
    .IF (fullscreen)
        ZeroMemory &dmScreenSettings, sizeof(dmScreenSettings)
        mov dmScreenSettings.dmSize, sizeof dmScreenSettings
        push w
        pop dmScreenSettings.dmPelsWidth
        push h
        pop dmScreenSettings.dmPelsHeight
        push bits
        pop dmScreenSettings.dmBitsPerPel
        mov dmScreenSettings.dmFields, DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT
        invoke ChangeDisplaySettings, ADDR dmScreenSettings, CDS_FULLSCREEN
        .IF eax != DISP_CHANGE_SUCCESSFUL
            invoke MessageBox, 0, ADDR not_full, ADDR scalp, MB_YESNO or MB_ICONEXCLAMATION
            .IF eax == IDYES
                mov fullscreen, 0
            .ELSE
                invoke MessageBox, 0, ADDR close, ADDR error, MB_OK or MB_ICONSTOP
                xor eax, eax
                ret
            .ENDIF
        .ENDIF
    .ENDIF
    .IF (fullscreen)
        mov dwExStyle, WS_EX_APPWINDOW
        mov dwStyle, WS_POPUP or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
        invoke ShowCursor, 0
    .ELSE
        mov dwExStyle, WS_EX_APPWINDOW or WS_EX_WINDOWEDGE
        mov dwStyle, WS_OVERLAPPEDWINDOW or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
    .ENDIF
    invoke AdjustWindowRectEx, ADDR rect, dwStyle, 0, dwExStyle
    mov eax, rect.left
    sub rect.right, eax
    mov eax, rect.top
    sub rect.bottom, eax
    invoke CreateWindowEx, dwExStyle, ADDR ClassName, ADDR our_title, \
                           dwStyle,0,0, rect.right, rect.bottom, 0, 0, hInstance, 0
    mov hWnd, eax
    ZeroMemory &pfd, sizeof(PIXELFORMATDESCRIPTOR)
    mov pfd.nSize, sizeof PIXELFORMATDESCRIPTOR
    mov pfd.nVersion, 1
    mov pfd.dwFlags, PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER
    mov pfd.iPixelType, PFD_TYPE_RGBA
    mov eax, bits
    mov pfd.cColorBits, al
    mov pfd.cDepthBits, 16
    mov pfd.dwLayerMask, PFD_MAIN_PLANE
    invoke GetDC, hWnd
    mov hDC, eax
    invoke ChoosePixelFormat, hDC, ADDR pfd
    mov PixelFormat, eax
    invoke SetPixelFormat, hDC, PixelFormat, ADDR pfd
    invoke wglCreateContext, hDC
    mov hRC, eax
    invoke wglMakeCurrent, hDC, hRC
    invoke ShowWindow, hWnd, SW_SHOW
    invoke SetForegroundWindow, hWnd
    invoke SetFocus, hWnd
    invoke ReSizeGLScene, w, h
    invoke InitGL
    mov eax, 1
    ret
CreateGLWindow endp

ReSizeGLScene proc w:DWORD, h:DWORD             ;resize scene
  LOCAL ratio:GLdouble
    .IF h == 0
        mov h, 1                                ;prevent div by zero
    .ENDIF
    invoke glViewport, 0, 0, w, h               ;reset viewport
    invoke glMatrixMode, GL_PROJECTION          ;select projection matrix
    invoke glLoadIdentity                       ;reset it
    fild w
    fild h
    fdivp st(1), st(0)
    fstp ratio
    _gluPerspective 45.0f, ratio, 0.1f,100.0f
    invoke glMatrixMode, GL_MODELVIEW                   ;select modelview matrix
    invoke glLoadIdentity                               ;reset it
    ret
ReSizeGLScene endp

KillGLWindow proc
    .IF (fullscreen)                                ;if fullscreen mode
        invoke ChangeDisplaySettings, 0, 0          ;get back to window
        invoke ShowCursor, 1                        ;and show mouse ptr
    .ENDIF
    .IF (hRC)
        invoke wglMakeCurrent, 0, 0
        invoke wglDeleteContext, hRC
        mov hRC, 0
    .ENDIF
    invoke ReleaseDC, hWnd, hDC
    invoke DestroyWindow, hWnd
    invoke UnregisterClass, ADDR ClassName, hInstance
    ret
KillGLWindow endp

DrawGLScene proc                                        ;drawing proc
  ;clear screen and depth buffer
    invoke glClear, GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT
    invoke glLoadIdentity                               ;reset modelview matrix
    _glTranslatef -1.5f, 0.0f, -6.0f
    _glRotatef rtri, 0.0f,1.0f,0.0f
    invoke glBegin, GL_TRIANGLES

    _glColor3f   1.0f, 0.0f, 0.0f
    _glVertex3f  0.0f, 1.0f, 0.0f
    _glColor3f   0.0f, 1.0f, 0.0f
    _glVertex3f -1.0f,-1.0f, 1.0f
    _glColor3f   0.0f, 0.0f, 1.0f
    _glVertex3f  1.0f,-1.0f, 1.0f

    _glColor3f   1.0f, 0.0f, 0.0f
    _glVertex3f  0.0f, 1.0f, 0.0f
    _glColor3f   0.0f, 0.0f, 1.0f
    _glVertex3f  1.0f,-1.0f, 1.0f
    _glColor3f   0.0f, 1.0f, 0.0f
    _glVertex3f  1.0f,-1.0f,-1.0f

    _glColor3f   1.0f, 0.0f, 0.0f
    _glVertex3f  0.0f, 1.0f, 0.0f
    _glColor3f   0.0f, 1.0f, 0.0f
    _glVertex3f  1.0f,-1.0f,-1.0f
    _glColor3f   0.0f, 0.0f, 1.0f
    _glVertex3f -1.0f,-1.0f,-1.0f

    _glColor3f   1.0f, 0.0f, 0.0f
    _glVertex3f  0.0f, 1.0f, 0.0f
    _glColor3f   0.0f, 0.0f, 1.0f
    _glVertex3f -1.0f,-1.0f,-1.0f
    _glColor3f   0.0f, 1.0f, 0.0f
    _glVertex3f -1.0f,-1.0f, 1.0f

    invoke glEnd
    invoke glLoadIdentity
    _glTranslatef 1.5f,0.0f,-7.0f
    _glRotatef rquad,1.0f,1.0f,1.0f
    invoke glBegin, GL_QUADS

    _glColor3f   0.0f, 1.0f, 0.0f
    _glVertex3f  1.0f, 1.0f,-1.0f
    _glVertex3f -1.0f, 1.0f,-1.0f
    _glVertex3f -1.0f, 1.0f, 1.0f
    _glVertex3f  1.0f, 1.0f, 1.0f

    _glColor3f   1.0f, 0.5f, 0.0f
    _glVertex3f  1.0f,-1.0f, 1.0f
    _glVertex3f -1.0f,-1.0f, 1.0f
    _glVertex3f -1.0f,-1.0f,-1.0f
    _glVertex3f  1.0f,-1.0f,-1.0f

    _glColor3f   1.0f, 0.0f, 0.0f
    _glVertex3f  1.0f, 1.0f, 1.0f
    _glVertex3f -1.0f, 1.0f, 1.0f
    _glVertex3f -1.0f,-1.0f, 1.0f
    _glVertex3f  1.0f,-1.0f, 1.0f

    _glColor3f   1.0f, 1.0f, 0.0f
    _glVertex3f  1.0f,-1.0f,-1.0f
    _glVertex3f -1.0f,-1.0f,-1.0f
    _glVertex3f -1.0f, 1.0f,-1.0f
    _glVertex3f  1.0f, 1.0f,-1.0f

    _glColor3f   0.0f, 0.0f, 1.0f
    _glVertex3f -1.0f, 1.0f, 1.0f
    _glVertex3f -1.0f, 1.0f,-1.0f
    _glVertex3f -1.0f,-1.0f,-1.0f
    _glVertex3f -1.0f,-1.0f, 1.0f

    _glColor3f   1.0f, 0.0f, 1.0f
    _glVertex3f  1.0f, 1.0f,-1.0f
    _glVertex3f  1.0f, 1.0f, 1.0f
    _glVertex3f  1.0f,-1.0f, 1.0f
    _glVertex3f  1.0f,-1.0f,-1.0f

    invoke glEnd
    fld rtri
    fadd t_rot_speed
    fstp rtri
    fld rquad
    fsub q_rot_speed
    fstp rquad
    mov eax, 1
    ret
DrawGLScene endp

END start


Lesson 06

; include file for Masm32 (and nasm/tasm)
; could be find at hardcode site :
; http://bizarrecreations.webjump.com

.586p
.MODEL FLAT,STDCALL
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\ogl\gl.def
include \masm32\include\ogl\glu.def
include \masm32\include\ogl\kernel32.inc
include \masm32\include\ogl\user32.inc
include \masm32\include\ogl\gdi32.inc

include \masm32\include\ogl\WinExtra\winextra.def

includelib \masm32\lib\opengl32.lib
includelib \masm32\lib\glu32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\gdi32.lib

CreateGLWindow  PROTO :DWORD,:DWORD,:DWORD,:DWORD,:BYTE
WinMain         PROTO :DWORD,:DWORD,:DWORD,:DWORD
WndProc         PROTO :DWORD,:DWORD,:DWORD,:DWORD
ReSizeGLScene   PROTO :DWORD,:DWORD
KillGLWindow    PROTO
InitGL          PROTO
DrawGLScene     PROTO
LoadGLTexture   PROTO

include include.def
IDD_BITMAP      equ 100

.DATA
rot_speed       GLfloat 1.4
scalp           db "(scalp)",0
our_title       db "Texture mapping, (s)",0
ClassName       db "asmOpenGL",0
error_str       db "ERROR",0
not_full        db "The requested fullscreen mode is not supported by",13,10
                db "your video card. Use windowed mode instead ?",0
close           db "Program will now close",0
start_full      db "Would you like to run in fullscreen mode ?",0

.data?
hDC             HDC ?
hRC             HGLRC ?
hInstance       HANDLE ?
keys            db 256 dup (?)
active          db ?
fullscreen      db ?
hWnd            dd ?
texture         GLuint  ?
xrot            GLfloat ?
yrot            GLfloat ?
zrot            GLfloat ?

.CODE
start:
    invoke GetModuleHandle, 0
    mov hInstance, eax
    invoke WinMain, eax, 0, 0, 0
    invoke ExitProcess, eax

WinMain proc hInst:HWND, hPrevInst:UINT, CmdLine:WPARAM, CmdShow:LPARAM
  LOCAL msg:MSG, done:DWORD
    invoke MessageBox, 0, ADDR start_full, ADDR our_title, MB_YESNO or MB_ICONQUESTION
    .IF eax == IDNO
        mov fullscreen, 0
    .ELSE
        mov fullscreen, 1
    .ENDIF
    invoke CreateGLWindow, ADDR our_title, 640, 480, 16, fullscreen
    .IF (!eax)
        ret
    .ENDIF
    mov done, 0
    .while (!done)
        invoke PeekMessage, ADDR msg,0,0,0,PM_REMOVE
        .IF (eax)
            .IF msg.message == WM_QUIT
                mov done, 1
            .ELSE
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
            .ENDIF
        .ELSE
            invoke DrawGLScene
            .IF ((active) && (!eax)) || (keys[VK_ESCAPE])
                mov done, 1
            .ELSE
                invoke SwapBuffers, hDC
            .ENDIF
            .IF (keys[VK_F1])
                mov keys[VK_F1], 0
                invoke KillGLWindow
                .IF (fullscreen)
                    mov fullscreen, 0
                .ELSE
                    mov fullscreen, 1
                .ENDIF
                invoke CreateGLWindow, ADDR our_title, 640,480,16,fullscreen
                .IF (!eax)
                    ret
                .ENDIF
            .ENDIF
        .ENDIF
    .endw
    invoke KillGLWindow
    mov eax, msg.wParam
    ret
WinMain endp

WndProc proc hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    .IF uMsg == WM_ACTIVATE
        mov eax, wParam
        .IF (!ah)
            mov active, 1
        .ELSE
            mov active, 0
        .ENDIF
    .ELSEIF uMsg == WM_SYSCOMMAND
        .IF (wParam == SC_SCREENSAVE) || (wParam == SC_MONITORPOWER)
            xor eax, eax
            ret
        .ENDIF
    .ELSEIF uMsg == WM_KEYDOWN
        mov eax, wParam
        mov keys[eax], 1
    .ELSEIF uMsg == WM_KEYUP
        mov eax, wParam
        mov keys[eax], 0
    .ELSEIF uMsg == WM_SIZE
        mov eax, lParam
        mov ebx, eax
        and eax, 0ffffh         ;eax=width=LOWORD
        shr ebx, 16             ;ebx=height=HIWORD
        invoke ReSizeGLScene, eax, ebx
    .ELSEIF uMsg == WM_CLOSE
        invoke PostQuitMessage, 0
        xor eax, eax
        ret
    .ENDIF
    invoke DefWindowProc, hWin, uMsg, wParam, lParam
    ret
WndProc endp

InitGL proc
    invoke LoadGLTexture
    cmp eax, 0
    je end_init
    invoke glEnable, GL_TEXTURE_2D
    invoke glShadeModel, GL_SMOOTH
    _glClearDepth 1.0f
    invoke glEnable, GL_DEPTH_TEST
    invoke glDepthFunc, GL_LEQUAL
  ;set really nice perspective calculations
    invoke glHint, GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST
    mov eax, 1
   end_init:
    ret
InitGL endp

CreateGLWindow proc letitle:DWORD,w:DWORD,h:DWORD,bits:DWORD,flag:BYTE
  LOCAL PixelFormat:DWORD, wc:WNDCLASS
  LOCAL dwExStyle:DWORD, dwStyle:DWORD
  LOCAL rect:RECT, dmScreenSettings:DEVMODE
  LOCAL pfd:PIXELFORMATDESCRIPTOR
    mov rect.left, 0
    push w
    pop rect.right
    mov rect.top, 0
    push h
    pop rect.bottom
    mov al, flag
    mov fullscreen, al
    mov wc.style, CS_HREDRAW or CS_VREDRAW or CS_OWNDC
    mov wc.lpfnWndProc, offset WndProc
    mov wc.cbClsExtra, 0
    mov wc.cbWndExtra, 0
    push hInstance
    pop wc.hInstance
    mov wc.hIcon, 0
    invoke LoadCursor, 0, IDC_ARROW
    mov wc.hCursor, eax
    mov wc.lpszMenuName, 0
    mov wc.lpszClassName, offset ClassName
    mov wc.hbrBackground, 0
    invoke RegisterClass, ADDR wc
    .IF (!eax)
        xor eax, eax
        ret
    .ENDIF
    .IF (fullscreen)
        ZeroMemory &dmScreenSettings, sizeof(dmScreenSettings)
        mov dmScreenSettings.dmSize, sizeof dmScreenSettings
        push w
        pop dmScreenSettings.dmPelsWidth
        push h
        pop dmScreenSettings.dmPelsHeight
        push bits
        pop dmScreenSettings.dmBitsPerPel
        mov dmScreenSettings.dmFields, DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT
        invoke ChangeDisplaySettings, ADDR dmScreenSettings, CDS_FULLSCREEN
        .IF eax != DISP_CHANGE_SUCCESSFUL
            invoke MessageBox, 0, ADDR not_full, ADDR scalp, MB_YESNO or MB_ICONEXCLAMATION
            .IF eax == IDYES
                mov fullscreen, 0
            .ELSE
                invoke MessageBox, 0, ADDR close, ADDR error_str, MB_OK or MB_ICONSTOP
                xor eax, eax
                ret
            .ENDIF
        .ENDIF
    .ENDIF
    .IF (fullscreen)
        mov dwExStyle, WS_EX_APPWINDOW
        mov dwStyle, WS_POPUP or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
        invoke ShowCursor, 0
    .ELSE
        mov dwExStyle, WS_EX_APPWINDOW or WS_EX_WINDOWEDGE
        mov dwStyle, WS_OVERLAPPEDWINDOW or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
    .ENDIF
    invoke AdjustWindowRectEx, ADDR rect, dwStyle, 0, dwExStyle
    mov eax, rect.left
    sub rect.right, eax
    mov eax, rect.top
    sub rect.bottom, eax
    invoke CreateWindowEx, dwExStyle, ADDR ClassName, ADDR our_title, \
                           dwStyle,0,0, rect.right, rect.bottom, 0, 0, hInstance, 0
    mov hWnd, eax
    ZeroMemory &pfd, sizeof(PIXELFORMATDESCRIPTOR)
    mov pfd.nSize, sizeof PIXELFORMATDESCRIPTOR
    mov pfd.nVersion, 1
    mov pfd.dwFlags, PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER
    mov pfd.iPixelType, PFD_TYPE_RGBA
    mov eax, bits
    mov pfd.cColorBits, al
    mov pfd.cDepthBits, 16
    mov pfd.dwLayerMask, PFD_MAIN_PLANE
    invoke GetDC, hWnd
    mov hDC, eax
    invoke ChoosePixelFormat, hDC, ADDR pfd
    mov PixelFormat, eax
    invoke SetPixelFormat, hDC, PixelFormat, ADDR pfd
    invoke wglCreateContext, hDC
    mov hRC, eax
    invoke wglMakeCurrent, hDC, hRC
    invoke ShowWindow, hWnd, SW_SHOW
    invoke SetForegroundWindow, hWnd
    invoke SetFocus, hWnd
    invoke ReSizeGLScene, w, h
    invoke InitGL
    cmp eax, 0
    je end_create
    mov eax, 1
   end_create:
    ret
CreateGLWindow endp

ReSizeGLScene proc w:DWORD, h:DWORD             ;resize scene
  LOCAL ratio:GLdouble
    .IF h == 0
        mov h, 1                                ;prevent div by zero
    .ENDIF
    invoke glViewport, 0, 0, w, h               ;reset viewport
    invoke glMatrixMode, GL_PROJECTION          ;select projection matrix
    invoke glLoadIdentity                       ;reset it
    fild w
    fild h
    fdivp st(1), st(0)
    fstp ratio
    _gluPerspective 45.0f, ratio, 0.1f,100.0f
    invoke glMatrixMode, GL_MODELVIEW                   ;select modelview matrix
    invoke glLoadIdentity                               ;reset it
    ret
ReSizeGLScene endp

LoadGLTexture PROC
  LOCAL hBMP:DWORD, ImgInfo:BITMAP
    invoke glGenTextures, 1, ADDR texture
    invoke LoadImage, hInstance, IDD_BITMAP, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION
    cmp eax, 0
    je end_load
    mov hBMP, eax
    invoke GetObject, hBMP, sizeof BITMAP, ADDR ImgInfo
    invoke glBindTexture, GL_TEXTURE_2D, texture
    invoke glTexImage2D, GL_TEXTURE_2D, 0, 3, ImgInfo.bmWidth, ImgInfo.bmHeight,\
                         0, GL_BGR_EXT, GL_UNSIGNED_BYTE, ImgInfo.bmBits
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR
    mov eax, 1
   end_load:
    ret
LoadGLTexture ENDP

KillGLWindow proc
    .IF (fullscreen)                                ;if fullscreen mode
        invoke ChangeDisplaySettings, 0, 0          ;get back to window
        invoke ShowCursor, 1                        ;and show mouse ptr
    .ENDIF
    .IF (hRC)
        invoke wglMakeCurrent, 0, 0
        invoke wglDeleteContext, hRC
        mov hRC, 0
    .ENDIF
    invoke ReleaseDC, hWnd, hDC
    invoke DestroyWindow, hWnd
    invoke UnregisterClass, ADDR ClassName, hInstance
    ret
KillGLWindow endp

DrawGLScene proc                                        ;drawing proc
    invoke glClear, GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT
    invoke glLoadIdentity                               ;reset modelview matrix
    _glTranslatef 0.0f, 0.0f, -5.0f
    _glRotatef xrot, 1.0f,0.0f,0.0f
    _glRotatef yrot, 0.0f,1.0f,0.0f
    _glRotatef zrot, 0.0f,0.0f,1.0f

    invoke glBindTexture, GL_TEXTURE_2D, texture

    invoke glBegin, GL_QUADS
  ;front
    _glTexCoord2f 0.0f,0.0f
    _glVertex3f -1.0f,-1.0f, 1.0f
    _glTexCoord2f 1.0f,0.0f
    _glVertex3f  1.0f,-1.0f, 1.0f
    _glTexCoord2f 1.0f,1.0f
    _glVertex3f  1.0f, 1.0f, 1.0f
    _glTexCoord2f 0.0f,1.0f
    _glVertex3f -1.0f, 1.0f, 1.0f
  ;back
    _glTexCoord2f 1.0f,0.0f
    _glVertex3f -1.0f,-1.0f,-1.0f
    _glTexCoord2f 1.0f,1.0f
    _glVertex3f -1.0f, 1.0f,-1.0f
    _glTexCoord2f 0.0f,1.0f
    _glVertex3f  1.0f, 1.0f,-1.0f
    _glTexCoord2f 0.0f,0.0f
    _glVertex3f  1.0f,-1.0f,-1.0f
  ;top
    _glTexCoord2f 0.0f,1.0f
    _glVertex3f -1.0f, 1.0f,-1.0f
    _glTexCoord2f 0.0f,0.0f
    _glVertex3f -1.0f, 1.0f, 1.0f
    _glTexCoord2f 1.0f,0.0f
    _glVertex3f  1.0f, 1.0f, 1.0f
    _glTexCoord2f 1.0f,1.0f
    _glVertex3f  1.0f, 1.0f,-1.0f
  ;bottom
    _glTexCoord2f 1.0f,1.0f
    _glVertex3f -1.0f,-1.0f,-1.0f
    _glTexCoord2f 0.0f,1.0f
    _glVertex3f  1.0f,-1.0f,-1.0f
    _glTexCoord2f 0.0f,0.0f
    _glVertex3f  1.0f,-1.0f, 1.0f
    _glTexCoord2f 1.0f,0.0f
    _glVertex3f -1.0f,-1.0f, 1.0f
  ;right
    _glTexCoord2f 1.0f,0.0f
    _glVertex3f  1.0f,-1.0f,-1.0f
    _glTexCoord2f 1.0f,1.0f
    _glVertex3f  1.0f, 1.0f,-1.0f
    _glTexCoord2f 0.0f,1.0f
    _glVertex3f  1.0f, 1.0f, 1.0f
    _glTexCoord2f 0.0f,0.0f
    _glVertex3f  1.0f,-1.0f, 1.0f
  ;left
    _glTexCoord2f 0.0f,0.0f
    _glVertex3f -1.0f,-1.0f,-1.0f
    _glTexCoord2f 1.0f,0.0f
    _glVertex3f -1.0f,-1.0f, 1.0f
    _glTexCoord2f 1.0f,1.0f
    _glVertex3f -1.0f, 1.0f, 1.0f
    _glTexCoord2f 0.0f,1.0f
    _glVertex3f -1.0f, 1.0f,-1.0f
    invoke glEnd
    fld xrot
    fadd rot_speed
    fstp xrot
    fld yrot
    fadd rot_speed
    fstp yrot
    fld zrot
    fadd rot_speed
    fstp zrot
    mov eax, 1
    ret
DrawGLScene endp

END start


Lesson 07

.586p
.MODEL FLAT,STDCALL
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\ogl\gl.def
include \masm32\include\ogl\glu.def
include \masm32\include\ogl\kernel32.inc
include \masm32\include\ogl\user32.inc
include \masm32\include\ogl\gdi32.inc
include \masm32\include\ogl\WinExtra\winextra.def
includelib \masm32\lib\opengl32.lib
includelib \masm32\lib\glu32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\gdi32.lib

CreateGLWindow  PROTO :DWORD,:DWORD,:DWORD,:DWORD,:BYTE
WinMain         PROTO :DWORD,:DWORD,:DWORD,:DWORD
WndProc         PROTO :DWORD,:DWORD,:DWORD,:DWORD
ReSizeGLScene   PROTO :DWORD,:DWORD
KillGLWindow    PROTO
InitGL          PROTO
DrawGLScene     PROTO
LoadGLTexture   PROTO

include include.def
IDD_BITMAP      equ 100

.DATA
x_speed         GLfloat 1.4
y_speed         GLfloat 1.4
z               REAL4 -5.0
z_move          GLfloat 0.02
add_speed       GLfloat 0.01
LightAmbient    GLfloat 0.5,0.5,0.5,1.0
LightDiffuse    GLfloat 1.0,1.0,1.0,1.0
LightPosition   GLfloat 0.0,0.0,2.0,1.0
scalp           db "(scalp)",0
our_title       db "Texture Filters, Lighting & Keyboard Control, (s)",0
ClassName       db "asmOpenGL",0
error_str       db "ERROR",0
not_full        db "The requested fullscreen mode is not supported by",13,10
                db "your video card. Use windowed mode instead ?",0
close           db "Program will now close",0
start_full      db "Would you like to run in fullscreen mode ?",0

.DATA?
hDC             HDC ?
hRC             HGLRC ?
hInstance       HANDLE ?
keys            db 256 dup (?)
active          db ?
fullscreen      db ?
light           db ?
fp              db ?
lp              db ?
hWnd            dd ?
filter          dd ?
texture         GLuint  ?        ;this 3 lines are the same as texture[3] in C
texture1        GLuint  ?        ;I do it this way cause it is easyier to access
texture2        GLuint  ?        ;texture1 and texture2
xrot            GLfloat ?
yrot            GLfloat ?

.CODE
start:
    invoke GetModuleHandle, 0
    mov hInstance, eax
    invoke WinMain, eax, 0, 0, 0
    invoke ExitProcess, eax

WinMain proc hInst:HWND, hPrevInst:UINT, CmdLine:WPARAM, CmdShow:LPARAM
  LOCAL msg:MSG, done:DWORD
    invoke MessageBox, 0, ADDR start_full, ADDR our_title, MB_YESNO or MB_ICONQUESTION
    .IF eax == IDNO
        mov fullscreen, 0
    .ELSE
        mov fullscreen, 1
    .ENDIF
    invoke CreateGLWindow, ADDR our_title, 640, 480, 16, fullscreen
    .IF (!eax)
        ret
    .ENDIF
    mov done, 0
    .while (!done)
        invoke PeekMessage, ADDR msg,0,0,0,PM_REMOVE
        .IF (eax)
            .IF msg.message == WM_QUIT
                mov done, 1
            .ELSE
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
            .ENDIF
        .ELSE
            invoke DrawGLScene
            .IF ((active) && (!eax)) || (keys[VK_ESCAPE])
                mov done, 1
            .ELSE
                invoke SwapBuffers, hDC
            .ENDIF
            .IF (keys['L'] && !lp)
                mov lp, 1
                .IF (light)
                    mov light, 0
                .ELSE
                    mov light, 1
                .ENDIF
                .IF (!light)
                    invoke glDisable, GL_LIGHTING
                .ELSE
                    invoke glEnable, GL_LIGHTING
                .ENDIF
            .ENDIF
            .IF (!keys['L'])
                mov lp, 0
            .ENDIF
            .IF (keys['F'] && !lp)
                mov fp, 1
                inc filter
                .IF filter > 2
                    mov filter, 0
                .ENDIF
            .ENDIF
            .IF (!keys['F'])
                mov fp, 0
            .ENDIF
            .IF (keys[VK_PRIOR])
                fld z
                fsub z_move
                fstp z
            .ENDIF
            .IF (keys[VK_NEXT])
                fld z
                fadd z_move
                fstp z
            .ENDIF
            .IF (keys[VK_UP])
                fld x_speed
                fsub add_speed
                fstp x_speed
            .ENDIF
            .IF (keys[VK_DOWN])
                fld x_speed
                fadd add_speed
                fstp x_speed
            .ENDIF
            .IF (keys[VK_RIGHT])
                fld y_speed
                fadd add_speed
                fstp y_speed
            .ENDIF
            .IF (keys[VK_LEFT])
                fld y_speed
                fsub add_speed
                fstp y_speed
            .ENDIF
            .IF (keys[VK_F1])
                mov keys[VK_F1], 0
                invoke KillGLWindow
                .IF (fullscreen)
                    mov fullscreen, 0
                .ELSE
                    mov fullscreen, 1
                .ENDIF
                invoke CreateGLWindow, ADDR our_title, 640,480,16,fullscreen
                .IF (!eax)
                    ret
                .ENDIF
            .ENDIF
        .ENDIF
    .endw
    invoke KillGLWindow
    mov eax, msg.wParam
    ret
WinMain endp

WndProc proc hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    .IF uMsg == WM_ACTIVATE
        mov eax, wParam
        .IF (!ah)
            mov active, 1
        .ELSE
            mov active, 0
        .ENDIF
    .ELSEIF uMsg == WM_SYSCOMMAND
        .IF (wParam == SC_SCREENSAVE) || (wParam == SC_MONITORPOWER)
            xor eax, eax
            ret
        .ENDIF
    .ELSEIF uMsg == WM_KEYDOWN
        mov eax, wParam
        mov keys[eax], 1
    .ELSEIF uMsg == WM_KEYUP
        mov eax, wParam
        mov keys[eax], 0
    .ELSEIF uMsg == WM_SIZE
        mov eax, lParam
        mov ebx, eax
        and eax, 0ffffh         ;eax=width=LOWORD
        shr ebx, 16             ;ebx=height=HIWORD
        invoke ReSizeGLScene, eax, ebx
    .ELSEIF uMsg == WM_CLOSE
        invoke PostQuitMessage, 0
        xor eax, eax
        ret
    .ENDIF
    invoke DefWindowProc, hWin, uMsg, wParam, lParam
    ret
WndProc endp

InitGL proc
    invoke LoadGLTexture
    cmp eax, 0
    je end_init
    invoke glEnable, GL_TEXTURE_2D
    invoke glShadeModel, GL_SMOOTH
    _glClearDepth 1.0f
    invoke glEnable, GL_DEPTH_TEST
    invoke glDepthFunc, GL_LEQUAL
    invoke glHint, GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST
    invoke glLightfv, GL_LIGHT1, GL_AMBIENT, ADDR LightAmbient
    invoke glLightfv, GL_LIGHT1, GL_DIFFUSE, ADDR LightDiffuse
    invoke glLightfv, GL_LIGHT1, GL_POSITION, ADDR LightPosition
    invoke glEnable, GL_LIGHT1
    mov eax, 1
   end_init:
    ret
InitGL endp

CreateGLWindow proc letitle:DWORD,w:DWORD,h:DWORD,bits:DWORD,flag:BYTE
  LOCAL PixelFormat:DWORD, wc:WNDCLASS
  LOCAL dwExStyle:DWORD, dwStyle:DWORD
  LOCAL rect:RECT, dmScreenSettings:DEVMODE
  LOCAL pfd:PIXELFORMATDESCRIPTOR
    mov rect.left, 0
    push w
    pop rect.right
    mov rect.top, 0
    push h
    pop rect.bottom
    mov al, flag
    mov fullscreen, al
    mov wc.style, CS_HREDRAW or CS_VREDRAW or CS_OWNDC
    mov wc.lpfnWndProc, offset WndProc
    mov wc.cbClsExtra, 0
    mov wc.cbWndExtra, 0
    push hInstance
    pop wc.hInstance
    mov wc.hIcon, 0
    invoke LoadCursor, 0, IDC_ARROW
    mov wc.hCursor, eax
    mov wc.lpszMenuName, 0
    mov wc.lpszClassName, offset ClassName
    mov wc.hbrBackground, 0
    invoke RegisterClass, ADDR wc
    .IF (!eax)
        ret
    .ENDIF
    .IF (fullscreen)
        ZeroMemory &dmScreenSettings, sizeof(dmScreenSettings)
        mov dmScreenSettings.dmSize, sizeof dmScreenSettings
        push w
        pop dmScreenSettings.dmPelsWidth
        push h
        pop dmScreenSettings.dmPelsHeight
        push bits
        pop dmScreenSettings.dmBitsPerPel
        mov dmScreenSettings.dmFields, DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT
        invoke ChangeDisplaySettings, ADDR dmScreenSettings, CDS_FULLSCREEN
        .IF eax != DISP_CHANGE_SUCCESSFUL
            invoke MessageBox, 0, ADDR not_full, ADDR scalp, MB_YESNO or MB_ICONEXCLAMATION
            .IF eax == IDYES
                mov fullscreen, 0
            .ELSE
                invoke MessageBox, 0, ADDR close, ADDR error_str, MB_OK or MB_ICONSTOP
                xor eax, eax
                ret
            .ENDIF
        .ENDIF
    .ENDIF
    .IF (fullscreen)
        mov dwExStyle, WS_EX_APPWINDOW
        mov dwStyle, WS_POPUP or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
        invoke ShowCursor, 0
    .ELSE
        mov dwExStyle, WS_EX_APPWINDOW or WS_EX_WINDOWEDGE
        mov dwStyle, WS_OVERLAPPEDWINDOW or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
    .ENDIF
    invoke AdjustWindowRectEx, ADDR rect, dwStyle, 0, dwExStyle
    mov eax, rect.left
    sub rect.right, eax
    mov eax, rect.top
    sub rect.bottom, eax
    invoke CreateWindowEx, dwExStyle, ADDR ClassName, ADDR our_title, \
                           dwStyle,0,0, rect.right, rect.bottom, 0, 0, hInstance, 0
    mov hWnd, eax
    ZeroMemory &pfd, sizeof(PIXELFORMATDESCRIPTOR)
    mov pfd.nSize, sizeof PIXELFORMATDESCRIPTOR
    mov pfd.nVersion, 1
    mov pfd.dwFlags, PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER
    mov pfd.iPixelType, PFD_TYPE_RGBA
    mov eax, bits
    mov pfd.cColorBits, al
    mov pfd.cDepthBits, 16
    mov pfd.dwLayerMask, PFD_MAIN_PLANE
    invoke GetDC, hWnd
    mov hDC, eax
    invoke ChoosePixelFormat, hDC, ADDR pfd
    mov PixelFormat, eax
    invoke SetPixelFormat, hDC, PixelFormat, ADDR pfd
    invoke wglCreateContext, hDC
    mov hRC, eax
    invoke wglMakeCurrent, hDC, hRC
    invoke ShowWindow, hWnd, SW_SHOW
    invoke SetForegroundWindow, hWnd
    invoke SetFocus, hWnd
    invoke ReSizeGLScene, w, h
    invoke InitGL
    cmp eax, 0
    je end_create
    mov eax, 1
   end_create:
    ret
CreateGLWindow endp

ReSizeGLScene proc w:DWORD, h:DWORD             ;resize scene
  LOCAL ratio:GLdouble
    .IF h == 0
        mov h, 1                                ;prevent div by zero
    .ENDIF
    invoke glViewport, 0, 0, w, h               ;reset viewport
    invoke glMatrixMode, GL_PROJECTION          ;select projection matrix
    invoke glLoadIdentity                       ;reset it
    fild w
    fild h
    fdivp st(1), st(0)
    fstp ratio
    _gluPerspective 45.0f, ratio, 0.1f,100.0f
    invoke glMatrixMode, GL_MODELVIEW                   ;select modelview matrix
    invoke glLoadIdentity                               ;reset it
    ret
ReSizeGLScene endp

LoadGLTexture PROC
  LOCAL hBMP:DWORD, ImgInfo:BITMAP
    invoke glGenTextures, 3, ADDR texture
    invoke LoadImage, hInstance, IDD_BITMAP, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION
    cmp eax, 0
    je end_load
    mov hBMP, eax
    invoke GetObject, hBMP, sizeof BITMAP, ADDR ImgInfo
    invoke glBindTexture, GL_TEXTURE_2D, texture
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST
    invoke glTexImage2D, GL_TEXTURE_2D, 0, 3, ImgInfo.bmWidth, ImgInfo.bmHeight,\
                         0, GL_BGR_EXT, GL_UNSIGNED_BYTE, ImgInfo.bmBits
    invoke glBindTexture, GL_TEXTURE_2D, texture1
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR
    invoke glTexImage2D, GL_TEXTURE_2D, 0, 3, ImgInfo.bmWidth, ImgInfo.bmHeight,\
                         0, GL_BGR_EXT, GL_UNSIGNED_BYTE, ImgInfo.bmBits
    invoke glBindTexture, GL_TEXTURE_2D, texture2
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_NEAREST
    invoke glTexImage2D, GL_TEXTURE_2D, 0, 3, ImgInfo.bmWidth, ImgInfo.bmHeight,\
                         0, GL_BGR_EXT, GL_UNSIGNED_BYTE, ImgInfo.bmBits
    mov eax, 1
   end_load:
    ret
LoadGLTexture ENDP

KillGLWindow proc
    .IF (fullscreen)                                ;if fullscreen mode
        invoke ChangeDisplaySettings, 0, 0          ;get back to window
        invoke ShowCursor, 1                        ;and show mouse ptr
    .ENDIF
    .IF (hRC)
        invoke wglMakeCurrent, 0, 0
        invoke wglDeleteContext, hRC
        mov hRC, 0
    .ENDIF
    invoke ReleaseDC, hWnd, hDC
    invoke DestroyWindow, hWnd
    invoke UnregisterClass, ADDR ClassName, hInstance
    ret
KillGLWindow endp

DrawGLScene proc                                        ;drawing proc
    invoke glClear, GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT
    invoke glLoadIdentity                               ;reset modelview matrix
    _glTranslatef 0.0f, 0.0f, z
    _glRotatef xrot, 1.0f,0.0f,0.0f
    _glRotatef yrot, 0.0f,1.0f,0.0f
    mov eax, offset texture
    .IF filter == 1
        add eax, 4
    .ELSEIF filter == 2
        add eax, 8
    .ENDIF
    invoke glBindTexture, GL_TEXTURE_2D, DWORD PTR [eax]
    invoke glBegin, GL_QUADS
  ;front
    _glNormal3f 0.0f,0.0f,1.0f
    _glTexCoord2f 0.0f,0.0f
    _glVertex3f -1.0f,-1.0f, 1.0f
    _glTexCoord2f 1.0f,0.0f
    _glVertex3f  1.0f,-1.0f, 1.0f
    _glTexCoord2f 1.0f,1.0f
    _glVertex3f  1.0f, 1.0f, 1.0f
    _glTexCoord2f 0.0f,1.0f
    _glVertex3f -1.0f, 1.0f, 1.0f
  ;back
    _glNormal3f 0.0f,0.0f,-1.0f
    _glTexCoord2f 1.0f,0.0f
    _glVertex3f -1.0f,-1.0f,-1.0f
    _glTexCoord2f 1.0f,1.0f
    _glVertex3f -1.0f, 1.0f,-1.0f
    _glTexCoord2f 0.0f,1.0f
    _glVertex3f  1.0f, 1.0f,-1.0f
    _glTexCoord2f 0.0f,0.0f
    _glVertex3f  1.0f,-1.0f,-1.0f
  ;top
    _glNormal3f 0.0f,1.0f,0.0f
    _glTexCoord2f 0.0f,1.0f
    _glVertex3f -1.0f, 1.0f,-1.0f
    _glTexCoord2f 0.0f,0.0f
    _glVertex3f -1.0f, 1.0f, 1.0f
    _glTexCoord2f 1.0f,0.0f
    _glVertex3f  1.0f, 1.0f, 1.0f
    _glTexCoord2f 1.0f,1.0f
    _glVertex3f  1.0f, 1.0f,-1.0f
  ;bottom
    _glNormal3f 0.0f,-1.0f,0.0f
    _glTexCoord2f 1.0f,1.0f
    _glVertex3f -1.0f,-1.0f,-1.0f
    _glTexCoord2f 0.0f,1.0f
    _glVertex3f  1.0f,-1.0f,-1.0f
    _glTexCoord2f 0.0f,0.0f
    _glVertex3f  1.0f,-1.0f, 1.0f
    _glTexCoord2f 1.0f,0.0f
    _glVertex3f -1.0f,-1.0f, 1.0f
  ;right
    _glNormal3f 1.0f,0.0f,0.0f
    _glTexCoord2f 1.0f,0.0f
    _glVertex3f  1.0f,-1.0f,-1.0f
    _glTexCoord2f 1.0f,1.0f
    _glVertex3f  1.0f, 1.0f,-1.0f
    _glTexCoord2f 0.0f,1.0f
    _glVertex3f  1.0f, 1.0f, 1.0f
    _glTexCoord2f 0.0f,0.0f
    _glVertex3f  1.0f,-1.0f, 1.0f
  ;left
    _glNormal3f -1.0f,0.0f,0.0f
    _glTexCoord2f 0.0f,0.0f
    _glVertex3f -1.0f,-1.0f,-1.0f
    _glTexCoord2f 1.0f,0.0f
    _glVertex3f -1.0f,-1.0f, 1.0f
    _glTexCoord2f 1.0f,1.0f
    _glVertex3f -1.0f, 1.0f, 1.0f
    _glTexCoord2f 0.0f,1.0f
    _glVertex3f -1.0f, 1.0f,-1.0f
    invoke glEnd
    fld xrot
    fadd x_speed
    fstp xrot
    fld yrot
    fadd y_speed
    fstp yrot
    mov eax, 1
    ret
DrawGLScene endp

END start


Lesson 08

.586p
.MODEL FLAT,STDCALL
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\ogl\gl.def
include \masm32\include\ogl\glu.def
include \masm32\include\ogl\kernel32.inc
include \masm32\include\ogl\user32.inc
include \masm32\include\ogl\gdi32.inc
include \masm32\include\ogl\WinExtra\winextra.def
includelib \masm32\lib\opengl32.lib
includelib \masm32\lib\glu32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\gdi32.lib

CreateGLWindow  PROTO :DWORD,:DWORD,:DWORD,:DWORD,:BYTE
WinMain         PROTO :DWORD,:DWORD,:DWORD,:DWORD
WndProc         PROTO :DWORD,:DWORD,:DWORD,:DWORD
ReSizeGLScene   PROTO :DWORD,:DWORD
KillGLWindow    PROTO
InitGL          PROTO
DrawGLScene     PROTO
LoadGLTexture   PROTO

include include.def
IDD_BITMAP      equ 100

.DATA
x_speed         GLfloat 1.4
y_speed         GLfloat 1.4
z               REAL4 -5.0
z_move          GLfloat 0.02
add_speed       GLfloat 0.01
LightAmbient    GLfloat 0.5,0.5,0.5,1.0
LightDiffuse    GLfloat 1.0,1.0,1.0,1.0
LightPosition   GLfloat 0.0,0.0,2.0,1.0
scalp           db "(scalp)",0
our_title       db "Blending, (s)",0
ClassName       db "asmOpenGL",0
error_str       db "ERROR",0
not_full        db "The requested fullscreen mode is not supported by",13,10
                db "your video card. Use windowed mode instead ?",0
close           db "Program will now close",0
start_full      db "Would you like to run in fullscreen mode ?",0

.DATA?
hDC             HDC ?
hRC             HGLRC ?
hInstance       HANDLE ?
keys            db 256 dup (?)
active          db ?
fullscreen      db ?
light           db ?
blend           db ?
fp              db ?
lp              db ?
bpressed        db ?
hWnd            dd ?
filter          dd ?
texture         GLuint ?        ;this 3 lines are the same as texture[3] in C
texture1        GLuint ?        ;I do it this way cause it is easyier to access
texture2        GLuint ?        ;texture1 and texture2
xrot            GLfloat ?
yrot            GLfloat ?

.CODE
start:
    invoke GetModuleHandle, 0
    mov hInstance, eax
    invoke WinMain, eax, 0, 0, 0
    invoke ExitProcess, eax

WinMain proc hInst:HWND, hPrevInst:UINT, CmdLine:WPARAM, CmdShow:LPARAM
  LOCAL msg:MSG, done:DWORD
    invoke MessageBox, 0, ADDR start_full, ADDR our_title, MB_YESNO or MB_ICONQUESTION
    .IF eax == IDNO
        mov fullscreen, 0
    .ELSE
        mov fullscreen, 1
    .ENDIF
    invoke CreateGLWindow, ADDR our_title, 640, 480, 16, fullscreen
    .IF (!eax)
        ret
    .ENDIF
    mov done, 0
    .while (!done)
        invoke PeekMessage, ADDR msg,0,0,0,PM_REMOVE
        .IF (eax)
            .IF msg.message == WM_QUIT
                mov done, 1
            .ELSE
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
            .ENDIF
        .ELSE
            invoke DrawGLScene
            .IF ((active) && (!eax)) || (keys[VK_ESCAPE])
                mov done, 1
            .ELSE
                invoke SwapBuffers, hDC
            .ENDIF
            .IF (keys['L'] && !lp)
                mov lp, 1
                .IF (light)
                    mov light, 0
                .ELSE
                    mov light, 1
                .ENDIF
                .IF (!light)
                    invoke glDisable, GL_LIGHTING
                .ELSE
                    invoke glEnable, GL_LIGHTING
                .ENDIF
            .ENDIF
            .IF (!keys['L'])
                mov lp, 0
            .ENDIF
            .IF (keys['F'] && !lp)
                mov fp, 1
                inc filter
                .IF filter > 2
                    mov filter, 0
                .ENDIF
            .ENDIF
            .IF (!keys['F'])
                mov fp, 0
            .ENDIF
            .IF (keys[VK_PRIOR])
                fld z
                fsub z_move
                fstp z
            .ENDIF
            .IF (keys[VK_NEXT])
                fld z
                fadd z_move
                fstp z
            .ENDIF
            .IF (keys[VK_UP])
                fld x_speed
                fsub add_speed
                fstp x_speed
            .ENDIF
            .IF (keys[VK_DOWN])
                fld x_speed
                fadd add_speed
                fstp x_speed
            .ENDIF
            .IF (keys[VK_RIGHT])
                fld y_speed
                fadd add_speed
                fstp y_speed
            .ENDIF
            .IF (keys[VK_LEFT])
                fld y_speed
                fsub add_speed
                fstp y_speed
            .ENDIF
            .IF (keys['B'] && !bpressed)
                mov bpressed, 1
                .IF (blend)
                    mov blend, 0
                .ELSE
                    mov blend, 1
                .ENDIF
                .IF (blend)
                    invoke glEnable, GL_BLEND
                    invoke glDisable, GL_DEPTH_TEST
                .ELSE
                    invoke glDisable, GL_BLEND
                    invoke glEnable, GL_DEPTH_TEST
                .ENDIF
            .ENDIF
            .IF (!keys['B'])
                mov bpressed, 0
            .ENDIF
            .IF (keys[VK_F1])
                mov keys[VK_F1], 0
                invoke KillGLWindow
                .IF (fullscreen)
                    mov fullscreen, 0
                .ELSE
                    mov fullscreen, 1
                .ENDIF
                invoke CreateGLWindow, ADDR our_title, 640,480,16,fullscreen
                .IF (!eax)
                    ret
                .ENDIF
            .ENDIF
        .ENDIF
    .endw
    invoke KillGLWindow
    mov eax, msg.wParam
    ret
WinMain endp

WndProc proc hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    .IF uMsg == WM_ACTIVATE
        mov eax, wParam
        .IF (!ah)
            mov active, 1
        .ELSE
            mov active, 0
        .ENDIF
    .ELSEIF uMsg == WM_SYSCOMMAND
        .IF (wParam == SC_SCREENSAVE) || (wParam == SC_MONITORPOWER)
            xor eax, eax
            ret
        .ENDIF
    .ELSEIF uMsg == WM_KEYDOWN
        mov eax, wParam
        mov keys[eax], 1
    .ELSEIF uMsg == WM_KEYUP
        mov eax, wParam
        mov keys[eax], 0
    .ELSEIF uMsg == WM_SIZE
        mov eax, lParam
        mov ebx, eax
        and eax, 0ffffh         ;eax=width=LOWORD
        shr ebx, 16             ;ebx=height=HIWORD
        invoke ReSizeGLScene, eax, ebx
    .ELSEIF uMsg == WM_CLOSE
        invoke PostQuitMessage, 0
        xor eax, eax
        ret
    .ENDIF
    invoke DefWindowProc, hWin, uMsg, wParam, lParam
    ret
WndProc endp

InitGL proc
    invoke LoadGLTexture
    cmp eax, 0
    je end_init
    invoke glEnable, GL_TEXTURE_2D
    invoke glShadeModel, GL_SMOOTH
    _glClearDepth 1.0f
    _glColor4f 1.0f,1.0f,1.0f,0.5f
    invoke glBlendFunc, GL_SRC_ALPHA, GL_ONE
    invoke glEnable, GL_DEPTH_TEST
    invoke glDepthFunc, GL_LEQUAL
    invoke glHint, GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST
    invoke glLightfv, GL_LIGHT1, GL_AMBIENT, ADDR LightAmbient
    invoke glLightfv, GL_LIGHT1, GL_DIFFUSE, ADDR LightDiffuse
    invoke glLightfv, GL_LIGHT1, GL_POSITION, ADDR LightPosition
    invoke glEnable, GL_LIGHT1
    mov eax, 1
   end_init:
    ret
InitGL endp

CreateGLWindow proc letitle:DWORD,w:DWORD,h:DWORD,bits:DWORD,flag:BYTE
  LOCAL PixelFormat:DWORD, wc:WNDCLASS
  LOCAL dwExStyle:DWORD, dwStyle:DWORD
  LOCAL rect:RECT, dmScreenSettings:DEVMODE
  LOCAL pfd:PIXELFORMATDESCRIPTOR
    mov rect.left, 0
    push w
    pop rect.right
    mov rect.top, 0
    push h
    pop rect.bottom
    mov al, flag
    mov fullscreen, al
    mov wc.style, CS_HREDRAW or CS_VREDRAW or CS_OWNDC
    mov wc.lpfnWndProc, offset WndProc
    mov wc.cbClsExtra, 0
    mov wc.cbWndExtra, 0
    push hInstance
    pop wc.hInstance
    mov wc.hIcon, 0
    invoke LoadCursor, 0, IDC_ARROW
    mov wc.hCursor, eax
    mov wc.lpszMenuName, 0
    mov wc.lpszClassName, offset ClassName
    mov wc.hbrBackground, 0
    invoke RegisterClass, ADDR wc
    .IF (!eax)
        xor eax, eax
        ret
    .ENDIF
    .IF (fullscreen)
        ZeroMemory &dmScreenSettings, sizeof(dmScreenSettings)
        mov dmScreenSettings.dmSize, sizeof dmScreenSettings
        push w
        pop dmScreenSettings.dmPelsWidth
        push h
        pop dmScreenSettings.dmPelsHeight
        push bits
        pop dmScreenSettings.dmBitsPerPel
        mov dmScreenSettings.dmFields, DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT
        invoke ChangeDisplaySettings, ADDR dmScreenSettings, CDS_FULLSCREEN
        .IF eax != DISP_CHANGE_SUCCESSFUL
            invoke MessageBox, 0, ADDR not_full, ADDR scalp, MB_YESNO or MB_ICONEXCLAMATION
            .IF eax == IDYES
                mov fullscreen, 0
            .ELSE
                invoke MessageBox, 0, ADDR close, ADDR error_str, MB_OK or MB_ICONSTOP
                xor eax, eax
                ret
            .ENDIF
        .ENDIF
    .ENDIF
    .IF (fullscreen)
        mov dwExStyle, WS_EX_APPWINDOW
        mov dwStyle, WS_POPUP or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
        invoke ShowCursor, 0
    .ELSE
        mov dwExStyle, WS_EX_APPWINDOW or WS_EX_WINDOWEDGE
        mov dwStyle, WS_OVERLAPPEDWINDOW or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
    .ENDIF
    invoke AdjustWindowRectEx, ADDR rect, dwStyle, 0, dwExStyle
    mov eax, rect.left
    sub rect.right, eax
    mov eax, rect.top
    sub rect.bottom, eax
    invoke CreateWindowEx, dwExStyle, ADDR ClassName, ADDR our_title, \
                           dwStyle,0,0, rect.right, rect.bottom, 0, 0, hInstance, 0
    mov hWnd, eax
    ZeroMemory &pfd, sizeof(PIXELFORMATDESCRIPTOR)
    mov pfd.nSize, sizeof PIXELFORMATDESCRIPTOR
    mov pfd.nVersion, 1
    mov pfd.dwFlags, PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER
    mov pfd.iPixelType, PFD_TYPE_RGBA
    mov eax, bits
    mov pfd.cColorBits, al
    mov pfd.cDepthBits, 16
    mov pfd.dwLayerMask, PFD_MAIN_PLANE
    invoke GetDC, hWnd
    mov hDC, eax
    invoke ChoosePixelFormat, hDC, ADDR pfd
    mov PixelFormat, eax
    invoke SetPixelFormat, hDC, PixelFormat, ADDR pfd
    invoke wglCreateContext, hDC
    mov hRC, eax
    invoke wglMakeCurrent, hDC, hRC
    invoke ShowWindow, hWnd, SW_SHOW
    invoke SetForegroundWindow, hWnd
    invoke SetFocus, hWnd
    invoke ReSizeGLScene, w, h
    invoke InitGL
    cmp eax, 0
    je end_create
    mov eax, 1
   end_create:
    ret
CreateGLWindow endp

ReSizeGLScene proc w:DWORD, h:DWORD             ;resize scene
  LOCAL ratio:GLdouble
    .IF h == 0
        mov h, 1                                ;prevent div by zero
    .ENDIF
    invoke glViewport, 0, 0, w, h               ;reset viewport
    invoke glMatrixMode, GL_PROJECTION          ;select projection matrix
    invoke glLoadIdentity                       ;reset it
    fild w
    fild h
    fdivp st(1), st(0)
    fstp ratio
    _gluPerspective 45.0f, ratio, 0.1f,100.0f
    invoke glMatrixMode, GL_MODELVIEW                   ;select modelview matrix
    invoke glLoadIdentity                               ;reset it
    ret
ReSizeGLScene endp

LoadGLTexture PROC
  LOCAL hBMP:DWORD, ImgInfo:BITMAP
    invoke glGenTextures, 3, ADDR texture
    invoke LoadImage, hInstance, IDD_BITMAP, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION
    cmp eax, 0
    je end_load
    mov hBMP, eax
    invoke GetObject, hBMP, sizeof BITMAP, ADDR ImgInfo

    invoke glBindTexture, GL_TEXTURE_2D, texture
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST
    invoke glTexImage2D, GL_TEXTURE_2D, 0, 3, ImgInfo.bmWidth, ImgInfo.bmHeight,\
                         0, GL_BGR_EXT, GL_UNSIGNED_BYTE, ImgInfo.bmBits

    invoke glBindTexture, GL_TEXTURE_2D, texture1
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR
    invoke glTexImage2D, GL_TEXTURE_2D, 0, 3, ImgInfo.bmWidth, ImgInfo.bmHeight,\
                         0, GL_BGR_EXT, GL_UNSIGNED_BYTE, ImgInfo.bmBits

    invoke glBindTexture, GL_TEXTURE_2D, texture2
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_NEAREST
    invoke gluBuild2DMipmaps, GL_TEXTURE_2D, 3, ImgInfo.bmWidth, ImgInfo.bmHeight,\
                              GL_BGR_EXT, GL_UNSIGNED_BYTE, ImgInfo.bmBits

    mov eax, 1
   end_load:
    ret
LoadGLTexture ENDP

KillGLWindow proc
    .IF (fullscreen)                                ;if fullscreen mode
        invoke ChangeDisplaySettings, 0, 0          ;get back to window
        invoke ShowCursor, 1                        ;and show mouse ptr
    .ENDIF
    .IF (hRC)
        invoke wglMakeCurrent, 0, 0
        invoke wglDeleteContext, hRC
        mov hRC, 0
    .ENDIF
    invoke ReleaseDC, hWnd, hDC
    invoke DestroyWindow, hWnd
    invoke UnregisterClass, ADDR ClassName, hInstance
    ret
KillGLWindow endp

DrawGLScene proc                                        ;drawing proc
    invoke glClear, GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT
    invoke glLoadIdentity                               ;reset modelview matrix
    _glTranslatef 0.0f, 0.0f, z
    _glRotatef xrot, 1.0f,0.0f,0.0f
    _glRotatef yrot, 0.0f,1.0f,0.0f

    mov eax, offset texture
    .IF filter == 1
        add eax, 4
    .ELSEIF filter == 2
        add eax, 8
    .ENDIF
    invoke glBindTexture, GL_TEXTURE_2D, DWORD PTR [eax]

    invoke glBegin, GL_QUADS
  ;front
    _glNormal3f 0.0f,0.0f,1.0f
    _glTexCoord2f 0.0f,0.0f
    _glVertex3f -1.0f,-1.0f, 1.0f
    _glTexCoord2f 1.0f,0.0f
    _glVertex3f  1.0f,-1.0f, 1.0f
    _glTexCoord2f 1.0f,1.0f
    _glVertex3f  1.0f, 1.0f, 1.0f
    _glTexCoord2f 0.0f,1.0f
    _glVertex3f -1.0f, 1.0f, 1.0f
  ;back
    _glNormal3f 0.0f,0.0f,-1.0f
    _glTexCoord2f 1.0f,0.0f
    _glVertex3f -1.0f,-1.0f,-1.0f
    _glTexCoord2f 1.0f,1.0f
    _glVertex3f -1.0f, 1.0f,-1.0f
    _glTexCoord2f 0.0f,1.0f
    _glVertex3f  1.0f, 1.0f,-1.0f
    _glTexCoord2f 0.0f,0.0f
    _glVertex3f  1.0f,-1.0f,-1.0f
  ;top
    _glNormal3f 0.0f,1.0f,0.0f
    _glTexCoord2f 0.0f,1.0f
    _glVertex3f -1.0f, 1.0f,-1.0f
    _glTexCoord2f 0.0f,0.0f
    _glVertex3f -1.0f, 1.0f, 1.0f
    _glTexCoord2f 1.0f,0.0f
    _glVertex3f  1.0f, 1.0f, 1.0f
    _glTexCoord2f 1.0f,1.0f
    _glVertex3f  1.0f, 1.0f,-1.0f
  ;bottom
    _glNormal3f 0.0f,-1.0f,0.0f
    _glTexCoord2f 1.0f,1.0f
    _glVertex3f -1.0f,-1.0f,-1.0f
    _glTexCoord2f 0.0f,1.0f
    _glVertex3f  1.0f,-1.0f,-1.0f
    _glTexCoord2f 0.0f,0.0f
    _glVertex3f  1.0f,-1.0f, 1.0f
    _glTexCoord2f 1.0f,0.0f
    _glVertex3f -1.0f,-1.0f, 1.0f
  ;right
    _glNormal3f 1.0f,0.0f,0.0f
    _glTexCoord2f 1.0f,0.0f
    _glVertex3f  1.0f,-1.0f,-1.0f
    _glTexCoord2f 1.0f,1.0f
    _glVertex3f  1.0f, 1.0f,-1.0f
    _glTexCoord2f 0.0f,1.0f
    _glVertex3f  1.0f, 1.0f, 1.0f
    _glTexCoord2f 0.0f,0.0f
    _glVertex3f  1.0f,-1.0f, 1.0f
  ;left
    _glNormal3f -1.0f,0.0f,0.0f
    _glTexCoord2f 0.0f,0.0f
    _glVertex3f -1.0f,-1.0f,-1.0f
    _glTexCoord2f 1.0f,0.0f
    _glVertex3f -1.0f,-1.0f, 1.0f
    _glTexCoord2f 1.0f,1.0f
    _glVertex3f -1.0f, 1.0f, 1.0f
    _glTexCoord2f 0.0f,1.0f
    _glVertex3f -1.0f, 1.0f,-1.0f
    invoke glEnd
    fld xrot
    fadd x_speed
    fstp xrot
    fld yrot
    fadd y_speed
    fstp yrot
    mov eax, 1
    ret
DrawGLScene endp

END start


Lesson 09

.586p
.MODEL FLAT,STDCALL
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\ogl\gl.def
include \masm32\include\ogl\glu.def
include \masm32\include\ogl\kernel32.inc
include \masm32\include\ogl\user32.inc
include \masm32\include\ogl\gdi32.inc
include \masm32\include\ogl\WinExtra\winextra.def
includelib \masm32\lib\opengl32.lib
includelib \masm32\lib\glu32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\gdi32.lib

CreateGLWindow  PROTO :DWORD,:DWORD,:DWORD,:DWORD,:BYTE
WinMain         PROTO :DWORD,:DWORD,:DWORD,:DWORD
WndProc         PROTO :DWORD,:DWORD,:DWORD,:DWORD
ReSizeGLScene   PROTO :DWORD,:DWORD
randlimit       PROTO :DWORD
randomize       PROTO
KillGLWindow    PROTO
InitGL          PROTO
DrawGLScene     PROTO
LoadGLTexture   PROTO

stars STRUCT
    r DWORD ?
    g DWORD ?
    b DWORD ?
    dist  GLfloat ?
    angle GLfloat ?
stars ENDS

include include.def             ;macro and constant definition
IDD_BITMAP      equ 100
num             equ 50
size_star       equ sizeof stars

.DATA
zoom            REAL4 -15.0f
tilt            GLfloat 90.0f
add_tilt        GLfloat 0.5f
add_spin        GLfloat 0.01
z_move          GLfloat 0.2f
fzero           GLfloat 0.0f
fcinq           GLfloat 5.0f
scalp           db "(scalp)",0
our_title       db "Moving bitmap in 3D space, (s)",0
ClassName       db "asmOpenGL",0
error_str       db "ERROR",0
not_full        db "The requested fullscreen mode is not supported by",13,10
                db "your video card. Use windowed mode instead ?",0
close           db "Program will now close",0
start_full      db "Would you like to run in fullscreen mode ?",0

.DATA?
hDC             HDC ?
hRC             HGLRC ?
hInstance       HANDLE ?
hWnd            dd ?
keys            db 256 dup (?)
active          db ?
fullscreen      db ?
twinkle         db ?
tp              db ?
texture         GLuint  ?
spin            GLfloat ?
star            stars num dup()
RandSeed        dd ?

.CODE
start:
    invoke GetModuleHandle, 0
    mov hInstance, eax
    invoke WinMain, eax, 0, 0, 0
    invoke ExitProcess, eax

WinMain proc hInst:HWND, hPrevInst:UINT, CmdLine:WPARAM, CmdShow:LPARAM
  LOCAL msg:MSG, done:DWORD
    invoke MessageBox, 0, ADDR start_full, ADDR our_title, MB_YESNO or MB_ICONQUESTION
    .IF eax == IDNO
        mov fullscreen, 0
    .ELSE
        mov fullscreen, 1
    .ENDIF
    invoke CreateGLWindow, ADDR our_title, 640, 480, 16, fullscreen
    .IF (!eax)
        ret
    .ENDIF
    mov done, 0
    .while (!done)
        invoke PeekMessage, ADDR msg,0,0,0,PM_REMOVE
        .IF (eax)
            .IF msg.message == WM_QUIT
                mov done, 1
            .ELSE
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
            .ENDIF
        .ELSE
            invoke DrawGLScene
            .IF ((active) && (!eax)) || (keys[VK_ESCAPE])
                mov done, 1
            .ELSE
                invoke SwapBuffers, hDC
            .ENDIF
            .IF (keys['T'] && !tp)
                mov tp, 1
                .IF (twinkle)
                    mov twinkle, 0
                .ELSE
                    mov twinkle, 1
                .ENDIF
            .ENDIF
            .IF (!keys['T'])
                mov tp, 0
            .ENDIF
            .IF (keys[VK_UP])
                fld tilt
                fsub add_tilt
                fstp tilt
            .ENDIF
            .IF (keys[VK_DOWN])
                fld tilt
                fadd add_tilt
                fstp tilt
            .ENDIF
            .IF (keys[VK_PRIOR])
                fld zoom
                fsub z_move
                fstp zoom
            .ENDIF
            .IF (keys[VK_NEXT])
                fld zoom
                fadd z_move
                fstp zoom
            .ENDIF
            .IF (keys[VK_F1])
                mov keys[VK_F1], 0
                invoke KillGLWindow
                .IF (fullscreen)
                    mov fullscreen, 0
                .ELSE
                    mov fullscreen, 1
                .ENDIF
                invoke CreateGLWindow, ADDR our_title, 640,480,16,fullscreen
                .IF (!eax)
                    ret
                .ENDIF
            .ENDIF
        .ENDIF
    .endw
    invoke KillGLWindow
    mov eax, msg.wParam
    ret
WinMain endp

WndProc proc hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    .IF uMsg == WM_ACTIVATE
        mov eax, wParam
        .IF (!ah)
            mov active, 1
        .ELSE
            mov active, 0
        .ENDIF
    .ELSEIF uMsg == WM_SYSCOMMAND
        .IF (wParam == SC_SCREENSAVE) || (wParam == SC_MONITORPOWER)
            xor eax, eax
            ret
        .ENDIF
    .ELSEIF uMsg == WM_KEYDOWN
        mov eax, wParam
        mov keys[eax], 1
    .ELSEIF uMsg == WM_KEYUP
        mov eax, wParam
        mov keys[eax], 0
    .ELSEIF uMsg == WM_SIZE
        mov eax, lParam
        mov ebx, eax
        and eax, 0ffffh         ;eax=width=LOWORD
        shr ebx, 16             ;ebx=height=HIWORD
        invoke ReSizeGLScene, eax, ebx
    .ELSEIF uMsg == WM_CLOSE
        invoke PostQuitMessage, 0
        xor eax, eax
        ret
    .ENDIF
    invoke DefWindowProc, hWin, uMsg, wParam, lParam
    ret
WndProc endp

InitGL proc
  LOCAL temp:DWORD
    invoke randomize
    invoke LoadGLTexture
    cmp eax, 0
    je end_init
    invoke glEnable, GL_TEXTURE_2D
    invoke glShadeModel, GL_SMOOTH
    _glClearColor 0.0f,0.0f,0.0f,0.5f
    _glClearDepth 1.0f
    invoke glHint, GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST
    invoke glBlendFunc, GL_SRC_ALPHA, GL_ONE
    invoke glEnable, GL_BLEND
    xor ecx, ecx
    .while (ecx < num)
        fld fzero
        mov eax, ecx
        mov edx, size_star
        mul edx
        push eax
        fstp star[eax].angle                ;star[ecx*size_star]=0
        mov temp, ecx
        fild temp
        mov temp, num
        fild temp
        fdivp st(1), st(0)
        fmul fcinq
        pop edx
        fstp star[edx].dist                 ;star[ecx*size_star]=(ecx/num)*5
        invoke randlimit, 255
        mov star[edx].r, eax
        invoke randlimit, 255
        mov star[edx].g, eax
        invoke randlimit, 255
        mov star[edx].b, eax
        inc ecx
    .endw
    mov eax, 1
   end_init:
    ret
InitGL endp

CreateGLWindow proc letitle:DWORD,w:DWORD,h:DWORD,bits:DWORD,flag:BYTE
  LOCAL PixelFormat:DWORD, wc:WNDCLASS
  LOCAL dwExStyle:DWORD, dwStyle:DWORD
  LOCAL rect:RECT, dmScreenSettings:DEVMODE
  LOCAL pfd:PIXELFORMATDESCRIPTOR
    mov rect.left, 0
    push w
    pop rect.right
    mov rect.top, 0
    push h
    pop rect.bottom
    mov al, flag
    mov fullscreen, al
    mov wc.style, CS_HREDRAW or CS_VREDRAW or CS_OWNDC
    mov wc.lpfnWndProc, offset WndProc
    mov wc.cbClsExtra, 0
    mov wc.cbWndExtra, 0
    push hInstance
    pop wc.hInstance
    mov wc.hIcon, 0
    invoke LoadCursor, 0, IDC_ARROW
    mov wc.hCursor, eax
    mov wc.lpszMenuName, 0
    mov wc.lpszClassName, offset ClassName
    mov wc.hbrBackground, 0
    invoke RegisterClass, ADDR wc
    .IF (!eax)
        xor eax, eax
        ret
    .ENDIF
    .IF (fullscreen)
        ZeroMemory &dmScreenSettings, sizeof(dmScreenSettings)
        mov dmScreenSettings.dmSize, sizeof dmScreenSettings
        push w
        pop dmScreenSettings.dmPelsWidth
        push h
        pop dmScreenSettings.dmPelsHeight
        push bits
        pop dmScreenSettings.dmBitsPerPel
        mov dmScreenSettings.dmFields, DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT
        invoke ChangeDisplaySettings, ADDR dmScreenSettings, CDS_FULLSCREEN
        .IF eax != DISP_CHANGE_SUCCESSFUL
            invoke MessageBox, 0, ADDR not_full, ADDR scalp, MB_YESNO or MB_ICONEXCLAMATION
            .IF eax == IDYES
                mov fullscreen, 0
            .ELSE
                invoke MessageBox, 0, ADDR close, ADDR error_str, MB_OK or MB_ICONSTOP
                xor eax, eax
                ret
            .ENDIF
        .ENDIF
    .ENDIF
    .IF (fullscreen)
        mov dwExStyle, WS_EX_APPWINDOW
        mov dwStyle, WS_POPUP or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
        invoke ShowCursor, 0
    .ELSE
        mov dwExStyle, WS_EX_APPWINDOW or WS_EX_WINDOWEDGE
        mov dwStyle, WS_OVERLAPPEDWINDOW or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
    .ENDIF
    invoke AdjustWindowRectEx, ADDR rect, dwStyle, 0, dwExStyle
    mov eax, rect.left
    sub rect.right, eax
    mov eax, rect.top
    sub rect.bottom, eax
    invoke CreateWindowEx, dwExStyle, ADDR ClassName, ADDR our_title, \
                           dwStyle,0,0, rect.right, rect.bottom, 0, 0, hInstance, 0
    mov hWnd, eax
    ZeroMemory &pfd, sizeof(PIXELFORMATDESCRIPTOR)
    mov pfd.nSize, sizeof PIXELFORMATDESCRIPTOR
    mov pfd.nVersion, 1
    mov pfd.dwFlags, PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER
    mov pfd.iPixelType, PFD_TYPE_RGBA
    mov eax, bits
    mov pfd.cColorBits, al
    mov pfd.cDepthBits, 16
    mov pfd.dwLayerMask, PFD_MAIN_PLANE
    invoke GetDC, hWnd
    mov hDC, eax
    invoke ChoosePixelFormat, hDC, ADDR pfd
    mov PixelFormat, eax
    invoke SetPixelFormat, hDC, PixelFormat, ADDR pfd
    invoke wglCreateContext, hDC
    mov hRC, eax
    invoke wglMakeCurrent, hDC, hRC
    invoke ShowWindow, hWnd, SW_SHOW
    invoke SetForegroundWindow, hWnd
    invoke SetFocus, hWnd
    invoke ReSizeGLScene, w, h
    invoke InitGL
    mov eax, 1
    ret
CreateGLWindow endp

ReSizeGLScene proc w:DWORD, h:DWORD             ;resize scene
  LOCAL ratio:GLdouble
    .IF h == 0
        mov h, 1                                ;prevent div by zero
    .ENDIF
    invoke glViewport, 0, 0, w, h               ;reset viewport
    invoke glMatrixMode, GL_PROJECTION          ;select projection matrix
    invoke glLoadIdentity                       ;reset it
    fild w
    fild h
    fdivp st(1), st(0)
    fstp ratio
    _gluPerspective 45.0f, ratio, 0.1f,100.0f
    invoke glMatrixMode, GL_MODELVIEW                   ;select modelview matrix
    invoke glLoadIdentity                               ;reset it
    ret
ReSizeGLScene endp

LoadGLTexture PROC
  LOCAL hBMP:DWORD, ImgInfo:BITMAP
    invoke glGenTextures, 1, ADDR texture
    invoke LoadImage, hInstance, IDD_BITMAP, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION
    cmp eax, 0
    je end_load
    mov hBMP, eax
    invoke GetObject, hBMP, sizeof BITMAP, ADDR ImgInfo
    invoke glBindTexture, GL_TEXTURE_2D, texture
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR
    invoke glTexImage2D, GL_TEXTURE_2D, 0, 3, ImgInfo.bmWidth, ImgInfo.bmHeight,\
                         0, GL_BGR_EXT, GL_UNSIGNED_BYTE, ImgInfo.bmBits
    mov eax, 1
   end_load:
    ret
LoadGLTexture ENDP

KillGLWindow proc
    .IF (fullscreen)                                ;if fullscreen mode
        invoke ChangeDisplaySettings, 0, 0          ;get back to window
        invoke ShowCursor, 1                        ;and show mouse ptr
    .ENDIF
    .IF (hRC)
        invoke wglMakeCurrent, 0, 0
        invoke wglDeleteContext, hRC
        mov hRC, 0
    .ENDIF
    invoke ReleaseDC, hWnd, hDC
    invoke DestroyWindow, hWnd
    invoke UnregisterClass, ADDR ClassName, hInstance
    ret
KillGLWindow endp

randlimit proc limit:DWORD
  mov eax, limit
  push edx
  imul edx,RandSeed,08088405H
  inc edx
  mov RandSeed, edx
  mul edx
  mov eax, edx
  pop edx
  ret
randlimit endp

randomize proc
  invoke GetTickCount
  mov RandSeed, eax
  ret
randomize endp

DrawGLScene proc
  LOCAL temp:GLfloat, temp2:DWORD, count:DWORD
    invoke glClear, GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT
    invoke glBindTexture, GL_TEXTURE_2D, texture
    mov count, 0
    .while count


Lesson 10

.586p
.MODEL FLAT,STDCALL
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\ogl\gl.def
include \masm32\include\ogl\glu.def
include \masm32\include\ogl\kernel32.inc
include \masm32\include\ogl\user32.inc
include \masm32\include\ogl\gdi32.inc
include \masm32\include\ogl\WinExtra\winextra.def
includelib \masm32\lib\opengl32.lib
includelib \masm32\lib\glu32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\gdi32.lib

CreateGLWindow  PROTO :DWORD,:DWORD,:DWORD,:DWORD,:BYTE
WinMain         PROTO :DWORD,:DWORD,:DWORD,:DWORD
WndProc         PROTO :DWORD,:DWORD,:DWORD,:DWORD
ReSizeGLScene   PROTO :DWORD,:DWORD
ReadALine       PROTO :DWORD,:DWORD
atodw           PROTO :DWORD
atof            PROTO :DWORD
KillGLWindow    PROTO
InitGL          PROTO
DrawGLScene     PROTO
LoadGLTexture   PROTO
SetupWorld      PROTO

VERTEX STRUCT
    x REAL4 ?
    y REAL4 ?
    z REAL4 ?
    u REAL4 ?
    v REAL4 ?
VERTEX ENDS

TRIANGLE STRUCT             ;just to know the size of TRIANGLE struct.
    vertex0 VERTEX
    vertex1 VERTEX
    vertex2 VERTEX
TRIANGLE ENDS

include include.def             ;macros and constants definition
IDD_BITMAP      equ 100

.DATA
piover180       GLfloat 0.0174532925f
zero25          GLfloat 0.25f
zero02          GLfloat 0.02f
zero05          GLfloat 0.05f
vingt           GLfloat 20.0f
un              GLfloat 1.0f
trois6          GLfloat 360.0f
worldfile       db "world.txt",0
scalp           db "(scalp)",0
our_title       db "Moving in a 3D world, by (scalp)",0
ClassName       db "asmOpenGL",0
error_str       db "ERROR",0
not_found       db "The file 'world.txt' should be in the current directory",0
not_full        db "The requested fullscreen mode is not supported by",13,10
                db "your video card. Use windowed mode instead ?",0
close           db "Program will now close",0
start_full      db "Would you like to run in fullscreen mode ?",0

.DATA?
hDC             HDC ?
hRC             HGLRC ?
hInstance       HANDLE ?
hWnd            dd ?
keys            db 256 dup (?)
active          db ?
fullscreen      db ?
blend           db ?
bpressed        db ?
fpressed        db ?
filter          GLuint  ?
texture         GLuint  ?
texture1        GLuint  ?
texture2        GLuint  ?
heading         GLfloat ?
xpos            GLfloat ?
zpos            GLfloat ?
yrot            GLfloat ?
numtriangles    dd ?
pTriangle       dd ?
buffer          db 265 dup(?)
walkbias        GLfloat ?
walkbiasangle   GLfloat ?
lookupdown      GLfloat ?
z               GLfloat ?

.CODE
start:
    invoke GetModuleHandle, 0
    mov hInstance, eax
    invoke WinMain, eax, 0, 0, 0
    invoke ExitProcess, eax

WinMain proc hInst:HWND, hPrevInst:UINT, CmdLine:WPARAM, CmdShow:LPARAM
  LOCAL msg:MSG, done:DWORD, temp:GLfloat
    invoke MessageBox, 0, ADDR start_full, ADDR our_title, MB_YESNO or MB_ICONQUESTION
    .IF eax == IDNO
        mov fullscreen, 0
    .ELSE
        mov fullscreen, 1
    .ENDIF
    invoke CreateGLWindow, ADDR our_title, 640, 480, 16, fullscreen
    .IF (!eax)
        ret
    .ENDIF
    mov done, 0
    .while (!done)
        invoke PeekMessage, ADDR msg,0,0,0,PM_REMOVE
        .IF (eax)
            .IF msg.message == WM_QUIT
                mov done, 1
            .ELSE
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
            .ENDIF
        .ELSE
            invoke DrawGLScene
            .IF ((active) && (!eax)) || (keys[VK_ESCAPE])
                mov done, 1
            .ELSE
                invoke SwapBuffers, hDC
            .ENDIF
            .IF (keys['F'] && !fpressed)
                mov fpressed, 1
                inc filter
                .IF filter > 2
                    mov filter, 0
                .ENDIF
            .ENDIF
            .IF (!keys['F'])
                mov fpressed, 0
            .ENDIF
            .IF (keys['B'] && !bpressed)
                mov bpressed, 1
                .IF (blend)
                    mov blend, 0
                .ELSE
                    mov blend, 1
                .ENDIF
                .IF (!blend)
                    invoke glDisable, GL_BLEND
                    invoke glEnable, GL_DEPTH_TEST
                .ELSE
                    invoke glEnable, GL_BLEND
                    invoke glDisable, GL_DEPTH_TEST
                .ENDIF
            .ENDIF
            .IF (!keys['B'])
                mov bpressed, 0
            .ENDIF
            .IF (keys[VK_RIGHT])
                fld heading
                fsub un
                fst heading
                fstp yrot
            .ENDIF
            .IF (keys[VK_LEFT])
                fld heading
                fadd un
                fst heading
                fstp yrot
            .ENDIF
            .IF (keys[VK_PRIOR])
                fld z
                fsub zero02
                fstp z
                fld lookupdown
                fsub un
                fstp lookupdown
            .ENDIF
            .IF (keys[VK_NEXT])
                fld z
                fadd zero02
                fstp z
                fld lookupdown
                fadd un
                fstp lookupdown
            .ENDIF
            .IF (keys[VK_UP])
                fld heading
                fmul piover180
                fsin
                fmul zero05
                fchs
                fadd xpos
                fstp xpos

                fld heading
                fmul piover180
                fcos
                fmul zero05
                fchs
                fadd zpos
                fstp zpos

                .IF walkbiasangle >= 359
                    mov walkbiasangle, 0
                .ELSE
                    add walkbiasangle, 10
                .ENDIF
                fild walkbiasangle
                fmul piover180
                fsin
                fld vingt
                fdivp st(1), st(0)
                fstp walkbias
            .ENDIF
            .IF (keys[VK_DOWN])
                fld heading
                fmul piover180
                fsin
                fmul zero05
                fadd xpos
                fstp xpos

                fld heading
                fmul piover180
                fcos
                fmul zero05
                fadd zpos
                fstp zpos

                .IF walkbiasangle <= 1
                    mov walkbiasangle, 359
                .ELSE
                    sub walkbiasangle, 10
                .ENDIF
                fild walkbiasangle
                fmul piover180
                fsin
                fld vingt
                fdivp st(1), st(0)
                fstp walkbias
            .ENDIF
            .IF (keys[VK_F1])
                mov keys[VK_F1], 0
                invoke KillGLWindow
                .IF (fullscreen)
                    mov fullscreen, 0
                .ELSE
                    mov fullscreen, 1
                .ENDIF
                invoke CreateGLWindow, ADDR our_title, 640,480,16,fullscreen
                .IF (!eax)
                    ret
                .ENDIF
            .ENDIF
        .ENDIF
    .endw
    invoke KillGLWindow
    invoke GlobalFree, pTriangle
    mov eax, msg.wParam
    ret
WinMain endp

WndProc proc hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    .IF uMsg == WM_ACTIVATE
        mov eax, wParam
        .IF (!ah)
            mov active, 1
        .ELSE
            mov active, 0
        .ENDIF
    .ELSEIF uMsg == WM_SYSCOMMAND
        .IF (wParam == SC_SCREENSAVE) || (wParam == SC_MONITORPOWER)
            xor eax, eax
            ret
        .ENDIF
    .ELSEIF uMsg == WM_KEYDOWN
        mov eax, wParam
        mov keys[eax], 1
    .ELSEIF uMsg == WM_KEYUP
        mov eax, wParam
        mov keys[eax], 0
    .ELSEIF uMsg == WM_SIZE
        mov eax, lParam
        mov ebx, eax
        and eax, 0ffffh         ;eax=width=LOWORD
        shr ebx, 16             ;ebx=height=HIWORD
        invoke ReSizeGLScene, eax, ebx
    .ELSEIF uMsg == WM_CLOSE
        invoke PostQuitMessage, 0
        xor eax, eax
        ret
    .ENDIF
    invoke DefWindowProc, hWin, uMsg, wParam, lParam
    ret
WndProc endp

InitGL proc
  LOCAL temp:DWORD
    invoke LoadGLTexture
    cmp eax, 0
    je end_init
    invoke glEnable, GL_TEXTURE_2D
    invoke glBlendFunc, GL_SRC_ALPHA, GL_ONE
    _glClearColor 0.0f,0.0f,0.0f,0.0f
    _glClearDepth 1.0f
    invoke glDepthFunc, GL_LESS
    invoke glEnable, GL_DEPTH_TEST
    invoke glShadeModel, GL_SMOOTH
    invoke glHint, GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST
    invoke SetupWorld
    cmp eax, 0
    je end_init
    mov eax, 1
   end_init:
    ret
InitGL endp

CreateGLWindow proc letitle:DWORD,w:DWORD,h:DWORD,bits:DWORD,flag:BYTE
  LOCAL PixelFormat:DWORD, wc:WNDCLASS
  LOCAL dwExStyle:DWORD, dwStyle:DWORD
  LOCAL rect:RECT, dmScreenSettings:DEVMODE
  LOCAL pfd:PIXELFORMATDESCRIPTOR
    mov rect.left, 0
    push w
    pop rect.right
    mov rect.top, 0
    push h
    pop rect.bottom
    mov al, flag
    mov fullscreen, al
    mov wc.style, CS_HREDRAW or CS_VREDRAW or CS_OWNDC
    mov wc.lpfnWndProc, offset WndProc
    mov wc.cbClsExtra, 0
    mov wc.cbWndExtra, 0
    push hInstance
    pop wc.hInstance
    mov wc.hIcon, 0
    invoke LoadCursor, 0, IDC_ARROW
    mov wc.hCursor, eax
    mov wc.lpszMenuName, 0
    mov wc.lpszClassName, offset ClassName
    mov wc.hbrBackground, 0
    invoke RegisterClass, ADDR wc
    .IF (!eax)
        xor eax, eax
        ret
    .ENDIF
    .IF (fullscreen)
        ZeroMemory &dmScreenSettings, sizeof(dmScreenSettings)
        mov dmScreenSettings.dmSize, sizeof dmScreenSettings
        push w
        pop dmScreenSettings.dmPelsWidth
        push h
        pop dmScreenSettings.dmPelsHeight
        push bits
        pop dmScreenSettings.dmBitsPerPel
        mov dmScreenSettings.dmFields, DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT
        invoke ChangeDisplaySettings, ADDR dmScreenSettings, CDS_FULLSCREEN
        .IF eax != DISP_CHANGE_SUCCESSFUL
            invoke MessageBox, 0, ADDR not_full, ADDR scalp, MB_YESNO or MB_ICONEXCLAMATION
            .IF eax == IDYES
                mov fullscreen, 0
            .ELSE
                invoke MessageBox, 0, ADDR close, ADDR error_str, MB_OK or MB_ICONSTOP
                xor eax, eax
                ret
            .ENDIF
        .ENDIF
    .ENDIF
    .IF (fullscreen)
        mov dwExStyle, WS_EX_APPWINDOW
        mov dwStyle, WS_POPUP or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
        invoke ShowCursor, 0
    .ELSE
        mov dwExStyle, WS_EX_APPWINDOW or WS_EX_WINDOWEDGE
        mov dwStyle, WS_OVERLAPPEDWINDOW or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
    .ENDIF
    invoke AdjustWindowRectEx, ADDR rect, dwStyle, 0, dwExStyle
    mov eax, rect.left
    sub rect.right, eax
    mov eax, rect.top
    sub rect.bottom, eax
    invoke CreateWindowEx, dwExStyle, ADDR ClassName, ADDR our_title, \
                           dwStyle,0,0, rect.right, rect.bottom, 0, 0, hInstance, 0
    mov hWnd, eax
    ZeroMemory &pfd, sizeof(PIXELFORMATDESCRIPTOR)
    mov pfd.nSize, sizeof PIXELFORMATDESCRIPTOR
    mov pfd.nVersion, 1
    mov pfd.dwFlags, PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER
    mov pfd.iPixelType, PFD_TYPE_RGBA
    mov eax, bits
    mov pfd.cColorBits, al
    mov pfd.cDepthBits, 16
    mov pfd.dwLayerMask, PFD_MAIN_PLANE
    invoke GetDC, hWnd
    mov hDC, eax
    invoke ChoosePixelFormat, hDC, ADDR pfd
    mov PixelFormat, eax
    invoke SetPixelFormat, hDC, PixelFormat, ADDR pfd
    invoke wglCreateContext, hDC
    mov hRC, eax
    invoke wglMakeCurrent, hDC, hRC
    invoke ShowWindow, hWnd, SW_SHOW
    invoke SetForegroundWindow, hWnd
    invoke SetFocus, hWnd
    invoke ReSizeGLScene, w, h
    invoke InitGL
    mov eax, 1
    ret
CreateGLWindow endp

ReSizeGLScene proc w:DWORD, h:DWORD             ;resize scene
  LOCAL ratio:GLdouble
    .IF h == 0
        inc h                                   ;prevent div by zero
    .ENDIF
    invoke glViewport, 0, 0, w, h               ;reset viewport
    invoke glMatrixMode, GL_PROJECTION          ;select projection matrix
    invoke glLoadIdentity                       ;reset it
    fild w
    fild h
    fdivp st(1), st(0)
    fstp ratio
    _gluPerspective 45.0f, ratio, 0.1f,100.0f
    invoke glMatrixMode, GL_MODELVIEW                   ;select modelview matrix
    invoke glLoadIdentity                               ;reset it
    ret
ReSizeGLScene endp

LoadGLTexture PROC
  LOCAL hBMP:DWORD, ImgInfo:BITMAP
    invoke glGenTextures, 3, ADDR texture
    invoke LoadImage, hInstance, IDD_BITMAP, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION
    cmp eax, 0
    je end_load
    mov hBMP, eax
    invoke GetObject, hBMP, sizeof BITMAP, ADDR ImgInfo
    invoke glBindTexture, GL_TEXTURE_2D, texture
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST
    invoke glTexImage2D, GL_TEXTURE_2D, 0, 3, ImgInfo.bmWidth, ImgInfo.bmHeight,\
                         0, GL_BGR_EXT, GL_UNSIGNED_BYTE, ImgInfo.bmBits
    invoke glBindTexture, GL_TEXTURE_2D, texture1
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR
    invoke glTexImage2D, GL_TEXTURE_2D, 0, 3, ImgInfo.bmWidth, ImgInfo.bmHeight,\
                         0, GL_BGR_EXT, GL_UNSIGNED_BYTE, ImgInfo.bmBits
    invoke glBindTexture, GL_TEXTURE_2D, texture2
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_NEAREST
    invoke glTexImage2D, GL_TEXTURE_2D, 0, 3, ImgInfo.bmWidth, ImgInfo.bmHeight,\
                         0, GL_BGR_EXT, GL_UNSIGNED_BYTE, ImgInfo.bmBits
    mov eax, 1
   end_load:
    ret
LoadGLTexture ENDP

KillGLWindow proc
    .IF (fullscreen)                                ;if fullscreen mode
        invoke ChangeDisplaySettings, 0, 0          ;get back to window
        invoke ShowCursor, 1                        ;and show mouse ptr
    .ENDIF
    .IF (hRC)
        invoke wglMakeCurrent, 0, 0
        invoke wglDeleteContext, hRC
        mov hRC, 0
    .ENDIF
    invoke ReleaseDC, hWnd, hDC
    invoke DestroyWindow, hWnd
    invoke UnregisterClass, ADDR ClassName, hInstance
    ret
KillGLWindow endp

DrawGLScene proc
  LOCAL x_m, y_m, z_m, u_m, v_m:GLfloat, count2:DWORD
  LOCAL xtrans:GLfloat, ytrans:GLfloat, ztrans:GLfloat
  LOCAL sceneroty:GLfloat, count:DWORD
    push esi
    invoke glClear, GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT
    invoke glLoadIdentity
    fld xpos
    fchs
    fstp xtrans
    fld walkbias
    fchs
    fsub zero25
    fstp ytrans
    fld zpos
    fchs
    fstp ztrans
    fld yrot
    fchs
    fadd trois6
    fstp sceneroty
    _glRotatef lookupdown,1.0f,0.0f,0.0f
    _glRotatef sceneroty,0.0f,1.0f,0.0f
    _glTranslatef xtrans,ytrans,ztrans
    mov eax, offset texture
    .IF filter == 1
        add eax, 4
    .ELSEIF filter == 2
        add eax, 8
    .ENDIF
    invoke glBindTexture, GL_TEXTURE_2D,DWORD PTR [eax]
    mov count, 0
    xor eax, eax
    mov esi, pTriangle
    .while eax < numtriangles
        push esi
        invoke glBegin, GL_TRIANGLES
        _glNormal3f 0.0f, 0.0f, 1.0f
        pop esi
        mov count2, 0
        .while count2 < 3
            fld GLfloat PTR [esi]
            fstp x_m
            add esi, sizeof GLfloat
            fld GLfloat PTR [esi]
            fstp y_m
            add esi, sizeof GLfloat
            fld GLfloat PTR [esi]
            fstp z_m
            add esi, sizeof GLfloat
            fld GLfloat PTR [esi]
            fstp u_m
            add esi, sizeof GLfloat
            fld GLfloat PTR [esi]
            fstp v_m
            add esi, sizeof GLfloat
            push esi
            _glTexCoord2f u_m, v_m
            _glVertex3f x_m, y_m,z_m
            pop esi
            inc count2
        .endw
        push esi
        invoke glEnd
        pop esi
        inc count
        mov eax, count
    .endw
    pop esi
    mov eax, 1
    ret
DrawGLScene endp

SetupWorld proc
  LOCAL FileHdl:DWORD, file_size:DWORD, pMemory:DWORD, count:DWORD
  LOCAL bytes_read:DWORD, pointer:DWORD, count2:DWORD, vert:DWORD
    push edi
    push esi
    invoke CreateFile, ADDR worldfile, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0
    .IF (eax == INVALID_HANDLE_VALUE)
        invoke MessageBox, 0, ADDR not_found, ADDR error_str, MB_OK or MB_ICONEXCLAMATION
        xor eax, eax
        pop esi
        pop edi
        ret
    .ENDIF
    mov FileHdl, eax
    invoke GetFileSize, eax, 0
    inc eax
    mov file_size, eax
    invoke GlobalAlloc, GPTR, file_size
    mov pMemory, eax
    dec file_size
    invoke ReadFile, FileHdl, pMemory, file_size, ADDR bytes_read, 0
    invoke CloseHandle, FileHdl
    push pMemory
    pop pointer
    xor eax, eax
    .while eax == 0
        invoke ReadALine, pointer, ADDR buffer
        add pointer, ecx
    .endw
    mov eax, offset buffer
    .IF (BYTE PTR [eax] == 'N')&&(BYTE PTR [eax+1] == 'U')&&(BYTE PTR [eax+2] == 'M')
        add eax, 11
    .ENDIF
    invoke atodw, eax
    mov numtriangles, eax
    mov ebx, sizeof TRIANGLE
    mul ebx
    invoke GlobalAlloc, GPTR, eax
    mov pTriangle, eax
    mov count, 0
    mov esi, eax
    xor eax, eax
    .while (eax < numtriangles)
        mov vert, 0
        .while (vert < 3)
            mov count2, 0
            xor eax, eax
            .while eax == 0
                invoke ReadALine, pointer, ADDR buffer
                add pointer, ecx
            .endw
            mov edi, offset buffer
            .while (count2 < 5)
                .while (BYTE PTR [edi] == ' ')
                    inc edi
                .endw
                invoke atof, edi
                fstp (GLfloat PTR [esi])
                add edi, eax
                add esi, sizeof GLfloat
                inc count2
            .endw
            inc vert
        .endw
        inc count
        mov eax, count
    .endw
    invoke GlobalFree, pMemory
    mov eax, 1
   end_setup:
    pop esi
    pop edi
    ret
SetupWorld endp

ReadALine proc pSource:DWORD, pDest:DWORD               ;eax = nb of significant bytes (0 = nothing)
  LOCAL cpy:BYTE                                        ;ecx = nb of bytes read (should be add to pointer)
    push esi
    push edi
    mov esi, 40000
    mov cpy, 1
    mov eax, pSource
    mov edx, pDest
    xor ecx, ecx
    .WHILE (BYTE PTR [eax+ecx] != 13)
        .IF (BYTE PTR [eax+ecx] == '/')
            mov cpy, 0
            .IF esi == 40000
                mov esi, ecx
            .ENDIF
        .ENDIF
        .IF (cpy)
            mov bl, BYTE PTR [eax+ecx]
            mov BYTE PTR [edx+ecx], bl
        .ENDIF
        inc ecx
    .ENDW
    mov BYTE PTR [edx+ecx], 0
    mov eax, ecx
    inc ecx
    inc ecx
    .IF esi == 0
        xor eax, eax
    .ENDIF
    pop edi
    pop esi
    ret
ReadALine endp

atodw proc ptrString:DWORD
  ;----------------------------------------
  ; Convert decimal string into dword value
  ; return value in eax
  ; by Tim Roberts (modified by me)
  ;----------------------------------------
    xor eax, eax
    mov esi, ptrString
    xor ecx, ecx
    xor edx, edx
    mov al, BYTE PTR [esi]
    inc esi
    .if al == '-'
        mov edx, -1
      mov al, BYTE PTR [esi]
      inc esi
    .endif
    .while al != 0
      sub al, '0'	; convert to bcd
      lea ecx, [ecx+ecx*4]	; ecx = ecx * 5
      lea ecx, [eax+ecx*2]	; ecx = eax + old ecx * 10
      mov al, BYTE PTR [esi]
      inc esi
    .endw
    lea eax, [ecx+edx]	; move to eax and negate
    xor eax, edx
    ret
atodw endp

atof proc ptrString:DWORD
  ;----------------------------------------
  ;convert a string containing a float to
  ;a simple precision float number returned
  ;in st(0), number of bytes read + 1
  ;returned in eax, made by (scalp)
  ;----------------------------------------
  LOCAL sign:BYTE, entiere:DWORD, decimal:DWORD, precision:DWORD, temp:REAL4
    push esi
    push edi
    xor eax, eax
    mov esi, ptrString
    mov al, BYTE PTR [esi]
    cmp al, 0
    je fini0
    mov sign, 0
    .IF al == '+'
        inc esi
    .ELSEIF al == '-'
        mov sign, 1
        inc esi
    .ENDIF
    xor ecx, ecx
    mov al, BYTE PTR [esi]
    .while al != '.'
      sub al, '0'
      lea ecx, [ecx+ecx*4]
      lea ecx, [eax+ecx*2]
      inc esi
      mov al, BYTE PTR [esi]
    .endw
    mov entiere, ecx
    xor ecx, ecx
    mov ebx, 1
    inc esi
    mov al, BYTE PTR [esi]
    .while ((al != ' ')&&(al != 0))
      sub al, '0'
      lea ecx, [ecx+ecx*4]
      lea ecx, [eax+ecx*2]
      inc esi
        imul ebx, 10
      mov al, BYTE PTR [esi]
    .endw
    sub esi, ptrString
    mov eax, esi
    mov decimal, ecx
    mov precision, ebx
    fild decimal
    fild precision
    fdivp st(1), st(0)
    fstp temp
    fild entiere
    fadd temp
    .IF (sign == 1)
        fchs
    .ENDIF
    jmp fini
  fini0:
    xor eax, eax
  fini:
    pop edi
    pop esi
    ret
atof endp

END start


Lesson 11

;i know i don't do so much comments... so for this
;tuts i tried to do some (not much). If you have
;any question, mail me ! (scalp@bigfoot.com).

.586p
.MODEL FLAT,STDCALL
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\ogl\gl.def
include \masm32\include\ogl\glu.def
include \masm32\include\ogl\kernel32.inc
include \masm32\include\ogl\user32.inc
include \masm32\include\ogl\gdi32.inc
include \masm32\include\ogl\WinExtra\winextra.def
includelib \masm32\lib\opengl32.lib
includelib \masm32\lib\glu32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\gdi32.lib

CreateGLWindow  PROTO :DWORD,:DWORD,:DWORD,:DWORD,:BYTE
WinMain         PROTO :DWORD,:DWORD,:DWORD,:DWORD
WndProc         PROTO :DWORD,:DWORD,:DWORD,:DWORD
ReSizeGLScene   PROTO :DWORD,:DWORD
KillGLWindow    PROTO
InitGL          PROTO
DrawGLScene     PROTO
LoadGLTexture   PROTO

include include.def
IDD_BITMAP      equ 100
SIZEOF_LINE     equ 45

.DATA
cinq            GLfloat 5.0f
quatre5         GLfloat 4.5f
quarante4       GLfloat 44.0f
zero2           GLfloat 0.2f
zero3           GLfloat 0.3f
zero4           GLfloat 0.4f
vingt25         GLfloat 22.5f
scalp           db "(scalp)",0
our_title       db "OpenGL Flag Effect, by (scalp)",0
ClassName       db "asmOpenGL",0
error_str       db "ERROR",0
not_full        db "The requested fullscreen mode is not supported by",13,10
                db "your video card. Use windowed mode instead ?",0
close           db "Program will now close",0
start_full      db "Would you like to run in fullscreen mode ?",0
wiggle_count    db 0

.data?
hDC             HDC ?
hRC             HGLRC ?
hInstance       HANDLE ?
keys            db 256 dup (?)
active          db ?
fullscreen      db ?
hWnd            dd ?
points          GLfloat (SIZEOF_LINE*SIZEOF_LINE*3) dup(?)
hold            GLfloat ?
xrot            GLfloat ?
yrot            GLfloat ?
zrot            GLfloat ?
texture         GLuint  ?

.CODE
start:
    invoke GetModuleHandle, 0
    mov hInstance, eax
    invoke WinMain, eax, 0, 0, 0
    invoke ExitProcess, eax

WinMain proc hInst:HWND, hPrevInst:UINT, CmdLine:WPARAM, CmdShow:LPARAM
  LOCAL msg:MSG, done:DWORD
    invoke MessageBox, 0, ADDR start_full, ADDR our_title, MB_YESNO or MB_ICONQUESTION
    .IF eax == IDNO
        mov fullscreen, 0
    .ELSE
        mov fullscreen, 1
    .ENDIF
    invoke CreateGLWindow, ADDR our_title, 640, 480, 16, fullscreen
    .IF (!eax)
        ret
    .ENDIF
    mov done, 0
    .while (!done)
        invoke PeekMessage, ADDR msg,0,0,0,PM_REMOVE
        .IF (eax)
            .IF msg.message == WM_QUIT
                mov done, 1
            .ELSE
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
            .ENDIF
        .ELSE
            invoke DrawGLScene          ;here we do all the drawing
            .IF ((active) && (!eax)) || (keys[VK_ESCAPE])
                mov done, 1
            .ELSE
                invoke SwapBuffers, hDC
            .ENDIF
            .IF (keys[VK_F1])
                mov keys[VK_F1], 0      ;F1 pressed ?
                invoke KillGLWindow
                .IF (fullscreen)        ;fullscreen = not(fullscreen)
                    mov fullscreen, 0
                .ELSE
                    mov fullscreen, 1
                .ENDIF
                invoke CreateGLWindow, ADDR our_title, 640,480,16,fullscreen
                .IF (!eax)
                    ret
                .ENDIF
            .ENDIF
        .ENDIF
    .endw
    invoke KillGLWindow
    mov eax, msg.wParam
    ret
WinMain endp

WndProc proc hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    .IF uMsg == WM_ACTIVATE
        mov eax, wParam
        .IF (!ah)
            mov active, 1
        .ELSE
            mov active, 0
        .ENDIF
    .ELSEIF uMsg == WM_SYSCOMMAND       ;to avoid scr from running
        .IF (wParam == SC_SCREENSAVE) || (wParam == SC_MONITORPOWER)
            xor eax, eax
            ret
        .ENDIF
    .ELSEIF uMsg == WM_KEYDOWN
        mov eax, wParam
        mov keys[eax], 1                ;update our key array
    .ELSEIF uMsg == WM_KEYUP
        mov eax, wParam
        mov keys[eax], 0                ;update our key array
    .ELSEIF uMsg == WM_SIZE
        mov eax, lParam
        mov ebx, eax
        and eax, 0ffffh                 ;eax=width=LOWORD
        shr ebx, 16                     ;ebx=height=HIWORD
        invoke ReSizeGLScene, eax, ebx  ;resize our window
    .ELSEIF uMsg == WM_CLOSE
        invoke PostQuitMessage, 0       ;quit...
        xor eax, eax
        ret
    .ENDIF
    invoke DefWindowProc, hWin, uMsg, wParam, lParam    ;gives other msg to windoze
    ret
WndProc endp

InitGL proc
  LOCAL loop_x:DWORD, loop_y:DWORD
    invoke LoadGLTexture                    ;load the bitmap
    cmp eax, 0                              ;works ?
    je end_init
    invoke glEnable, GL_TEXTURE_2D          ;enable texture mapping (the img is 2D)
    invoke glShadeModel, GL_SMOOTH          ;smooth shading
    _glClearColor 0.0f,0.0f,0.0f,0.5f       ;make a black backgroung
    _glClearDepth 1.0f
    invoke glEnable, GL_DEPTH_TEST          ;enable depth
    invoke glDepthFunc, GL_LEQUAL
    invoke glHint, GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST
    invoke glPolygonMode, GL_BACK, GL_FILL  ;back face is solid...
    invoke glPolygonMode, GL_FRONT, GL_LINE ;...and front is grid
    mov loop_x, 0
    .while loop_x < SIZEOF_LINE             ;we do a int loop unlike Bosco
        mov loop_y, 0
        .while loop_y < SIZEOF_LINE
            fild loop_x
            fdiv cinq
            fsub quatre5
            mov eax, SIZEOF_LINE
            mul loop_y
            add eax, loop_x
            mov ebx, sizeof GLfloat
            mul ebx                         ;now eax=[loop_x][loop_y][0]
            fstp points[eax]                ;points[loop_x][loop_y][0]=x/5.0-4.5

            fild loop_y
            fdiv cinq
            fsub quatre5
            add eax, (SIZEOF_LINE*SIZEOF_LINE*(sizeof GLfloat))
            fstp points[eax]                ;points[loop_x][loop_y][1]=y/5.0-4.5

            fldpi                           ;load Pi
            fdiv vingt25
            fimul loop_x
            fsin
            add eax, (SIZEOF_LINE*SIZEOF_LINE*(sizeof GLfloat))
            fstp points[eax]                ;points[loop_x][loop_y][2]=sin(x/5*40/360 * Pi * 2)

            inc loop_y
        .endw
        inc loop_x
    .endw
    mov eax, 1
   end_init:
    ret
InitGL endp

CreateGLWindow proc letitle:DWORD,w:DWORD,h:DWORD,bits:DWORD,flag:BYTE
  LOCAL PixelFormat:DWORD, wc:WNDCLASS
  LOCAL dwExStyle:DWORD, dwStyle:DWORD
  LOCAL rect:RECT, dmScreenSettings:DEVMODE
  LOCAL pfd:PIXELFORMATDESCRIPTOR
    mov rect.left, 0
    push w
    pop rect.right
    mov rect.top, 0
    push h
    pop rect.bottom
    mov al, flag
    mov fullscreen, al
    mov wc.style, CS_HREDRAW or CS_VREDRAW or CS_OWNDC
    mov wc.lpfnWndProc, offset WndProc
    mov wc.cbClsExtra, 0
    mov wc.cbWndExtra, 0
    push hInstance
    pop wc.hInstance
    mov wc.hIcon, 0
    invoke LoadCursor, 0, IDC_ARROW
    mov wc.hCursor, eax
    mov wc.lpszMenuName, 0
    mov wc.lpszClassName, offset ClassName
    mov wc.hbrBackground, 0
    invoke RegisterClass, ADDR wc
    .IF (!eax)
        xor eax, eax
        ret
    .ENDIF
    .IF (fullscreen)
        ZeroMemory &dmScreenSettings, sizeof(dmScreenSettings)
        mov dmScreenSettings.dmSize, sizeof dmScreenSettings
        push w
        pop dmScreenSettings.dmPelsWidth
        push h
        pop dmScreenSettings.dmPelsHeight
        push bits
        pop dmScreenSettings.dmBitsPerPel
        mov dmScreenSettings.dmFields, DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT
        invoke ChangeDisplaySettings, ADDR dmScreenSettings, CDS_FULLSCREEN
        .IF eax != DISP_CHANGE_SUCCESSFUL
            invoke MessageBox, 0, ADDR not_full, ADDR scalp, MB_YESNO or MB_ICONEXCLAMATION
            .IF eax == IDYES
                mov fullscreen, 0
            .ELSE
                invoke MessageBox, 0, ADDR close, ADDR error_str, MB_OK or MB_ICONSTOP
                xor eax, eax
                ret
            .ENDIF
        .ENDIF
    .ENDIF
    .IF (fullscreen)
        mov dwExStyle, WS_EX_APPWINDOW
        mov dwStyle, WS_POPUP or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
        invoke ShowCursor, 0
    .ELSE
        mov dwExStyle, WS_EX_APPWINDOW or WS_EX_WINDOWEDGE
        mov dwStyle, WS_OVERLAPPEDWINDOW or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
    .ENDIF
    invoke AdjustWindowRectEx, ADDR rect, dwStyle, 0, dwExStyle
    mov eax, rect.left
    sub rect.right, eax
    mov eax, rect.top
    sub rect.bottom, eax
    invoke CreateWindowEx, dwExStyle, ADDR ClassName, ADDR our_title, \
                           dwStyle,0,0, rect.right, rect.bottom, 0, 0, hInstance, 0
    mov hWnd, eax
    ZeroMemory &pfd, sizeof(PIXELFORMATDESCRIPTOR)
    mov pfd.nSize, sizeof PIXELFORMATDESCRIPTOR
    mov pfd.nVersion, 1
    mov pfd.dwFlags, PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER
    mov pfd.iPixelType, PFD_TYPE_RGBA
    mov eax, bits
    mov pfd.cColorBits, al
    mov pfd.cDepthBits, 16
    mov pfd.dwLayerMask, PFD_MAIN_PLANE
    invoke GetDC, hWnd
    mov hDC, eax
    invoke ChoosePixelFormat, hDC, ADDR pfd
    mov PixelFormat, eax
    invoke SetPixelFormat, hDC, PixelFormat, ADDR pfd
    invoke wglCreateContext, hDC
    mov hRC, eax
    invoke wglMakeCurrent, hDC, hRC
    invoke ShowWindow, hWnd, SW_SHOW
    invoke SetForegroundWindow, hWnd
    invoke SetFocus, hWnd
    invoke ReSizeGLScene, w, h
    invoke InitGL
    cmp eax, 0
    je end_create
    mov eax, 1
   end_create:
    ret
CreateGLWindow endp

ReSizeGLScene proc w:DWORD, h:DWORD             ;resize scene
  LOCAL ratio:GLdouble
    .IF h == 0
        mov h, 1                                ;prevent div by zero
    .ENDIF
    invoke glViewport, 0, 0, w, h               ;reset viewport
    invoke glMatrixMode, GL_PROJECTION          ;select projection matrix
    invoke glLoadIdentity                       ;reset it
    fild w
    fild h
    fdivp st(1), st(0)
    fstp ratio
    _gluPerspective 45.0f, ratio, 0.1f,100.0f   ;calculate aspect ratio
    invoke glMatrixMode, GL_MODELVIEW           ;select modelview matrix
    invoke glLoadIdentity                       ;reset it
    ret
ReSizeGLScene endp

LoadGLTexture PROC
  LOCAL hBMP:DWORD, ImgInfo:BITMAP
    invoke glGenTextures, 1, ADDR texture
    invoke LoadImage, hInstance, IDD_BITMAP, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION
    cmp eax, 0
    je end_load
    mov hBMP, eax
    invoke GetObject, hBMP, sizeof BITMAP, ADDR ImgInfo
    invoke glBindTexture, GL_TEXTURE_2D, texture
    invoke glTexImage2D, GL_TEXTURE_2D, 0, 3, ImgInfo.bmWidth, ImgInfo.bmHeight,\
                         0, GL_BGR_EXT, GL_UNSIGNED_BYTE, ImgInfo.bmBits
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR
    mov eax, 1
   end_load:
    ret
LoadGLTexture ENDP

KillGLWindow proc
    .IF (fullscreen)                                ;if fullscreen mode
        invoke ChangeDisplaySettings, 0, 0          ;get back to window
        invoke ShowCursor, 1                        ;and show mouse ptr
    .ENDIF
    .IF (hRC)
        invoke wglMakeCurrent, 0, 0
        invoke wglDeleteContext, hRC
        mov hRC, 0
    .ENDIF
    invoke ReleaseDC, hWnd, hDC
    invoke DestroyWindow, hWnd
    invoke UnregisterClass, ADDR ClassName, hInstance
    ret
KillGLWindow endp

DrawGLScene proc
  LOCAL fx,fy,fx2,fy2:GLfloat
  LOCAL x,y:DWORD
    invoke glClear, GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT          ;clear screen before drawing
    invoke glLoadIdentity                                               ;reset view
    _glTranslatef 0.0f,0.0f,-12.0f
    _glRotatef xrot,1.0f,0.0f,0.0f
    _glRotatef yrot,0.0f,1.0f,0.0f
    _glRotatef zrot,0.0f,0.0f,1.0f
    invoke glBindTexture, GL_TEXTURE_2D, texture
    invoke glBegin, GL_QUADS
    mov x, 0
    .while x < 44
        mov y, 0
        .while y < 44
            fild x
            fdiv quarante4
            fstp fx             ;fx = float(x)/44.0

            fild y
            fdiv quarante4
            fstp fy             ;fy = float(y)/44.0

            inc x
            inc y
            fild x
            fdiv quarante4
            fstp fx2            ;fx2 = float(x+1)/44.0
            fild y
            fdiv quarante4
            fstp fy2            ;fy2 = float(y+1)/44.0
            dec y
            dec x

            _glTexCoord2f fx, fy
            mov eax, SIZEOF_LINE                                    ;size of a line in our array
            mul y
            add eax, x                                              ;position = y*size_line + x
            mov ebx, sizeof GLfloat
            mul ebx                                                 ;we are working with float
            mov ebx, eax                                            ;eax=[x][y][0] (if GLfloat)
            add ebx, (SIZEOF_LINE*SIZEOF_LINE*(sizeof GLfloat))
            mov ecx, ebx                                            ;ebx=[x][y][1]
            add ecx, (SIZEOF_LINE*SIZEOF_LINE*(sizeof GLfloat))     ;ecx=[x][y][2]
            _glVertex3f points[eax], points[ebx], points[ecx]

            _glTexCoord2f fx, fy2
            inc y
            mov eax, SIZEOF_LINE
            mul y
            add eax, x
            mov ebx, sizeof GLfloat
            mul ebx
            mov ebx, eax
            add ebx, (SIZEOF_LINE*SIZEOF_LINE*(sizeof GLfloat))
            mov ecx, ebx
            add ecx, (SIZEOF_LINE*SIZEOF_LINE*(sizeof GLfloat))
            _glVertex3f points[eax], points[ebx], points[ecx]

            _glTexCoord2f fx2, fy2
            inc x
            mov eax, SIZEOF_LINE
            mul y
            add eax, x
            mov ebx, sizeof GLfloat
            mul ebx
            mov ebx, eax
            add ebx, (SIZEOF_LINE*SIZEOF_LINE*(sizeof GLfloat))
            mov ecx, ebx
            add ecx, (SIZEOF_LINE*SIZEOF_LINE*(sizeof GLfloat))
            _glVertex3f points[eax], points[ebx], points[ecx]

            _glTexCoord2f fx2, fy
            dec y
            mov eax, SIZEOF_LINE
            mul y
            add eax, x
            mov ebx, sizeof GLfloat
            mul ebx
            mov ebx, eax
            add ebx, (SIZEOF_LINE*SIZEOF_LINE*(sizeof GLfloat))
            mov ecx, ebx
            add ecx, (SIZEOF_LINE*SIZEOF_LINE*(sizeof GLfloat))
            _glVertex3f points[eax], points[ebx], points[ecx]

            dec x
            inc y           ;y++ for the loop
        .endw
        inc x               ;x++ for the loop
    .endw
    invoke glEnd
    .IF wiggle_count == 2   ;trick to slow down animation (!)
        mov y, 0
        .WHILE (y < SIZEOF_LINE)
            mov eax, SIZEOF_LINE*(sizeof GLfloat)
            mul y
            add eax, (SIZEOF_LINE*SIZEOF_LINE*(sizeof GLfloat)*2)
            fld points[eax]
            fstp hold
            mov x, 0
            .WHILE (x < 44)
                mov eax, SIZEOF_LINE
                mul y
                add eax, x
                mov ebx, (sizeof GLfloat)
                mul ebx
                add eax, (SIZEOF_LINE*SIZEOF_LINE*(sizeof GLfloat)*2)
                mov ebx, eax
                add ebx, (sizeof GLfloat)
                fld points[ebx]
                fstp points[eax]
                inc x
            .ENDW
            mov eax, SIZEOF_LINE
            mul y
            add eax, 44             ; 44 = x
            mov ebx, sizeof GLfloat
            mul ebx
            add eax, (SIZEOF_LINE*SIZEOF_LINE*(sizeof GLfloat)*2)
            fld hold
            fstp points[eax]
            inc y
        .ENDW
        mov wiggle_count, 0
    .ENDIF
    inc wiggle_count

    fld xrot
    fadd zero3
    fstp xrot           ;xrot+=0.3f

    fld yrot
    fadd zero2
    fstp yrot           ;yrot+=0.2f

    fld zrot
    fadd zero4
    fstp zrot           ;zrot+=0.4f

    mov eax, 1
    ret
DrawGLScene endp

END start


Lesson 12

;i know i don't do so much comments... so for this
;tuts i tried to do some (not much). If you have
;any question, mail me ! (scalp@bigfoot.com).

.586p
.MODEL FLAT,STDCALL
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\ogl\gl.def
include \masm32\include\ogl\glu.def
include \masm32\include\ogl\kernel32.inc
include \masm32\include\ogl\user32.inc
include \masm32\include\ogl\gdi32.inc
include \masm32\include\ogl\WinExtra\winextra.def
includelib \masm32\lib\opengl32.lib
includelib \masm32\lib\glu32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\gdi32.lib

CreateGLWindow  PROTO :DWORD,:DWORD,:DWORD,:DWORD,:BYTE
WinMain         PROTO :DWORD,:DWORD,:DWORD,:DWORD
WndProc         PROTO :DWORD,:DWORD,:DWORD,:DWORD
ReSizeGLScene   PROTO :DWORD,:DWORD
KillGLWindow    PROTO
InitGL          PROTO
DrawGLScene     PROTO
LoadGLTexture   PROTO
BuildLists      PROTO

include include.def
IDD_BITMAP      equ 100

.DATA
scalp           db "(scalp)",0
our_title       db "Display List, converted in asm by (scalp)",0
ClassName       db "asmOpenGL",0
error_str       db "ERROR",0
not_full        db "The requested fullscreen mode is not supported by",13,10
                db "your video card. Use windowed mode instead ?",0
close           db "Program will now close",0
start_full      db "Would you like to run in fullscreen mode ?",0
boxcol          GLfloat 1.0f,0.0f,0.0f,1.0f,0.5f,0.0f,1.0f,1.0f,0.0f,0.0f,1.0f,0.0f,0.0f,1.0f,1.0f
topcol          GLfloat 0.5f,0.0f,0.0f,0.5f,0.25f,0.0f,0.5f,0.5f,0.0f,0.0f,0.5f,0.0f,0.0f,0.5f,0.5f
deux            GLfloat 2.0f
deux4           GLfloat 2.4f
deux8           GLfloat 2.8f
six             GLfloat 6.0f
sept            GLfloat 7.0f
moins20         REAL4 -20.0f
quarante5       GLfloat 45.0f
un              GLfloat 1.0f
un4             GLfloat 1.4f
zero2           GLfloat 0.2f

.data?
hDC             HDC ?
hRC             HGLRC ?
hInstance       HANDLE ?
keys            db 256 dup (?)
active          db ?
fullscreen      db ?
hWnd            dd ?
texture         GLuint  ?
box             GLuint  ?
top             GLuint  ?
xloop           GLuint  ?
yloop           GLuint  ?
xrot            GLfloat ?
yrot            GLfloat ?

.CODE
start:
    invoke GetModuleHandle, 0
    mov hInstance, eax
    invoke WinMain, eax, 0, 0, 0
    invoke ExitProcess, eax

WinMain proc hInst:HWND, hPrevInst:UINT, CmdLine:WPARAM, CmdShow:LPARAM
  LOCAL msg:MSG, done:DWORD
    invoke MessageBox, 0, ADDR start_full, ADDR our_title, MB_YESNO or MB_ICONQUESTION
    .IF eax == IDNO
        mov fullscreen, 0
    .ELSE
        mov fullscreen, 1
    .ENDIF
    invoke CreateGLWindow, ADDR our_title, 640, 480, 16, fullscreen
    .IF (!eax)
        ret
    .ENDIF
    mov done, 0
    .while (!done)
        invoke PeekMessage, ADDR msg,0,0,0,PM_REMOVE
        .IF (eax)
            .IF msg.message == WM_QUIT
                mov done, 1
            .ELSE
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
            .ENDIF
        .ELSE
            invoke DrawGLScene          ;here we do all the drawing
            .IF ((active) && (!eax)) || (keys[VK_ESCAPE])
                mov done, 1
            .ELSE
                invoke SwapBuffers, hDC
            .ENDIF
            .IF (keys[VK_F1])
                mov keys[VK_F1], 0      ;F1 pressed ?
                invoke KillGLWindow
                .IF (fullscreen)        ;fullscreen = not(fullscreen)
                    mov fullscreen, 0
                .ELSE
                    mov fullscreen, 1
                .ENDIF
                invoke CreateGLWindow, ADDR our_title, 640,480,16,fullscreen
                .IF (!eax)
                    ret
                .ENDIF
            .ENDIF
            .IF (keys[VK_LEFT])
                fld yrot
                fsub zero2
                fstp yrot
            .ENDIF
            .IF (keys[VK_RIGHT])
                fld yrot
                fadd zero2
                fstp yrot
            .ENDIF
            .IF (keys[VK_UP])
                fld xrot
                fsub zero2
                fstp xrot
            .ENDIF
            .IF (keys[VK_DOWN])
                fld xrot
                fadd zero2
                fstp xrot
            .ENDIF
        .ENDIF
    .endw
    invoke KillGLWindow
    mov eax, msg.wParam
    ret
WinMain endp

WndProc proc hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    .IF uMsg == WM_ACTIVATE
        mov eax, wParam
        .IF (!ah)
            mov active, 1
        .ELSE
            mov active, 0
        .ENDIF
    .ELSEIF uMsg == WM_SYSCOMMAND       ;to avoid scr from running
        .IF (wParam == SC_SCREENSAVE) || (wParam == SC_MONITORPOWER)
            xor eax, eax
            ret
        .ENDIF
    .ELSEIF uMsg == WM_KEYDOWN
        mov eax, wParam
        mov keys[eax], 1                ;update our key array
    .ELSEIF uMsg == WM_KEYUP
        mov eax, wParam
        mov keys[eax], 0                ;update our key array
    .ELSEIF uMsg == WM_SIZE
        mov eax, lParam
        mov ebx, eax
        and eax, 0ffffh                 ;eax=width=LOWORD
        shr ebx, 16                     ;ebx=height=HIWORD
        invoke ReSizeGLScene, eax, ebx  ;resize our window
    .ELSEIF uMsg == WM_CLOSE
        invoke PostQuitMessage, 0       ;quit...
        xor eax, eax
        ret
    .ENDIF
    invoke DefWindowProc, hWin, uMsg, wParam, lParam    ;gives other msg to windoze
    ret
WndProc endp

InitGL proc
  LOCAL loop_x:DWORD, loop_y:DWORD
    invoke LoadGLTexture                    ;load the bitmap
    cmp eax, 0                              ;works ?
    je end_init
    invoke BuildLists
    invoke glEnable, GL_TEXTURE_2D          ;enable texture mapping (the img is 2D)
    invoke glShadeModel, GL_SMOOTH          ;smooth shading
    _glClearColor 0.0f,0.0f,0.0f,0.5f       ;make a black backgroung
    _glClearDepth 1.0f
    invoke glEnable, GL_DEPTH_TEST          ;enable depth
    invoke glDepthFunc, GL_LEQUAL
    invoke glEnable, GL_LIGHT0              ;dirty lighting
    invoke glEnable, GL_LIGHTING            ;enable lighting
    invoke glEnable, GL_COLOR_MATERIAL      ;enable material coloring
    invoke glHint, GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST
    mov eax, 1
   end_init:
    ret
InitGL endp

CreateGLWindow proc letitle:DWORD,w:DWORD,h:DWORD,bits:DWORD,flag:BYTE
  LOCAL PixelFormat:DWORD, wc:WNDCLASS
  LOCAL dwExStyle:DWORD, dwStyle:DWORD
  LOCAL rect:RECT, dmScreenSettings:DEVMODE
  LOCAL pfd:PIXELFORMATDESCRIPTOR
    mov rect.left, 0
    push w
    pop rect.right
    mov rect.top, 0
    push h
    pop rect.bottom
    mov al, flag
    mov fullscreen, al
    mov wc.style, CS_HREDRAW or CS_VREDRAW or CS_OWNDC
    mov wc.lpfnWndProc, offset WndProc
    mov wc.cbClsExtra, 0
    mov wc.cbWndExtra, 0
    push hInstance
    pop wc.hInstance
    mov wc.hIcon, 0
    invoke LoadCursor, 0, IDC_ARROW
    mov wc.hCursor, eax
    mov wc.lpszMenuName, 0
    mov wc.lpszClassName, offset ClassName
    mov wc.hbrBackground, 0
    invoke RegisterClass, ADDR wc
    .IF (!eax)
        xor eax, eax
        ret
    .ENDIF
    .IF (fullscreen)
        ZeroMemory &dmScreenSettings, sizeof(dmScreenSettings)
        mov dmScreenSettings.dmSize, sizeof dmScreenSettings
        push w
        pop dmScreenSettings.dmPelsWidth
        push h
        pop dmScreenSettings.dmPelsHeight
        push bits
        pop dmScreenSettings.dmBitsPerPel
        mov dmScreenSettings.dmFields, DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT
        invoke ChangeDisplaySettings, ADDR dmScreenSettings, CDS_FULLSCREEN
        .IF eax != DISP_CHANGE_SUCCESSFUL
            invoke MessageBox, 0, ADDR not_full, ADDR scalp, MB_YESNO or MB_ICONEXCLAMATION
            .IF eax == IDYES
                mov fullscreen, 0
            .ELSE
                invoke MessageBox, 0, ADDR close, ADDR error_str, MB_OK or MB_ICONSTOP
                xor eax, eax
                ret
            .ENDIF
        .ENDIF
    .ENDIF
    .IF (fullscreen)
        mov dwExStyle, WS_EX_APPWINDOW
        mov dwStyle, WS_POPUP or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
        invoke ShowCursor, 0
    .ELSE
        mov dwExStyle, WS_EX_APPWINDOW or WS_EX_WINDOWEDGE
        mov dwStyle, WS_OVERLAPPEDWINDOW or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
    .ENDIF
    invoke AdjustWindowRectEx, ADDR rect, dwStyle, 0, dwExStyle
    mov eax, rect.left
    sub rect.right, eax
    mov eax, rect.top
    sub rect.bottom, eax
    invoke CreateWindowEx, dwExStyle, ADDR ClassName, ADDR our_title, \
                           dwStyle,0,0, rect.right, rect.bottom, 0, 0, hInstance, 0
    mov hWnd, eax
    ZeroMemory &pfd, sizeof(PIXELFORMATDESCRIPTOR)
    mov pfd.nSize, sizeof PIXELFORMATDESCRIPTOR
    mov pfd.nVersion, 1
    mov pfd.dwFlags, PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER
    mov pfd.iPixelType, PFD_TYPE_RGBA
    mov eax, bits
    mov pfd.cColorBits, al
    mov pfd.cDepthBits, 16
    mov pfd.dwLayerMask, PFD_MAIN_PLANE
    invoke GetDC, hWnd
    mov hDC, eax
    invoke ChoosePixelFormat, hDC, ADDR pfd
    mov PixelFormat, eax
    invoke SetPixelFormat, hDC, PixelFormat, ADDR pfd
    invoke wglCreateContext, hDC
    mov hRC, eax
    invoke wglMakeCurrent, hDC, hRC
    invoke ShowWindow, hWnd, SW_SHOW
    invoke SetForegroundWindow, hWnd
    invoke SetFocus, hWnd
    invoke ReSizeGLScene, w, h
    invoke InitGL
    cmp eax, 0
    je end_create
    mov eax, 1
   end_create:
    ret
CreateGLWindow endp

ReSizeGLScene proc w:DWORD, h:DWORD             ;resize scene
  LOCAL ratio:GLdouble
    .IF h == 0
        mov h, 1                                ;prevent div by zero
    .ENDIF
    invoke glViewport, 0, 0, w, h               ;reset viewport
    invoke glMatrixMode, GL_PROJECTION          ;select projection matrix
    invoke glLoadIdentity                       ;reset it
    fild w
    fild h
    fdivp st(1), st(0)
    fstp ratio
    _gluPerspective 45.0f, ratio, 0.1f,100.0f   ;calculate aspect ratio
    invoke glMatrixMode, GL_MODELVIEW           ;select modelview matrix
    invoke glLoadIdentity                       ;reset it
    ret
ReSizeGLScene endp

LoadGLTexture PROC
  LOCAL hBMP:DWORD, ImgInfo:BITMAP
    invoke glGenTextures, 1, ADDR texture
    invoke LoadImage, hInstance, IDD_BITMAP, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION
    cmp eax, 0
    je end_load
    mov hBMP, eax
    invoke GetObject, hBMP, sizeof BITMAP, ADDR ImgInfo
    invoke glBindTexture, GL_TEXTURE_2D, texture
    invoke glTexImage2D, GL_TEXTURE_2D, 0, 3, ImgInfo.bmWidth, ImgInfo.bmHeight,\
                         0, GL_BGR_EXT, GL_UNSIGNED_BYTE, ImgInfo.bmBits
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR
    mov eax, 1
   end_load:
    ret
LoadGLTexture ENDP

KillGLWindow proc
    .IF (fullscreen)                                ;if fullscreen mode
        invoke ChangeDisplaySettings, 0, 0          ;get back to window
        invoke ShowCursor, 1                        ;and show mouse ptr
    .ENDIF
    .IF (hRC)
        invoke wglMakeCurrent, 0, 0
        invoke wglDeleteContext, hRC
        mov hRC, 0
    .ENDIF
    invoke ReleaseDC, hWnd, hDC
    invoke DestroyWindow, hWnd
    invoke UnregisterClass, ADDR ClassName, hInstance
    ret
KillGLWindow endp

DrawGLScene proc
  LOCAL temp1, temp2:GLfloat
    invoke glClear, GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT          ;clear screen before drawing
    invoke glBindTexture, GL_TEXTURE_2D, texture
    mov yloop, 1
    .WHILE (yloop < 6)
        mov xloop, 0
        xor eax, eax
        .WHILE (eax < yloop)
            invoke glLoadIdentity
            fild yloop
            fmul un4
            fstp temp1
            fild xloop
            fmul deux8
            fadd un4
            fsub temp1
            fstp temp1          ;1.4f+(float(xloop)*2.8f)-(float(yloop)*1.4f)

            fld six
            fisub yloop
            fmul deux4
            fsub sept
            fstp temp2          ;((6.0f-float(yloop))*2.4f)-7.0f

            _glTranslatef temp1, temp2, moins20

            fild yloop
            fmul deux
            fchs
            fadd quarante5
            fadd xrot
            fstp temp1
            _glRotatef temp1, 1.0f, 0.0f, 0.0f
            fld yrot
            fadd quarante5
            fstp temp2
            _glRotatef temp2, 0.0f, 1.0f, 0.0f
            mov eax, yloop
            dec eax
            mov ebx, ((sizeof GLfloat)*3)
            mul ebx
            push eax
            add eax, offset boxcol
            invoke glColor3fv, eax
            invoke glCallList, box
            pop eax
            add eax, offset topcol
            invoke glColor3fv, eax
            invoke glCallList, top
            inc xloop
            mov eax, xloop
        .ENDW
        inc yloop
    .ENDW
    mov eax, 1
    ret
DrawGLScene endp

BuildLists PROC             ;build our cube display lists
    invoke glGenLists, 2    ;we want 2 lists
    mov box, eax            ;now box points to the first one
    invoke glNewList, box, GL_COMPILE   ;GL_COMPILE is important, see Nehe's tut for this
    invoke glBegin, GL_QUADS            ;start a new QUAD
    _glNormal3f 0.0f,-1.0f, 0.0f       ;bottom face
    _glTexCoord2f 1.0f, 1.0f
    _glVertex3f  -1.0f,-1.0f,-1.0f
    _glTexCoord2f 0.0f, 1.0f
    _glVertex3f   1.0f,-1.0f,-1.0f
    _glTexCoord2f 0.0f, 0.0f
    _glVertex3f   1.0f,-1.0f, 1.0f
    _glTexCoord2f 1.0f, 0.0f
    _glVertex3f  -1.0f,-1.0f, 1.0f

    _glNormal3f 0.0f, 0.0f, 1.0f       ;front face
    _glTexCoord2f 0.0f, 0.0f
    _glVertex3f  -1.0f,-1.0f, 1.0f
    _glTexCoord2f 1.0f, 0.0f
    _glVertex3f   1.0f,-1.0f, 1.0f
    _glTexCoord2f 1.0f, 1.0f
    _glVertex3f   1.0f, 1.0f, 1.0f
    _glTexCoord2f 0.0f, 1.0f
    _glVertex3f  -1.0f, 1.0f, 1.0f

    _glNormal3f 0.0f, 0.0f,-1.0f       ;back face
    _glTexCoord2f 1.0f, 0.0f
    _glVertex3f  -1.0f,-1.0f,-1.0f
    _glTexCoord2f 1.0f, 1.0f
    _glVertex3f  -1.0f, 1.0f,-1.0f
    _glTexCoord2f 0.0f, 1.0f
    _glVertex3f   1.0f, 1.0f,-1.0f
    _glTexCoord2f 0.0f, 0.0f
    _glVertex3f   1.0f,-1.0f,-1.0f

    _glNormal3f 1.0f, 0.0f, 0.0f       ;right face
    _glTexCoord2f 1.0f, 0.0f
    _glVertex3f   1.0f,-1.0f,-1.0f
    _glTexCoord2f 1.0f, 1.0f
    _glVertex3f   1.0f, 1.0f,-1.0f
    _glTexCoord2f 0.0f, 1.0f
    _glVertex3f   1.0f, 1.0f, 1.0f
    _glTexCoord2f 0.0f, 0.0f
    _glVertex3f   1.0f,-1.0f, 1.0f

    _glNormal3f -1.0f, 0.0f, 0.0f       ;left face
    _glTexCoord2f 0.0f, 0.0f
    _glVertex3f  -1.0f,-1.0f,-1.0f
    _glTexCoord2f 1.0f, 0.0f
    _glVertex3f  -1.0f,-1.0f, 1.0f
    _glTexCoord2f 1.0f, 1.0f
    _glVertex3f  -1.0f, 1.0f, 1.0f
    _glTexCoord2f 0.0f, 1.0f
    _glVertex3f  -1.0f, 1.0f,-1.0f

    invoke glEnd
    invoke glEndList   ;it's ok for this list
    mov eax, box
    inc eax
    mov top, eax        ;storage for the top
    invoke glNewList, top, GL_COMPILE
    invoke glBegin, GL_QUADS
    _glNormal3f  0.0f, 1.0f, 0.0f
    _glTexCoord2f  0.0f, 1.0f
    _glVertex3f   -1.0f, 1.0f,-1.0f
    _glTexCoord2f  0.0f, 0.0f
    _glVertex3f   -1.0f, 1.0f, 1.0f
    _glTexCoord2f  1.0f, 0.0f
    _glVertex3f    1.0f, 1.0f, 1.0f
    _glTexCoord2f  1.0f, 1.0f
    _glVertex3f    1.0f, 1.0f,-1.0f
    invoke glEnd
    invoke glEndList
    ret
BuildLists ENDP

END start


Lesson 13

;written by (scalp), modified by Andras Kobor

.586p
.MODEL FLAT,STDCALL
option casemap:none
include	\masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
include \masm32\include\opengl32.inc
include \masm32\include\glu32.inc
include \masm32\include\masm32.inc
include \masm32\include\fpu.inc

includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\opengl32.lib
includelib \masm32\lib\glu32.lib
includelib \masm32\lib\masm32.lib
includelib \masm32\lib\fpu.lib

PFD_MAIN_PLANE		equ	0
PFD_TYPE_COLORINDEX	equ	1
PFD_TYPE_RGBA		equ	0
PFD_DOUBLEBUFFER	equ	1
PFD_DRAW_TO_WINDOW	equ	4
PFD_SUPPORT_OPENGL	equ	020h
DM_BITSPERPEL           equ     00040000h
DM_PELSWIDTH            equ     00080000h
DM_PELSHEIGHT           equ     00100000h

CreateGLWindow  PROTO :DWORD,:DWORD,:DWORD,:DWORD,:BYTE
WinMain         PROTO :DWORD,:DWORD,:DWORD,:DWORD
WndProc         PROTO :DWORD,:DWORD,:DWORD,:DWORD
ReSizeGLScene   PROTO :DWORD,:DWORD
KillGLWindow    PROTO
InitGL          PROTO
DrawGLScene     PROTO
glPrint         PROTO :DWORD

.DATA

our_title       db "Masm32 conversion of Nehe's tuts ",0
ClassName       db "asmOpenGL",0
error           db "ERROR",0
noDC_RC         db "Release of DC and RC failed.",0
RCrelease       db "Release rendering context failed.",0
DCrelease       db "Release device context failed.",0
hWndRelease     db "Could not release hWnd.",0
register_error  db "Failed to register the window class.",0
not_full        db "The requested fullscreen mode is not supported by",13,10
                db "your video card. Use windowed mode instead ?",0
create_device   db "Can't create a GL device context.",0
create_pixel    db "Can't find a suitable pixelformat.",0
close           db "Program will now close",0
create_error    db "Window creation error.",0
pixel_error     db "Can't set the pixelformat.",0
rend_error      db "Can't create a GL rendering context.",0
act_error       db "Can't activate the GL rendering context.",0
init_error      db "Initialization Failed.",0
start_full      db "Would you like to run in fullscreen mode ?",0
class_error     db "Could not unregister class.",0

Value1Dbl	dq	1.0
Value45Dbl	dq	45.0
Value01Dbl	dq	0.1
Value100Dbl	dq	100.0

base            dd      0
font            dd      0
oldfont         dd      0
fontfamily      db      "Courier New",0
mytext          db      "Active OpenGL Text With NeHe - ",0
F00             dd      0.0
Fm10            dd     -1.0
Fm05            dd     -0.5
F10             dd      1.0
F0051           dd      0.051
F0005           dd      0.005
F005            dd      0.05
Fm045           dd     -0.45
F035            dd      0.35
cnt1            dd      0.0
cnt2            dd      0.0
cnt12           dd      0.0

.DATA?
hDC         HDC ?
hInstance   HANDLE ?
keys        db 256 dup (?)
active      db ?
fullscreen  db ?
hWnd        dd ?
hRC         dd ?
buffer1     db 10 dup (?)
buffer2     db 50 dup (?)

.CODE
start:
    invoke GetModuleHandle, 0
    mov hInstance, eax
    invoke WinMain, eax, 0, 0, 0
    invoke ExitProcess, eax

WinMain proc hInst:HWND, hPrevInst:UINT, CmdLine:WPARAM, CmdShow:LPARAM
  LOCAL msg:MSG, done:DWORD
    invoke MessageBox, 0, ADDR start_full, ADDR our_title, MB_YESNO or MB_ICONQUESTION
    .IF eax == IDNO
        mov fullscreen, 0
    .ELSE
        mov fullscreen, 1
    .ENDIF
    invoke CreateGLWindow, ADDR our_title, 640, 480, 16, fullscreen
    .IF (!eax)
        ret
    .ENDIF
    mov done, 0
    .while (!done)
        invoke PeekMessage, ADDR msg,NULL,0,0,PM_REMOVE
        .IF (eax)
            .IF msg.message == WM_QUIT
                mov done, 1
            .ELSE
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
            .ENDIF
        .ELSE
            invoke DrawGLScene
            .IF ((active) && (!eax)) || (keys[VK_ESCAPE])
                mov done, 1
            .ELSE
                invoke SwapBuffers, hDC
            .ENDIF
            .IF (keys[VK_F1])
                mov keys[VK_F1], 0
                invoke KillGLWindow
                .IF (fullscreen)
                    mov fullscreen, 0
                .ELSE
                    mov fullscreen, 1
                .ENDIF
                invoke CreateGLWindow, ADDR our_title, 640,480,16,fullscreen
                .IF (!eax)
                    ret
                .ENDIF
            .ENDIF
        .ENDIF
    .endw
    invoke KillGLWindow
    mov eax, msg.wParam
    ret
WinMain endp

WndProc proc hWin:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
    .IF uMsg == WM_ACTIVATE
        mov eax, wParam
        .IF (!ah)
            mov active, 1
        .ELSE
            mov active, 0
        .ENDIF
    .ELSEIF uMsg == WM_SYSCOMMAND
        .IF (wParam == SC_SCREENSAVE) || (wParam == SC_MONITORPOWER)
            xor eax, eax
            ret
        .ENDIF
    .ELSEIF uMsg == WM_KEYDOWN
        mov eax, wParam
        mov keys[eax], 1
    .ELSEIF uMsg == WM_KEYUP
        mov eax, wParam
        mov keys[eax], 0
    .ELSEIF uMsg == WM_SIZE
        mov eax, lParam
        mov ebx, eax
        and eax, 0ffffh         ;eax=width=LOWORD
        shr ebx, 16             ;ebx=height=HIWORD
        invoke ReSizeGLScene, eax, ebx
    .ELSEIF uMsg == WM_CLOSE
        invoke PostQuitMessage, 0
        xor eax, eax
        ret
    .ENDIF
    invoke DefWindowProc, hWin, uMsg, wParam, lParam
    ret
WndProc endp

InitGL proc         ;opengl setup (like color, etc...)
    invoke glShadeModel, GL_SMOOTH              ;enable smooth shading
    invoke glClearDepth,DWORD PTR Value1Dbl,DWORD PTR Value1Dbl+4       ;depth buffer setup
    invoke glEnable, GL_DEPTH_TEST              ;enable depth testing
    invoke glDepthFunc, GL_LEQUAL               ;type of depth test
  ;set really nice perspective calculations
    invoke glHint, GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST
    invoke glGenLists, 96
    mov base, eax
    invoke CreateFont, -24, 0, 0, 0, FW_BOLD, 0, 0, 0, ANSI_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, FF_DONTCARE, addr fontfamily
    mov font, eax
    invoke SelectObject, hDC, font
    mov oldfont, eax
    invoke wglUseFontBitmaps, hDC, 32, 96, base
    invoke SelectObject, hDC, oldfont
    invoke DeleteObject, font
    mov eax, 1
    ret
InitGL endp

CreateGLWindow proc letitle:DWORD,w:DWORD,h:DWORD,bits:DWORD,flag:BYTE
  LOCAL PixelFormat:DWORD, wc:WNDCLASS
  LOCAL dwExStyle:DWORD, dwStyle:DWORD
  LOCAL rect:RECT, dmScreenSettings:DEVMODE
  LOCAL pfd:PIXELFORMATDESCRIPTOR
    mov rect.left, 0
    push w
    pop rect.right
    mov rect.top, 0
    push h
    pop rect.bottom
    mov al, flag
    mov fullscreen, al
    mov wc.style, CS_HREDRAW or CS_VREDRAW or CS_OWNDC
    mov wc.lpfnWndProc, offset WndProc
    mov wc.cbClsExtra, 0
    mov wc.cbWndExtra, 0
    push hInstance
    pop wc.hInstance
    mov wc.hIcon, 0
    invoke LoadCursor, 0, IDC_ARROW
    mov wc.hCursor, eax
    mov wc.lpszMenuName, 0
    mov wc.lpszClassName, offset ClassName
    mov wc.hbrBackground, 0
    invoke RegisterClass, ADDR wc
    .IF (!eax)
        invoke MessageBox, 0, ADDR register_error, ADDR error, MB_OK or MB_ICONEXCLAMATION
        xor eax, eax
        ret
    .ENDIF
    .IF (fullscreen)
        invoke RtlZeroMemory, ADDR dmScreenSettings, sizeof dmScreenSettings
        mov dmScreenSettings.dmSize, sizeof dmScreenSettings
        push w
        pop dmScreenSettings.dmPelsWidth
        push h
        pop dmScreenSettings.dmPelsHeight
        push bits
        pop dmScreenSettings.dmBitsPerPel
        mov dmScreenSettings.dmFields, DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT
        invoke ChangeDisplaySettings, ADDR dmScreenSettings, CDS_FULLSCREEN
        .IF eax != DISP_CHANGE_SUCCESSFUL
            invoke MessageBox, 0, ADDR not_full, ADDR error, MB_YESNO or MB_ICONEXCLAMATION
            .IF eax == IDYES
                mov fullscreen, 0
            .ELSE
                invoke MessageBox, 0, ADDR close, ADDR error, MB_OK or MB_ICONSTOP
                xor eax, eax
                ret
            .ENDIF
        .ENDIF
    .ENDIF
    .IF (fullscreen)
        mov dwExStyle, WS_EX_APPWINDOW
        mov dwStyle, WS_POPUP or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
        invoke ShowCursor, 0
    .ELSE
        mov dwExStyle, WS_EX_APPWINDOW or WS_EX_WINDOWEDGE
        mov dwStyle, WS_OVERLAPPEDWINDOW or WS_CLIPSIBLINGS or WS_CLIPCHILDREN
    .ENDIF
    invoke AdjustWindowRectEx, ADDR rect, dwStyle, 0, dwExStyle
    mov eax, rect.left
    sub rect.right, eax
    mov eax, rect.top
    sub rect.bottom, eax
    invoke CreateWindowEx, dwExStyle, ADDR ClassName, ADDR our_title, \
                           dwStyle,0,0, rect.right, rect.bottom, 0, 0, hInstance, 0
    mov hWnd, eax
    .IF (!eax)
        invoke KillGLWindow
        invoke MessageBox, 0, ADDR create_error, ADDR error, MB_OK or MB_ICONEXCLAMATION
        xor eax, eax
        ret
    .ENDIF
    invoke RtlZeroMemory,  ADDR pfd, sizeof pfd
    mov pfd.nSize, sizeof PIXELFORMATDESCRIPTOR
    mov pfd.nVersion, 1
    mov pfd.dwFlags, PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER
    mov pfd.iPixelType, PFD_TYPE_RGBA
    mov eax, bits
    mov pfd.cColorBits, al
    mov pfd.cDepthBits, 16
    mov pfd.dwLayerMask, PFD_MAIN_PLANE
    invoke GetDC, hWnd
    mov hDC, eax
    .IF (!eax)
        invoke KillGLWindow
        invoke MessageBox, 0, ADDR create_device, ADDR error, MB_OK or MB_ICONEXCLAMATION
        xor eax, eax
        ret
    .ENDIF
    invoke ChoosePixelFormat, hDC, ADDR pfd
    mov PixelFormat, eax
    .IF (!eax)
        invoke KillGLWindow
        invoke MessageBox, 0, ADDR create_pixel, ADDR error, MB_OK or MB_ICONEXCLAMATION
        xor eax, eax
        ret
    .ENDIF
    invoke SetPixelFormat, hDC, PixelFormat, ADDR pfd
    .IF (!eax)
        invoke KillGLWindow
        invoke MessageBox, 0, ADDR pixel_error, ADDR error, MB_OK or MB_ICONEXCLAMATION
        xor eax, eax
        ret
    .ENDIF
    invoke wglCreateContext, hDC
    mov hRC, eax
    .IF (!eax)
        invoke KillGLWindow
        invoke MessageBox, 0, ADDR rend_error, ADDR error, MB_OK or MB_ICONEXCLAMATION
        xor eax, eax
        ret
    .ENDIF
    invoke wglMakeCurrent, hDC, hRC
    .IF (!eax)
        invoke KillGLWindow
        invoke MessageBox, 0, ADDR act_error, ADDR error, MB_OK or MB_ICONEXCLAMATION
        xor eax, eax
        ret
    .ENDIF
    invoke ShowWindow, hWnd, SW_SHOW
    invoke SetForegroundWindow, hWnd
    invoke SetFocus, hWnd
    invoke ReSizeGLScene, w, h
    invoke InitGL
    .IF (!eax)
        invoke KillGLWindow
        invoke MessageBox, 0, ADDR init_error, ADDR error, MB_OK or MB_ICONEXCLAMATION
        xor eax, eax
        ret
    .ENDIF
    mov eax, 1
    ret
CreateGLWindow endp

DrawGLScene proc                                        ;drawing proc
 LOCAL c1:REAL4, c2:REAL4, c12:REAL4
 ;clear screen and depth buffer
    invoke glClear, GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT
    invoke glLoadIdentity                               ;reset modelview matrix
    invoke glTranslatef, F00, F00, Fm10
    fld cnt1
    fld cnt2
    fadd
    fcos
    fmul Fm05
    fadd F10
    fstp c12
    fld cnt1
    fcos
    fstp c1
    fld cnt2
    fsin
    fstp c2
    invoke glColor3f, c1, c2, c12
    fld c1
    fmul F005
    fadd Fm045
    fstp c1
    fld c2
    fmul F035
    fstp c2
    invoke glRasterPos2f, c1, c2
    fld cnt1
    invoke FpuFLtoA, 0, 2, ADDR buffer1, SRC1_FPU or SRC2_DIMM
    invoke RtlZeroMemory,  ADDR buffer2, 50
    invoke szCatStr, addr buffer2, addr mytext
    invoke szCatStr, addr buffer2, addr buffer1
    invoke glPrint, addr buffer2
    fld cnt1
    fadd  F0051
    fstp cnt1
    fld cnt2
    fadd  F0005
    fstp cnt2
    mov eax, 1
    ret
DrawGLScene endp

ReSizeGLScene proc w:DWORD, h:DWORD             ;resize scene
  LOCAL ratio:QWORD
    .IF h == 0
        mov h, 1                                ;prevent div by zero
    .ENDIF
    invoke glViewport, 0, 0, w, h               ;reset viewport
    invoke glMatrixMode, GL_PROJECTION          ;select projection matrix
    invoke glLoadIdentity                       ;reset it
    fild w
    fild h
    fdivp st(1), st ;width/height
    fstp ratio
    invoke gluPerspective,DWORD PTR Value45Dbl,DWORD PTR Value45Dbl+4,DWORD PTR ratio,DWORD PTR ratio+4,DWORD PTR Value01Dbl,DWORD PTR Value01Dbl+4,DWORD PTR Value100Dbl,DWORD PTR Value100Dbl+4
    invoke glMatrixMode, GL_MODELVIEW                   ;select modelview matrix
    invoke glLoadIdentity                               ;reset it
    ret
ReSizeGLScene endp

KillGLWindow proc
;here we do a lot of tests, but at least, you have to do
;wglMakeCurrent, wglDeleteContext and ReleaseDC.
    .IF (fullscreen)                                ;if fullscreen mode
        invoke ChangeDisplaySettings, 0, 0          ;get back to window
        invoke ShowCursor, 1                        ;and show mouse ptr
    .ENDIF
  ;do a lot of error checking before closing
    .IF (hRC)
        invoke wglMakeCurrent, 0, 0
        .IF (!eax)
            invoke MessageBox, 0, ADDR noDC_RC, ADDR error, MB_OK or MB_ICONINFORMATION
        .ENDIF
        invoke wglDeleteContext, hRC
        .IF (!eax)
            invoke MessageBox, 0, ADDR RCrelease, ADDR error, MB_OK or MB_ICONINFORMATION
        .ENDIF
        mov hRC, 0
    .ENDIF
    invoke ReleaseDC, hWnd, hDC
    .IF (hDC && (!eax))
        invoke MessageBox, 0, ADDR DCrelease, ADDR error, MB_OK or MB_ICONINFORMATION
        mov hDC, 0
    .ENDIF
    invoke DestroyWindow, hWnd
    .IF (hWnd && (!eax))
        invoke MessageBox, 0, ADDR hWndRelease, ADDR error, MB_OK or MB_ICONINFORMATION
        mov hWnd, 0
    .ENDIF
    invoke UnregisterClass, ADDR ClassName, hInstance
    .IF (!eax)
        invoke MessageBox, 0, ADDR class_error, ADDR error, MB_OK or MB_ICONINFORMATION
    .ENDIF
    invoke glDeleteLists, base, 96
    ret
KillGLWindow endp

glPrint proc thetext:DWORD
    invoke glPushAttrib, GL_LIST_BIT
    mov eax , base
    sub eax, 32
    invoke glListBase, eax
    invoke StrLen, thetext
    invoke glCallLists, eax, GL_UNSIGNED_BYTE, thetext
    invoke glPopAttrib
    ret
glPrint endp

END start


Lesson 14

; Attempt at NeHe's tutorial 14 : Drawing outline fonts
;
; Author: Greg Helps [x86@ozducati.com]
; Date: July 3 2003
;
; With thanks to NeHe for the OpenGL tutorials
; Inspiration for x86 Assembler from Nico
; OpenGL x86 includes originally from hardcode
; although the site is now offline.
; I've included the OpenGL include files in the original archive as they are hard to find.
;
; Bugs are mine.
;
; I'm a beginner. If you've any comments / suggestions / questions - bring em on
.586
.model flat, stdcall
option casemap:none

; INCLUDES * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
include c:\masm32\include\windows.inc
include c:\masm32\include\kernel32.inc
include c:\masm32\include\user32.inc
include c:\masm32\include\gdi32.inc
include c:\masm32\include\masm32.inc
include c:\masm32\def\gl.def
include c:\masm32\def\glu.def
include c:\masm32\def\winextra.def

includelib c:\masm32\lib\kernel32.lib
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\gdi32.lib
includelib c:\masm32\lib\opengl32.lib
includelib c:\masm32\lib\glu32.lib

; Uses MS C runtime library for sprintf function.
; Included with all MS windows systems (I think) from win95C onwards
includelib c:\masm32\lib\msvcrt.lib

; Missing from include files * * * * * * * * * * * * * * * * * * * * *
DM_BITSPERPEL       = 00040000h
DM_PELSWIDTH        = 00080000h
DM_PELSHEIGHT       = 00100000h
ANTIALIASED_QUALITY = 4
WGL_FONT_POLYGONS 	= 1

; PROTOTYPES * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
WinMain 		PROTO :HWND, :UINT, :WPARAM, :LPARAM
CreateGLWindow 	PROTO :DWORD, :DWORD, :DWORD, :UINT, :BOOL
WndProc 		PROTO :HWND, :UINT, :WPARAM, :LPARAM
KillGLWindow 	PROTO
ReSizeGLScene 	PROTO :GLsizei, :GLsizei
InitGL 			PROTO
DrawGLScene 	PROTO
LoadGLTexture	PROTO
BuildFont		PROTO
KillFont		PROTO
glPrint			PROTO :DWORD

sprintf 		PROTO C :DWORD, :VARARG	; printf C function prototype from MS C runtime library
strlen			PROTO C :VARARG			; strlen C function prototype from MS C runtime library

; MACROS * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; The following were missing from the hardcode include files
_glClearDepth MACRO t
    gl_dpush t
    mov eax, eax
    mov ebx, ebx
    call glClearDepth
ENDM

_gluPerspective MACRO a,b,c,d
    gl_dpush d
    gl_dpush c
    gl_dpush b
    gl_dpush a
    mov eax, eax
    mov ebx, ebx
    call gluPerspective
ENDM

_glClearColor MACRO a,b,c,d
    gl_fpush d
    gl_fpush c
    gl_fpush b
    gl_fpush a
    mov eax, eax
    mov ecx, ecx
    call glClearColor
ENDM

_glRasterPos2f MACRO a,b
    gl_fpush b
    gl_fpush a
    call glRasterPos2f
ENDM

; fpc macro allows constants to be used with floating point operations.
; Authored by bitRAKE from Win32ASM board
; Great resource for assembler hackers
fpc MACRO val:REQ
    LOCAL w,x,y,z,zz,ww

    ;; split type and value, defaulting to REAL4
    z INSTR 1,<&val>,
    IF z EQ 0
        y TEXTEQU
        x TEXTEQU <&val>
    ELSE
        y TEXTEQU @SubStr(<&val>,1,z-1)	;; Type
        x TEXTEQU @SubStr(<&val>,z+1,)	;; Value
    ENDIF

    ;; replace . with _
    z INSTR 1,x,
    IF z EQ 0
        w TEXTEQU x
        x CATSTR x,<.0> ;; prevent error message
    ELSE
        w CATSTR @SubStr(%x,1,z-1),<_>,@SubStr(%x,z+1,)
    ENDIF

    ;; replace - with _
    zz INSTR 1,w,
    IF zz EQ 0
        ww TEXTEQU w
    ELSE
        ww CATSTR @SubStr(%w,1,zz-1),<_>,@SubStr(%w,zz+1,)
    ENDIF

    ;; figure out global name for constant
    z SIZESTR y ;; use last char for size distiction
    ww CATSTR <__>,ww,,@SubStr(%y,z,1)

    IF (OPATTR(ww)) EQ 0 ;; not defined
        CONST SEGMENT
            ww y x
        CONST ENDS
    ENDIF
    EXITM ww
ENDM

; INITIALISED DATA * * * * * * * * * * * * * * * * * * * * * * * * * *
.data
txt_Query_Fullscn				db		"Do you want to run in fullscreen?",0
txt_Title_Fullscn				db		"Graphics mode",0
txt_Window_Title				db		"NeHe's Outline Font Tutorial",0
txt_OpenGL						db		"OpenGL",0
txt_WC_Failed					db		"Failed to registed window class",0
txt_GL_Context_Failed			db		"Failed to create a GL device context",0
txt_RC_Failed					db		"Failed to create a GL rendering context",0
txt_PixelFormat_Failed			db		"Failed to find suitable PixelFormat",0
txt_ActivateRC_Failed			db		"Failed to activate GL rendering context",0
txt_GLInit_Failed				db		"Initialisation failed",0
txt_SetPixelFormat_Failed		db		"Failed to set PixelFormat",0
txt_WindowCreateError			db		"Window creation error",0
txt_Error						db		"Error",0
;
txt_FontName					db		"Comic Sans MS",0
txt_Message						db		"NeHe - %3.2f",0

; UNINITIALISED DATA * * * * * * * * * * * * * * * * * * * * * * * * *
.data?
; Define GMF array
GLYPHMETRICSFLOAT STRUCT
    gmfBlackBoxX FLOAT ?
    gmfBlackBoxY FLOAT ?
    x FLOAT ?
    y FLOAT ?
    gmfCellIncX FLOAT ?
    gmfCellIncY FLOAT ?
GLYPHMETRICSFLOAT ENDS
GMFSIZE EQU SIZEOF GLYPHMETRICSFLOAT

gmf	GLYPHMETRICSFLOAT 256 dup (<>)

rot				GLfloat		?	; holds rotational value of text
base			GLuint		?	; Font base

hRC				HGLRC     	?	;
hDC           	HDC       	?	; Open GL window structures
hWnd          	HWND      	?	;
hInstance     	HINSTANCE	?	;

keys          	db 256 dup (?)	; keypress status
fullscreen    	BOOL		?	; fullscreen toggle
active        	BOOL     	?	; window active toggle/status

txt_Buffer		byte 64 dup (?)	; A general text buffer for string manipulation

; BUGS BEGIN HERE * * * * * * * * * * * * * * * * * * * * * * * * * *
.code

start:
invoke GetModuleHandle,0
mov hInstance, eax
invoke WinMain,hInstance,0,0,0
invoke ExitProcess, eax
;** END

; PROC WinMain * * * * * * * * * * * * * * * * * * * * * * * * * * * *
WinMain	proc hInst:HWND, hPrevInst:UINT, CmdLine:WPARAM, CmdShow:LPARAM
    LOCAL msg:MSG, done:UINT

    ; Check	if we should start in full screen
    invoke MessageBox,NULL,addr	txt_Query_Fullscn,addr txt_Title_Fullscn,MB_YESNO or MB_ICONQUESTION

    .IF	eax	== IDNO
        mov	fullscreen,FALSE
    .ELSE
        mov	fullscreen,TRUE
    .ENDIF

    invoke CreateGLWindow,addr txt_Window_Title,640,480,16,fullscreen
    .IF	!eax
        ret
    .ENDIF

    mov	done,FALSE

    .WHILE !done
        invoke PeekMessage,addr	msg,NULL,0,0,PM_REMOVE
        .IF	eax
            .IF	msg.message	== WM_QUIT
                mov	done,TRUE
            .ELSE
                invoke TranslateMessage,addr msg
                invoke DispatchMessage,addr	msg
            .ENDIF
        .ELSE
            invoke DrawGLScene
            .IF ((active) && (!eax)) || (keys[VK_ESCAPE])
                mov done, 1
            .ELSE
                invoke SwapBuffers,hDC
            .ENDIF

            .IF	keys[VK_F1]
                mov	keys[VK_F1],FALSE
                invoke KillGLWindow
                xor	fullscreen,	1
                invoke CreateGLWindow,addr txt_Window_Title,640,480,16,fullscreen
                .IF	!eax
                    mov	eax,FALSE
                .ENDIF
            .ENDIF
        .ENDIF
    .ENDW
    invoke KillGLWindow

    mov	eax,msg.wParam
    ret
WinMain	endp

; PROC InitGL * * * * * * * * * * * * * * * * * * * * * * * * * * * *
InitGL proc
      invoke glShadeModel,GL_SMOOTH
      _glClearColor 0.0f,0.0f,0.0f,0.5f
      _glClearDepth 1.0f
      invoke glEnable,GL_DEPTH_TEST
      invoke glDepthFunc,GL_LEQUAL
     invoke glHint,GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST
     invoke glEnable, GL_LIGHT0
     invoke glEnable, GL_LIGHTING
     invoke glEnable, GL_COLOR_MATERIAL

    invoke BuildFont	; Create the outline fonts

    fld fpc(0.0)	; Good practice to initialise vars before use
    fstp rot		; although we'd get away without it

    mov eax, 1
      ret
InitGL endp

; PROC CreateGLWindow * * * * * * * * * * * * * * * * * * * * * * * *
CreateGLWindow proc WinTitle:DWORD, WinWidth:DWORD, WinHeight:DWORD, WinBits:UINT, WinFullscreen:BOOL
    LOCAL dwExStyle:DWORD, dwStyle:DWORD, PixelFormat:GLuint
    LOCAL WindowRect:RECT, dmScreenSettings:DEVMODE, wc:WNDCLASS, pfd:PIXELFORMATDESCRIPTOR

    mov WindowRect.left,0
    mov WindowRect.top,0
    push WinWidth
    pop WindowRect.right
    push WinHeight
    pop WindowRect.bottom

    push WinFullscreen
    pop fullscreen

    invoke GetModuleHandle,NULL
    mov hInstance,eax
    mov wc.style,CS_HREDRAW or CS_VREDRAW or CS_OWNDC
    mov wc.lpfnWndProc, offset WndProc
    mov wc.cbClsExtra,0
    mov wc.cbWndExtra,0
    push hInstance
    pop wc.hInstance
    mov wc.hbrBackground,NULL
    mov wc.lpszMenuName,NULL
    mov wc.lpszClassName,offset txt_OpenGL
    invoke LoadIcon,NULL,IDI_WINLOGO
    mov wc.hIcon,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov wc.hCursor,eax

    ; Register the window class
    invoke RegisterClass,addr wc
    .IF !eax
        invoke MessageBox,NULL,addr txt_WC_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF

    ; Set fullscreen, if appropriate
    .IF fullscreen
        ZeroMemory &dmScreenSettings,sizeof(dmScreenSettings)
        mov dmScreenSettings.dmSize,sizeof dmScreenSettings
        push WinWidth
        pop dmScreenSettings.dmPelsWidth
        push WinHeight
        pop dmScreenSettings.dmPelsHeight
        push WinBits
        pop dmScreenSettings.dmBitsPerPel
        mov dmScreenSettings.dmFields,DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT

        ; Try to set selected mode
        invoke ChangeDisplaySettings,addr dmScreenSettings,CDS_FULLSCREEN
        .IF eax != DISP_CHANGE_SUCCESSFUL
            ; Original code prompts user, and asks to set to window or exit. I'll just set to window mode
            mov fullscreen,FALSE
        .ENDIF
    .ENDIF

    ; Check if we are (still?) in fullscreen
    .IF fullscreen
        mov dwExStyle, WS_EX_APPWINDOW
        mov dwStyle,WS_POPUP
        invoke ShowCursor,FALSE
    .ELSE
        mov dwExStyle,WS_EX_APPWINDOW or WS_EX_WINDOWEDGE
        mov dwStyle, WS_OVERLAPPEDWINDOW
    .ENDIF

    invoke AdjustWindowRectEx,addr WindowRect,dwStyle,FALSE,dwExStyle
    mov eax, WindowRect.left
    sub WindowRect.right,eax
    mov eax, WindowRect.top
    sub WindowRect.bottom, eax

    mov eax,dwStyle
    or eax,WS_CLIPSIBLINGS or WS_CLIPCHILDREN
    invoke CreateWindowEx,dwExStyle,addr txt_OpenGL,addr txt_Window_Title,eax,
        0,0,WindowRect.right,WindowRect.bottom,NULL,NULL,hInstance,NULL
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,txt_WindowCreateError,txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax, FALSE
        ret
    .ENDIF
     mov hWnd,eax

    ZeroMemory &pfd,sizeof(PIXELFORMATDESCRIPTOR)
    mov pfd.nSize,sizeof(PIXELFORMATDESCRIPTOR)
    mov pfd.nVersion,1
    mov pfd.dwFlags,PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER
    mov pfd.iPixelType,PFD_TYPE_RGBA
    mov pfd.cColorBits,16
    mov pfd.cDepthBits,16
    mov pfd.dwLayerMask,PFD_MAIN_PLANE

    ; Try to get a device context
    invoke GetDC,hWnd
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,addr txt_GL_Context_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF
    mov hDC,eax

    ; Select pixel format
    invoke ChoosePixelFormat,hDC,addr pfd
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,addr txt_PixelFormat_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF
    mov PixelFormat,eax

    ; Set pixel format
    invoke SetPixelFormat,hDC,PixelFormat,addr pfd
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,addr txt_SetPixelFormat_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF

    ; Get rendering context
    invoke wglCreateContext,hDC
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,addr txt_RC_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF
    mov hRC,eax

    ; Activate rendering context
    invoke wglMakeCurrent,hDC,hRC
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,addr txt_ActivateRC_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF

    invoke ShowWindow,hWnd,SW_SHOW
    invoke SetForegroundWindow,hWnd
    invoke SetFocus,hWnd
    invoke ReSizeGLScene,WinWidth,WinHeight

    invoke InitGL
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,addr txt_GLInit_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF

    mov eax,TRUE
    ret
CreateGLWindow endp

; PROC KillGLWindow * * * * * * * * * * * * * * * * * * * * * * * * *
KillGLWindow proc
    .IF fullscreen
        invoke ChangeDisplaySettings,NULL,0
        invoke ShowCursor,TRUE
    .ENDIF

    .IF hRC
        invoke wglMakeCurrent,NULL,NULL
        invoke wglDeleteContext,hRC
        mov hRC,NULL
    .ENDIF

    .IF hDC
        invoke ReleaseDC,hWnd,hDC
        mov hDC,NULL
    .ENDIF

    .IF hWnd
        invoke DestroyWindow,hWnd
        mov hWnd,NULL
    .ENDIF

    invoke UnregisterClass,addr txt_OpenGL,hInstance
    mov hInstance,NULL

    ret
KillGLWindow endp

; PROC WndProc * * * * * * * * * * * * * * * * * * * * * * * * * * * *
WndProc proc hWind:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg == WM_ACTIVATE
        mov eax,wParam
        .IF !ah
            mov active,TRUE
        .ELSE
              mov active,FALSE
        .ENDIF
        xor eax, eax
        ret
    .ELSEIF uMsg==WM_SYSCOMMAND
          .IF wParam == SC_SCREENSAVE or SC_MONITORPOWER
              xor eax, eax
              ret
        .ENDIF
    .ELSEIF uMsg==WM_CLOSE
          invoke PostQuitMessage,0
        xor eax, eax
        ret
      .ELSEIF uMsg==WM_KEYDOWN
        mov eax,wParam
        mov keys[eax],TRUE
        xor eax, eax
        ret
      .ELSEIF uMsg==WM_KEYUP
        mov eax,wParam
        mov keys[eax],FALSE
        xor eax, eax
        ret
      .ELSEIF uMsg==WM_SIZE
        mov eax,lParam
        mov ebx,eax
        and eax,00000000000000001111111111111111b
        shr ebx,16
        invoke ReSizeGLScene,eax,ebx
        xor eax, eax
        ret
      .ENDIF

      invoke DefWindowProc,hWind,uMsg,wParam,lParam
      ret
WndProc endp

; PROC ReSizeGLScene * * * * * * * * * * * * * * * * * * * * * * * * *
ReSizeGLScene proc widthx:DWORD, heighty:DWORD
    LOCAL ratio:GLdouble

      .IF heighty == 0
        mov heighty, 1 ; prevent div by zero
      .ENDIF
    invoke glViewport, 0, 0, widthx, heighty
      invoke glMatrixMode, GL_PROJECTION
      _glLoadIdentity

      fild widthx
      fild heighty
      fdivp st(1),st(0)
      fstp ratio
      _gluPerspective 45.0f, ratio, 0.1f,100.0f
      invoke glMatrixMode, GL_MODELVIEW
      _glLoadIdentity
      ret
ReSizeGLScene endp

; PROC BuildFont * * * * * * * * * * * * * * * * * * * * * * * * * * *
BuildFont proc
    LOCAL font:HFONT	; windows font ID

    invoke glGenLists, 256	; Create storage for 256 Characters
    mov base, eax

    invoke CreateFont, -12, 0, 0,0, FW_BOLD, FALSE, FALSE, FALSE, ANSI_CHARSET,
        OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, FF_DONTCARE or DEFAULT_PITCH, addr txt_FontName
    mov font, eax

    invoke SelectObject, hDC, font
    invoke wglUseFontOutlines, hDC, 0, 255, base, fpc(0.0), fpc(0.2), WGL_FONT_POLYGONS, offset gmf

    mov eax, 1
    ret
BuildFont endp

; PROC KillFont * * * * * * * * * * * * * * * * * * * * * * * * * * *
KillFont proc
    invoke glDeleteLists, base, 256
    mov eax, 1
    ret
KillFont endp

; PROC DrawGLScene * * * * * * * * * * * * * * * * * * * * * * * * * *
DrawGLScene proc
    LOCAL c4:QWORD
    LOCAL c1:GLfloat, c2:GLfloat, c3:GLfloat

      invoke glClear,GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT
    invoke glLoadIdentity
    _glTranslatef 0.0f, 0.0f, -10.0f

    _glRotatef rot, 1.0f, 0.0f, 0.0f
    fld rot
    fmul fpc(1.5)
    fstp c2
    fld rot
    fmul fpc(1.4)
    fstp c3
    _glRotatef c2, 0.0f, 1.0f, 0.0f
    _glRotatef c3, 0.0f, 0.0f, 1.0f

    fld rot
    fdiv fpc(20.0)
    fcos
    fstp c1
    fld rot
    fdiv fpc(25.0)
    fsin
    fstp c2
    fld rot
    fdiv fpc(17.0)
    fcos
    fmul fpc(0.5)
    fsubr fpc(1.0)
    fstp c3
      _glColor3f c1, c2, c3

    ; Add rot/50 to end of scrollymessage
    fld rot
    fdiv fpc(50.0)
    fstp c4

    ; Format the string.
    ; c4 is a QWORD. we can pass it to sprintf by passing it as two DWORDS.
    ; cast it with DWORD PTR and send HO dword and LO dword separately like thus:
    invoke sprintf, addr txt_Buffer, addr txt_Message, DWORD PTR [c4], DWORD PTR [c4+4]
      invoke glPrint, addr txt_Buffer

      fld rot
      fadd fpc(0.5)
      fstp rot

    mov eax, 1
    ret
DrawGLScene endp

; PROC glPrint * * * * * * * * * * * * * * * * * * * * * * * * * * * *
glPrint proc fmt:DWORD
    LOCAL textlen:REAL4
    LOCAL stringlen:UINT

    .IF (fmt == NULL)
        ret
    .ENDIF

    fild fpc(0.0)	; load zero to st0. this is the width of the outline text.

    invoke strlen, fmt
    mov stringlen, eax	; discover length of the fmt parameter

    mov ecx, 0			; Loop until we equal the length of the fmt parameter
@@:	xor ebx, ebx 		; mov ebx, 0

    mov eax, fmt		; st0 = st0 + gmf[fmt+ecx].gmfCellIncX;
    mov bl, [ecx+eax]
    mov eax, GMFSIZE
    mul ebx
    fadd gmf[eax].gmfCellIncX

    inc ecx
    cmp ecx, stringlen
    jl @B

    fdiv fpc(2.0)		; divide st0 by two & change signs to negative
    fchs
    fstp textlen
    invoke glTranslatef, textlen, fpc(0.0), fpc(0.0)

    invoke glPushAttrib, GL_LIST_BIT
    invoke glListBase, base
    invoke glCallLists, stringlen, GL_UNSIGNED_BYTE, fmt
    invoke glPopAttrib

    ret
glPrint endp

end start


Lesson 15

; Attempt at NeHe's tutorial 15 : Texture mapping outline fonts
;
; Author: Greg Helps [x86@ozducati.com]
; Date: July 10 2003
;
; With thanks to NeHe for the OpenGL tutorials
; Inspiration for x86 Assembler from Nico
; OpenGL x86 includes originally from hardcode
; although the site is now offline.
; I've included the OpenGL includes needed to compile this program.
;
; Bugs are mine.
;
; I'm a beginner. If you've any comments / suggestions / questions - bring em on
.586
.model flat, stdcall
option casemap:none

; INCLUDES * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
include c:\masm32\include\windows.inc
include c:\masm32\include\kernel32.inc
include c:\masm32\include\user32.inc
include c:\masm32\include\gdi32.inc
include c:\masm32\include\masm32.inc
include .\gl.def
include .\glu.def
include .\winextra.def

includelib c:\masm32\lib\kernel32.lib
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\gdi32.lib
includelib c:\masm32\lib\opengl32.lib
includelib c:\masm32\lib\glu32.lib
includelib c:\masm32\lib\masm32.lib

; Missing from include files * * * * * * * * * * * * * * * * * * * * *
DM_BITSPERPEL       = 00040000h
DM_PELSWIDTH        = 00080000h
DM_PELSHEIGHT       = 00100000h
ANTIALIASED_QUALITY = 4
WGL_FONT_POLYGONS 	= 1

; PROTOTYPES * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
WinMain 		PROTO :HWND, :UINT, :WPARAM, :LPARAM
CreateGLWindow 	PROTO :DWORD, :DWORD, :DWORD, :UINT, :BOOL
WndProc 		PROTO :HWND, :UINT, :WPARAM, :LPARAM
KillGLWindow 	PROTO
ReSizeGLScene 	PROTO :GLsizei, :GLsizei
InitGL 			PROTO
DrawGLScene 	PROTO
LoadGLTexture	PROTO
BuildFont		PROTO
KillFont		PROTO
glPrint			PROTO :DWORD
LoadGLTextures	PROTO

; STRUCTURES * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; Define BMP structure
; Used to reference width/height & data from the BMP file
BMPFILE STRUCT
    header BITMAPFILEHEADER <>
    info BITMAPINFOHEADER <>
    data DWORD ?
BMPFILE ENDS

; GMF structure- used for font handling
GLYPHMETRICSFLOAT STRUCT
    gmfBlackBoxX FLOAT ?
    gmfBlackBoxY FLOAT ?
    x FLOAT ?
    y FLOAT ?
    gmfCellIncX FLOAT ?
    gmfCellIncY FLOAT ?
GLYPHMETRICSFLOAT ENDS
GMFSIZE EQU SIZEOF GLYPHMETRICSFLOAT

; MACROS * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; The following were missing from the hardcode include files
_glClearDepth MACRO t
    gl_dpush t
    mov eax, eax
    mov ebx, ebx
    call glClearDepth
ENDM

_gluPerspective MACRO a,b,c,d
    gl_dpush d
    gl_dpush c
    gl_dpush b
    gl_dpush a
    mov eax, eax
    mov ebx, ebx
    call gluPerspective
ENDM

_glClearColor MACRO a,b,c,d
    gl_fpush d
    gl_fpush c
    gl_fpush b
    gl_fpush a
    mov eax, eax
    mov ecx, ecx
    call glClearColor
ENDM

; fpc macro allows constants to be used with floating point operations.
; Authored by bitRAKE from Win32ASM board
; Great resource for assembler hackers
fpc MACRO val:REQ
    LOCAL w,x,y,z,zz,ww

    ;; split type and value, defaulting to REAL4
    z INSTR 1,<&val>,
    IF z EQ 0
        y TEXTEQU
        x TEXTEQU <&val>
    ELSE
        y TEXTEQU @SubStr(<&val>,1,z-1)	;; Type
        x TEXTEQU @SubStr(<&val>,z+1,)	;; Value
    ENDIF

    ;; replace . with _
    z INSTR 1,x,
    IF z EQ 0
        w TEXTEQU x
        x CATSTR x,<.0> ;; prevent error message
    ELSE
        w CATSTR @SubStr(%x,1,z-1),<_>,@SubStr(%x,z+1,)
    ENDIF

    ;; replace - with _
    zz INSTR 1,w,
    IF zz EQ 0
        ww TEXTEQU w
    ELSE
        ww CATSTR @SubStr(%w,1,zz-1),<_>,@SubStr(%w,zz+1,)
    ENDIF

    ;; figure out global name for constant
    z SIZESTR y ;; use last char for size distiction
    ww CATSTR <__>,ww,,@SubStr(%y,z,1)

    IF (OPATTR(ww)) EQ 0 ;; not defined
        CONST SEGMENT
            ww y x
        CONST ENDS
    ENDIF
    EXITM ww
ENDM

; INITIALISED DATA * * * * * * * * * * * * * * * * * * * * * * * * * *
.data
txt_Query_Fullscn				db		"Do you want to run in fullscreen?",0
txt_Title_Fullscn				db		"Graphics mode",0
txt_Window_Title				db		"NeHe's Texturemapped Outline Font Tutorial",0
txt_OpenGL						db		"OpenGL",0
txt_WC_Failed					db		"Failed to registed window class",0
txt_GL_Context_Failed			db		"Failed to create a GL device context",0
txt_RC_Failed					db		"Failed to create a GL rendering context",0
txt_PixelFormat_Failed			db		"Failed to find suitable PixelFormat",0
txt_ActivateRC_Failed			db		"Failed to activate GL rendering context",0
txt_GLInit_Failed				db		"Initialisation failed",0
txt_SetPixelFormat_Failed		db		"Failed to set PixelFormat",0
txt_WindowCreateError			db		"Window creation error",0
txt_Error						db		"Error",0
;
txt_FontName					db		"Wingdings",0
txt_Lights_BMP					db		"Lights.BMP",0
txt_N_Skull						db		"N",0

; UNINITIALISED DATA * * * * * * * * * * * * * * * * * * * * * * * * *
.data?
gmf	GLYPHMETRICSFLOAT 256 dup (<>)	; Define GMF array

rot				GLfloat		?	; holds rotational value of text
base			GLuint		?	; Font base

hRC				HGLRC     	?	;
hDC           	HDC       	?	; Open GL window structures
hWnd          	HWND      	?	;
hInstance     	HINSTANCE	?	;

keys          	db 256 dup (?)	; keypress status
fullscreen    	BOOL		?	; fullscreen toggle
active        	BOOL     	?	; window active toggle/status

txt_Buffer		byte 64 dup (?)	; A general text buffer for string manipulation

texture			GLuint 		?	; Texture handle
BMPTexture		DWORD		?	; Texture pointer

; BUGS BEGIN HERE * * * * * * * * * * * * * * * * * * * * * * * * * *
.code

start:
invoke GetModuleHandle,0
mov hInstance, eax
invoke WinMain,hInstance,0,0,0
invoke ExitProcess, eax
;** END

; PROC WinMain * * * * * * * * * * * * * * * * * * * * * * * * * * * *
WinMain	proc hInst:HWND, hPrevInst:UINT, CmdLine:WPARAM, CmdShow:LPARAM
    LOCAL msg:MSG, done:UINT

    ; Check	if we should start in full screen
    invoke MessageBox,NULL,addr	txt_Query_Fullscn,addr txt_Title_Fullscn,MB_YESNO or MB_ICONQUESTION

    .IF	eax	== IDNO
        mov	fullscreen,FALSE
    .ELSE
        mov	fullscreen,TRUE
    .ENDIF

    invoke CreateGLWindow,addr txt_Window_Title,640,480,16,fullscreen
    .IF	!eax
        ret
    .ENDIF

    mov	done,FALSE

    .WHILE !done
        invoke PeekMessage,addr	msg,NULL,0,0,PM_REMOVE
        .IF	eax
            .IF	msg.message	== WM_QUIT
                mov	done,TRUE
            .ELSE
                invoke TranslateMessage,addr msg
                invoke DispatchMessage,addr	msg
            .ENDIF
        .ELSE
            invoke DrawGLScene
            .IF ((active) && (!eax)) || (keys[VK_ESCAPE])
                mov done, 1
            .ELSE
                invoke SwapBuffers,hDC
            .ENDIF

            .IF	keys[VK_F1]
                mov	keys[VK_F1],FALSE
                invoke KillGLWindow
                xor	fullscreen,	1
                invoke CreateGLWindow,addr txt_Window_Title,640,480,16,fullscreen
                .IF	!eax
                    mov	eax,FALSE
                .ENDIF
            .ENDIF
        .ENDIF
    .ENDW
    invoke KillGLWindow

    mov	eax,msg.wParam
    ret
WinMain	endp

; PROC InitGL * * * * * * * * * * * * * * * * * * * * * * * * * * * *
InitGL proc

    invoke LoadGLTextures	; Load textures
    .IF (eax == NULL)		; Exit if the textures failed to load
        ret
    .ENDIF

    invoke BuildFont	; Create the outline font

      invoke glShadeModel,GL_SMOOTH
      _glClearColor 0.0f,0.0f,0.0f,0.5f
      _glClearDepth 1.0f
      invoke glEnable,GL_DEPTH_TEST
      invoke glDepthFunc,GL_LEQUAL
     invoke glHint,GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST
     invoke glEnable, GL_LIGHT0
     invoke glEnable, GL_LIGHTING

     invoke glEnable, GL_TEXTURE_2D					; Enable texture mapping
     invoke glBindTexture, GL_TEXTURE_2D, texture	; Select the texture

    mov eax, 1
      ret
InitGL endp

; PROC CreateGLWindow * * * * * * * * * * * * * * * * * * * * * * * *
CreateGLWindow proc WinTitle:DWORD, WinWidth:DWORD, WinHeight:DWORD, WinBits:UINT, WinFullscreen:BOOL
    LOCAL dwExStyle:DWORD, dwStyle:DWORD, PixelFormat:GLuint
    LOCAL WindowRect:RECT, dmScreenSettings:DEVMODE, wc:WNDCLASS, pfd:PIXELFORMATDESCRIPTOR

    mov WindowRect.left,0
    mov WindowRect.top,0
    push WinWidth
    pop WindowRect.right
    push WinHeight
    pop WindowRect.bottom

    push WinFullscreen
    pop fullscreen

    invoke GetModuleHandle,NULL
    mov hInstance,eax
    mov wc.style,CS_HREDRAW or CS_VREDRAW or CS_OWNDC
    mov wc.lpfnWndProc, offset WndProc
    mov wc.cbClsExtra,0
    mov wc.cbWndExtra,0
    push hInstance
    pop wc.hInstance
    mov wc.hbrBackground,NULL
    mov wc.lpszMenuName,NULL
    mov wc.lpszClassName,offset txt_OpenGL
    invoke LoadIcon,NULL,IDI_WINLOGO
    mov wc.hIcon,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov wc.hCursor,eax

    ; Register the window class
    invoke RegisterClass,addr wc
    .IF !eax
        invoke MessageBox,NULL,addr txt_WC_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF

    ; Set fullscreen, if appropriate
    .IF fullscreen
        ZeroMemory &dmScreenSettings,sizeof(dmScreenSettings)
        mov dmScreenSettings.dmSize,sizeof dmScreenSettings
        push WinWidth
        pop dmScreenSettings.dmPelsWidth
        push WinHeight
        pop dmScreenSettings.dmPelsHeight
        push WinBits
        pop dmScreenSettings.dmBitsPerPel
        mov dmScreenSettings.dmFields,DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT

        ; Try to set selected mode
        invoke ChangeDisplaySettings,addr dmScreenSettings,CDS_FULLSCREEN
        .IF eax != DISP_CHANGE_SUCCESSFUL
            ; Original code prompts user, and asks to set to window or exit. I'll just set to window mode
            mov fullscreen,FALSE
        .ENDIF
    .ENDIF

    ; Check if we are (still?) in fullscreen
    .IF fullscreen
        mov dwExStyle, WS_EX_APPWINDOW
        mov dwStyle,WS_POPUP
        invoke ShowCursor,FALSE
    .ELSE
        mov dwExStyle,WS_EX_APPWINDOW or WS_EX_WINDOWEDGE
        mov dwStyle, WS_OVERLAPPEDWINDOW
    .ENDIF

    invoke AdjustWindowRectEx,addr WindowRect,dwStyle,FALSE,dwExStyle
    mov eax, WindowRect.left
    sub WindowRect.right,eax
    mov eax, WindowRect.top
    sub WindowRect.bottom, eax

    mov eax,dwStyle
    or eax,WS_CLIPSIBLINGS or WS_CLIPCHILDREN
    invoke CreateWindowEx,dwExStyle,addr txt_OpenGL,addr txt_Window_Title,eax,
        0,0,WindowRect.right,WindowRect.bottom,NULL,NULL,hInstance,NULL
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,txt_WindowCreateError,txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax, FALSE
        ret
    .ENDIF
     mov hWnd,eax

    ZeroMemory &pfd,sizeof(PIXELFORMATDESCRIPTOR)
    mov pfd.nSize,sizeof(PIXELFORMATDESCRIPTOR)
    mov pfd.nVersion,1
    mov pfd.dwFlags,PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER
    mov pfd.iPixelType,PFD_TYPE_RGBA
    mov pfd.cColorBits,16
    mov pfd.cDepthBits,16
    mov pfd.dwLayerMask,PFD_MAIN_PLANE

    ; Try to get a device context
    invoke GetDC,hWnd
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,addr txt_GL_Context_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF
    mov hDC,eax

    ; Select pixel format
    invoke ChoosePixelFormat,hDC,addr pfd
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,addr txt_PixelFormat_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF
    mov PixelFormat,eax

    ; Set pixel format
    invoke SetPixelFormat,hDC,PixelFormat,addr pfd
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,addr txt_SetPixelFormat_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF

    ; Get rendering context
    invoke wglCreateContext,hDC
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,addr txt_RC_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF
    mov hRC,eax

    ; Activate rendering context
    invoke wglMakeCurrent,hDC,hRC
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,addr txt_ActivateRC_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF

    invoke ShowWindow,hWnd,SW_SHOW
    invoke SetForegroundWindow,hWnd
    invoke SetFocus,hWnd
    invoke ReSizeGLScene,WinWidth,WinHeight

    invoke InitGL
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,addr txt_GLInit_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF

    mov eax,TRUE
    ret
CreateGLWindow endp

; PROC KillGLWindow * * * * * * * * * * * * * * * * * * * * * * * * *
KillGLWindow proc
    .IF fullscreen
        invoke ChangeDisplaySettings,NULL,0
        invoke ShowCursor,TRUE
    .ENDIF

    .IF hRC
        invoke wglMakeCurrent,NULL,NULL
        invoke wglDeleteContext,hRC
        mov hRC,NULL
    .ENDIF

    .IF hDC
        invoke ReleaseDC,hWnd,hDC
        mov hDC,NULL
    .ENDIF

    .IF hWnd
        invoke DestroyWindow,hWnd
        mov hWnd,NULL
    .ENDIF

    invoke UnregisterClass,addr txt_OpenGL,hInstance
    mov hInstance,NULL

    invoke KillFont

    ret
KillGLWindow endp

; PROC WndProc * * * * * * * * * * * * * * * * * * * * * * * * * * * *
WndProc proc hWind:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg == WM_ACTIVATE
        mov eax,wParam
        .IF !ah
            mov active,TRUE
        .ELSE
              mov active,FALSE
        .ENDIF
        xor eax, eax
        ret
    .ELSEIF uMsg==WM_SYSCOMMAND
          .IF wParam == SC_SCREENSAVE or SC_MONITORPOWER
              xor eax, eax
              ret
        .ENDIF
    .ELSEIF uMsg==WM_CLOSE
          invoke PostQuitMessage,0
        xor eax, eax
        ret
      .ELSEIF uMsg==WM_KEYDOWN
        mov eax,wParam
        mov keys[eax],TRUE
        xor eax, eax
        ret
      .ELSEIF uMsg==WM_KEYUP
        mov eax,wParam
        mov keys[eax],FALSE
        xor eax, eax
        ret
      .ELSEIF uMsg==WM_SIZE
        mov eax,lParam
        mov ebx,eax
        and eax,00000000000000001111111111111111b
        shr ebx,16
        invoke ReSizeGLScene,eax,ebx
        xor eax, eax
        ret
      .ENDIF

      invoke DefWindowProc,hWind,uMsg,wParam,lParam
      ret
WndProc endp

; PROC LoadBMP * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; This procedure only works with 24-bit BMP files.
; But it is small and avoids the use of glaux.
LoadBMP proc FileName:DWORD
    LOCAL FileHandle:DWORD, FileSize:DWORD, BytesRead:DWORD, BMP:DWORD, ImageSize:DWORD

    ; Open the file
    invoke CreateFile, FileName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0
     mov FileHandle, eax

     .IF eax==INVALID_HANDLE_VALUE	; get outta here if we failed to open the file
         xor eax, eax
         ret
     .ENDIF

     ; Determine size of the file
     invoke GetFileSize, FileHandle, 0
     mov FileSize, eax

     ; Allocate space for the file.
     invoke GlobalAlloc, GPTR, FileSize
     mov BMP, eax

     ; Read the file into memory
     invoke ReadFile, FileHandle, BMP, FileSize, addr BytesRead, 0
     invoke CloseHandle, FileHandle
     mov eax, BytesRead
     .IF (eax != FileSize)	; Free memory & exit if the readfile failed
         invoke GlobalFree, BMP
         xor eax, eax
         ret
     .ENDIF

    ; BMP file has the colours as BGR instead of RGB. Swap the R & B colours.
    mov ebx, BMP
    mov eax, 1
    mul (BMPFILE PTR [ebx]).info.biWidth
    mul (BMPFILE PTR [ebx]).info.biHeight
    lea ebx, (BMPFILE PTR [ebx]).data

    mov ecx, eax
@@:	mov al, [ebx]
    xchg al, [ebx+2]
    mov [ebx], al
    add ebx, 3
    loop @B

    ; Return a handle to the BMP
    mov eax, BMP
    ret
LoadBMP endp

; PROC LoadGLTextures * * * * * * * * * * * * * * * * * * * * * * * *
LoadGLTextures proc
    invoke LoadBMP, addr txt_Lights_BMP	; Load the BMP from disk & return a pointer to a BMPFILE structure
    mov BMPTexture, eax

    invoke glGenTextures, 1, addr texture

    invoke glBindTexture, GL_TEXTURE_2D, texture
    mov eax, BMPTexture
    lea ebx, (BMPFILE PTR [eax]).data	; Find the address of the BMP data
    invoke gluBuild2DMipmaps, GL_TEXTURE_2D, 3, (BMPFILE PTR [eax]).info.biWidth, (BMPFILE PTR [eax]).info.biHeight, \
        GL_RGB, GL_UNSIGNED_BYTE, ebx
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST

    invoke glTexGeni, GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR	; Texturing contour anchored to the object
    invoke glTexGeni, GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR	; Texturing contour anchored to the object
    invoke glEnable, GL_TEXTURE_GEN_S
    invoke glEnable, GL_TEXTURE_GEN_T

    .IF (BMPTexture != NULL)	; Remember to free the memory for the BMP texture
        invoke GlobalFree, BMPTexture
        mov eax, 1
    .ELSE
        xor eax, eax
    .ENDIF

    ret
LoadGLTextures endp

; PROC ReSizeGLScene * * * * * * * * * * * * * * * * * * * * * * * * *
ReSizeGLScene proc widthx:DWORD, heighty:DWORD
    LOCAL ratio:GLdouble

      .IF heighty == 0
        mov heighty, 1 ; prevent div by zero
      .ENDIF
    invoke glViewport, 0, 0, widthx, heighty
      invoke glMatrixMode, GL_PROJECTION
      _glLoadIdentity

      fild widthx
      fild heighty
      fdivp st(1),st(0)
      fstp ratio
      _gluPerspective 45.0f, ratio, 0.1f,100.0f
      invoke glMatrixMode, GL_MODELVIEW
      _glLoadIdentity
      ret
ReSizeGLScene endp

; PROC BuildFont * * * * * * * * * * * * * * * * * * * * * * * * * * *
BuildFont proc
    LOCAL font:HFONT	; windows font ID

    invoke glGenLists, 256	; Create storage for 256 Characters
    mov base, eax

    invoke CreateFont, -12, 0, 0,0, FW_BOLD, FALSE, FALSE, FALSE, SYMBOL_CHARSET, \
        OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, FF_DONTCARE or DEFAULT_PITCH, addr txt_FontName
    mov font, eax

    invoke SelectObject, hDC, font
    invoke wglUseFontOutlines, hDC, 0, 255, base, fpc(0.1), fpc(0.2), WGL_FONT_POLYGONS, offset gmf

    mov eax, 1
    ret
BuildFont endp

; PROC KillFont * * * * * * * * * * * * * * * * * * * * * * * * * * *
KillFont proc
    invoke glDeleteLists, base, 256
    mov eax, 1
    ret
KillFont endp

; PROC DrawGLScene * * * * * * * * * * * * * * * * * * * * * * * * * *
DrawGLScene proc
    LOCAL c4:QWORD
    LOCAL c1:GLfloat, c2:GLfloat, c3:GLfloat

      invoke glClear,GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT
    invoke glLoadIdentity
    ; Position the text
    fld rot
    fdiv fpc(16.0)
    fcos
    fmul fpc(1.1)
    fstp c1

    fld rot
    fdiv fpc(20.0)
    fsin
    fmul fpc(0.8)
    fstp c2

    invoke glTranslatef, c1, c2, fpc(-3.0)

    ; Rotate the text
    invoke glRotatef, rot, fpc(1.0), fpc(0.0), fpc(0.0)		; Rotate on the X axis
    fld rot
    fmul fpc(1.2)
    fstp c1
    invoke glRotatef, rot, fpc(0.0), fpc(1.0), fpc(0.0)		; Rotate on the Y axis
    fld rot
    fmul fpc(1.4)
    fstp c1
    invoke glRotatef, rot, fpc(0.0), fpc(0.0), fpc(1.0)		; Rotate on the Z axis

    invoke glTranslatef, fpc(-0.35), fpc(-0.35), fpc(0.1)	; Centre on X, Y & Z axis

      invoke glPrint, addr txt_N_Skull			; Draw the skull & crossbones symbol

      fld rot			; Increase the rotation variable
      fadd fpc(0.1)
      fstp rot

    mov eax, 1
    ret
DrawGLScene endp

; PROC glPrint * * * * * * * * * * * * * * * * * * * * * * * * * * * *
glPrint proc fmt:DWORD
    LOCAL textlen:REAL4
    LOCAL stringlength:UINT

    .IF (fmt == NULL)
        ret
    .ENDIF

    fild fpc(0.0)	; load zero to st0. this is the width of the outline text.

    invoke StrLen, fmt	; discover length of the fmt parameter
    mov stringlength, eax

    mov ecx, eax		; Loop for each character in the fmt parameter
@@:	xor ebx, ebx 		; mov ebx, 0

    mov eax, fmt		; st0 = st0 + gmf[fmt+ecx].gmfCellIncX;
    mov bl, [ecx+eax]
    mov eax, GMFSIZE
    mul ebx
    fadd gmf[eax].gmfCellIncX

    loop @B

    fdiv fpc(2.0)		; divide st0 by two & change signs to negative
    fchs
    fstp textlen
    invoke glTranslatef, textlen, fpc(0.0), fpc(0.0)

    invoke glPushAttrib, GL_LIST_BIT
    invoke glListBase, base
    invoke glCallLists, stringlength, GL_UNSIGNED_BYTE, fmt
    invoke glPopAttrib

    ret
glPrint endp

end start


Lesson 16

; Attempt at NeHe's tutorial 16 : Fog.
;
; Author: Greg Helps [x86@ozducati.com]
; Date: July 10 2003
;
; With thanks to NeHe for the OpenGL tutorials
; Inspiration for x86 Assembler from Nico
; OpenGL x86 includes originally from hardcode
; although the site is now offline.
; I've included the OpenGL includes needed to compile this program.
;
; Bugs are mine.
;
; I'm a beginner. If you've any comments / suggestions / questions - bring em on
;
; Please note: Assembled with MASM32V8 and windows.inc (Version 1.26e)
; Both available from
;
.586
.model flat, stdcall
option casemap:none

; INCLUDES * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
include c:\masm32\include\windows.inc
include c:\masm32\include\kernel32.inc
include c:\masm32\include\user32.inc
include c:\masm32\include\gdi32.inc
include .\gl.def
include .\glu.def
include .\winextra.def

includelib c:\masm32\lib\kernel32.lib
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\gdi32.lib
includelib c:\masm32\lib\opengl32.lib
includelib c:\masm32\lib\glu32.lib

; Missing from include files * * * * * * * * * * * * * * * * * * * * *
DM_BITSPERPEL       = 00040000h
DM_PELSWIDTH        = 00080000h
DM_PELSHEIGHT       = 00100000h

; PROTOTYPES * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
WinMain         PROTO :HWND, :UINT, :WPARAM, :LPARAM
CreateGLWindow  PROTO :DWORD, :DWORD, :DWORD, :UINT, :BOOL
WndProc         PROTO :HWND, :UINT, :WPARAM, :LPARAM
KillGLWindow    PROTO
ReSizeGLScene   PROTO :GLsizei, :GLsizei
InitGL          PROTO
DrawGLScene     PROTO
LoadGLTextures  PROTO

; STRUCTURES * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; Define BMP structure
; I use this to reference width/height & data from the BMP file
BMPFILE STRUCT
    header BITMAPFILEHEADER <>
    info BITMAPINFOHEADER <>
    data DWORD ?
BMPFILE ENDS

; MACROS * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; This macro is defined incorrectly in the includes. I fix him here
; rather than modify the include file.
; (GLenum pname, GLfloat param)
_glFogf MACRO pname,param
 gl_fpush param
 push pname
 call glFogf
ENDM

; fpc macro allows constants to be used with floating point operations.
; Authored by bitRAKE from Win32ASM board
; Great resource for assembler hackers
fpc MACRO val:REQ
    LOCAL w,x,y,z,zz,ww

    ;; split type and value, defaulting to REAL4
    z INSTR 1,<&val>,
    IF z EQ 0
        y TEXTEQU
        x TEXTEQU <&val>
    ELSE
        y TEXTEQU @SubStr(<&val>,1,z-1) ;; Type
        x TEXTEQU @SubStr(<&val>,z+1,)  ;; Value
    ENDIF

    ;; replace . with _
    z INSTR 1,x,
    IF z EQ 0
        w TEXTEQU x
        x CATSTR x,<.0> ;; prevent error message
    ELSE
        w CATSTR @SubStr(%x,1,z-1),<_>,@SubStr(%x,z+1,)
    ENDIF

    ;; replace - with _
    zz INSTR 1,w,
    IF zz EQ 0
        ww TEXTEQU w
    ELSE
        ww CATSTR @SubStr(%w,1,zz-1),<_>,@SubStr(%w,zz+1,)
    ENDIF

    ;; figure out global name for constant
    z SIZESTR y ;; use last char for size distiction
    ww CATSTR <__>,ww,,@SubStr(%y,z,1)

    IF (OPATTR(ww)) EQ 0 ;; not defined
        CONST SEGMENT
            ww y x
        CONST ENDS
    ENDIF
    EXITM ww
ENDM

; INITIALISED DATA * * * * * * * * * * * * * * * * * * * * * * * * * *
.data
LightAmbient                    GLfloat 0.5f,0.5f,0.5f,1.0f
LightDiffuse                    GLfloat 1.0f,1.0f,1.0f,1.0f
LightPosition                   GLfloat 0.0f,0.0f,2.0f,1.0f
fogcolor                        GLfloat 0.5f, 0.5f, 0.5f, 1.0f  ; Fog color
xrot                            GLfloat 0.0f    ; X Rotation
yrot                            GLfloat 0.0f    ; Y Rotation
xspeed                          GLfloat 0.0f    ; X Rotation speed
yspeed                          GLfloat 0.0f    ; Y Rotation speed
z                               REAL4   -5.0f   ; Depth into the screen
fogmode                         GLuint  GL_EXP, GL_EXP2, GL_LINEAR  ; Storage for three types of fog
fogfilter                       GLuint  0   ; Which fog to use
;
txt_Query_Fullscn               db      "Do you want to run in fullscreen?",0
txt_Title_Fullscn               db      "Graphics mode",0
txt_Window_Title                db      "NeHe's OpenGL Fog Tutorial",0
txt_OpenGL                      db      "OpenGL",0
txt_WC_Failed                   db      "Failed to registed window class",0
txt_GL_Context_Failed           db      "Failed to create a GL device context",0
txt_RC_Failed                   db      "Failed to create a GL rendering context",0
txt_PixelFormat_Failed          db      "Failed to find suitable PixelFormat",0
txt_ActivateRC_Failed           db      "Failed to activate GL rendering context",0
txt_GLInit_Failed               db      "Initialisation failed",0
txt_SetPixelFormat_Failed       db      "Failed to set PixelFormat",0
txt_WindowCreateError           db      "Window creation error",0
txt_Error                       db      "Error",0
;
txt_Crate_BMP                   db      "Crate.BMP",0

; UNINITIALISED DATA * * * * * * * * * * * * * * * * * * * * * * * * *
.data?
hRC             HGLRC       ?   ;
hDC             HDC         ?   ; Open GL window structures
hWnd            HWND        ?   ;
hInstance       HINSTANCE   ?   ;

keys            db 256 dup (?)  ; keypress status
fullscreen      BOOL        ?   ; fullscreen toggle
active          BOOL        ?   ; window active toggle/status

texture         GLuint      3 dup (?)   ; GL Texture handle x 3
light           GLuint      ?   ; Lighting on/off
filter          GLuint      ?   ; Which filter to use
BMPTexture      DWORD       ?   ; Texture pointer

gp              BOOL        ?   ; G Pressed?
lp              BOOL        ?   ; L Pressed?
fp              BOOL        ?   ; F Pressed?

; BUGS BEGIN HERE * * * * * * * * * * * * * * * * * * * * * * * * * *
.code

start:
invoke GetModuleHandle,0
mov hInstance, eax
invoke WinMain,hInstance,0,0,0
invoke ExitProcess, eax
;** END

; PROC WinMain * * * * * * * * * * * * * * * * * * * * * * * * * * * *
WinMain proc hInst:HWND, hPrevInst:UINT, CmdLine:WPARAM, CmdShow:LPARAM
    LOCAL msg:MSG, done:UINT

    ; Check if we should start in full screen
    invoke MessageBox,NULL,addr txt_Query_Fullscn,addr txt_Title_Fullscn,MB_YESNO or MB_ICONQUESTION

    .IF eax == IDNO
        mov fullscreen,FALSE
    .ELSE
        mov fullscreen,TRUE
    .ENDIF

    invoke CreateGLWindow,addr txt_Window_Title,640,480,16,fullscreen
    .IF !eax
        ret
    .ENDIF

    mov done,FALSE

    .WHILE !done
        invoke PeekMessage,addr msg,NULL,0,0,PM_REMOVE
        .IF eax
            .IF msg.message == WM_QUIT
                mov done,TRUE
            .ELSE
                invoke TranslateMessage,addr msg
                invoke DispatchMessage,addr msg
            .ENDIF
        .ELSE
            invoke DrawGLScene
            .IF ((active) && (!eax)) || (keys[VK_ESCAPE])
                mov done, 1
            .ELSE
                invoke SwapBuffers,hDC
            .ENDIF

            .IF keys[VK_F1]
                mov keys[VK_F1],FALSE
                invoke KillGLWindow
                xor fullscreen, 1
                invoke CreateGLWindow,addr txt_Window_Title,640,480,16,fullscreen
                .IF !eax
                    mov eax,FALSE
                .ENDIF
            .ENDIF
            .IF keys['L'] && !lp
                mov lp, TRUE
                xor light, 1
                .IF light == FALSE
                    invoke glDisable, GL_LIGHTING
                .ELSE
                    invoke glEnable, GL_LIGHTING
                .ENDIF
            .ENDIF
            .IF !keys['L']
                mov lp, FALSE
            .ENDIF

            .IF keys['F'] && !fp
                mov fp, TRUE
                inc filter
                .IF filter > 2
                    mov filter, 0
                .ENDIF
            .ENDIF
            .IF !keys['F']
                mov fp, FALSE
            .ENDIF

            .IF keys['G'] && !gp
                mov gp, TRUE
                inc fogfilter
                .IF fogfilter > 2
                    mov fogfilter, 0
                .ENDIF
                mov eax, fogfilter
                shl eax, 2  ; mul eax, sizeof(GLuint)
                invoke glFogi, GL_FOG_MODE, fogmode[eax]
            .ENDIF
            .IF !keys['G']
                mov gp, FALSE
            .ENDIF

            .IF keys[VK_PRIOR] == TRUE
                fld z
                fsub fpc(0.02)
                fstp z
            .ENDIF

            .IF keys[VK_NEXT] == TRUE
                fld z
                fadd fpc(0.02)
                fstp z
            .ENDIF

            .IF keys[VK_UP] == TRUE
                fld xspeed
                fsub fpc(0.01)
                fstp xspeed
            .ENDIF

            .IF keys[VK_DOWN] == TRUE
                fld xspeed
                fadd fpc(0.01)
                fstp xspeed
            .ENDIF

            .IF keys[VK_RIGHT] == TRUE
                fld yspeed
                fadd fpc(0.01)
                fstp yspeed
            .ENDIF

            .IF keys[VK_LEFT] == TRUE
                fld yspeed
                fsub fpc(0.01)
                fstp yspeed
            .ENDIF
        .ENDIF
    .ENDW
    invoke KillGLWindow

    mov eax,msg.wParam
    ret
WinMain endp

; PROC InitGL * * * * * * * * * * * * * * * * * * * * * * * * * * * *
InitGL proc

    invoke LoadGLTextures   ; Load textures
    .IF (eax == NULL)       ; Exit if the textures failed to load
        ret
    .ENDIF

    invoke glEnable, GL_TEXTURE_2D                              ; Enable texture mapping
    invoke glShadeModel,GL_SMOOTH                               ; Enable smooth shading
    _glClearColor 0.5f, 0.5f, 0.5f, 1.0f                        ; Clear to the color of the fog
    _glClearDepth 1.0f                                          ; Depth buffer setup
    invoke glEnable, GL_DEPTH_TEST                              ; Enable depth testing
    invoke glDepthFunc, GL_LEQUAL                               ; Set type of depth test
    invoke glHint,GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST      ; Set nice perspective calculations

    invoke glLightfv,GL_LIGHT1, GL_AMBIENT, addr LightAmbient   ; Setup ambient light
    invoke glLightfv,GL_LIGHT1, GL_DIFFUSE, addr LightDiffuse   ; Setup diffuse light
    invoke glLightfv,GL_LIGHT1, GL_POSITION, addr LightPosition ; Position the light
    invoke glEnable,GL_LIGHT1                                   ; Enable light one

    mov eax, fogfilter
    shl eax, 2  ; mul eax, sizeof(GLuint)
    invoke glFogi, GL_FOG_MODE, fogmode[eax]    ; Fog mode
    invoke glFogfv, GL_FOG_COLOR, addr fogcolor ; Set fog color
    _glFogf GL_FOG_DENSITY, 0.35f               ; How dense will the fog be
    invoke glHint, GL_FOG_HINT, GL_DONT_CARE    ; Fog hint value
    _glFogf GL_FOG_START, 1.0f                  ; Fog start depth
    _glFogf GL_FOG_END, 5.0f                    ; Fog End depth
    invoke glEnable, GL_FOG                     ; Enable fog

    mov eax, 1
    ret
InitGL endp

; PROC CreateGLWindow * * * * * * * * * * * * * * * * * * * * * * * *
CreateGLWindow proc WinTitle:DWORD, WinWidth:DWORD, WinHeight:DWORD, WinBits:UINT, WinFullscreen:BOOL
    LOCAL dwExStyle:DWORD, dwStyle:DWORD, PixelFormat:GLuint
    LOCAL WindowRect:RECT, dmScreenSettings:DEVMODE, wc:WNDCLASS, pfd:PIXELFORMATDESCRIPTOR

    mov WindowRect.left,0
    mov WindowRect.top,0
    push WinWidth
    pop WindowRect.right
    push WinHeight
    pop WindowRect.bottom

    push WinFullscreen
    pop fullscreen

    invoke GetModuleHandle,NULL
    mov hInstance,eax
    mov wc.style,CS_HREDRAW or CS_VREDRAW or CS_OWNDC
    mov wc.lpfnWndProc, offset WndProc
    mov wc.cbClsExtra,0
    mov wc.cbWndExtra,0
    push hInstance
    pop wc.hInstance
    mov wc.hbrBackground,NULL
    mov wc.lpszMenuName,NULL
    mov wc.lpszClassName,offset txt_OpenGL
    invoke LoadIcon,NULL,IDI_WINLOGO
    mov wc.hIcon,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov wc.hCursor,eax

    ; Register the window class
    invoke RegisterClass,addr wc
    .IF !eax
        invoke MessageBox,NULL,addr txt_WC_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF

    ; Set fullscreen, if appropriate
    .IF fullscreen
        ZeroMemory &dmScreenSettings,sizeof(dmScreenSettings)
        mov dmScreenSettings.dmSize,sizeof dmScreenSettings
        push WinWidth
        pop dmScreenSettings.dmPelsWidth
        push WinHeight
        pop dmScreenSettings.dmPelsHeight
        push WinBits
        pop dmScreenSettings.dmBitsPerPel
        mov dmScreenSettings.dmFields,DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT

        ; Try to set selected mode
        invoke ChangeDisplaySettings,addr dmScreenSettings,CDS_FULLSCREEN
        .IF eax != DISP_CHANGE_SUCCESSFUL
            ; Original code prompts user, and asks to set to window or exit. I'll just set to window mode
            mov fullscreen,FALSE
        .ENDIF
    .ENDIF

    ; Check if we are (still?) in fullscreen
    .IF fullscreen
        mov dwExStyle, WS_EX_APPWINDOW
        mov dwStyle,WS_POPUP
        invoke ShowCursor,FALSE
    .ELSE
        mov dwExStyle,WS_EX_APPWINDOW or WS_EX_WINDOWEDGE
        mov dwStyle, WS_OVERLAPPEDWINDOW
    .ENDIF

    invoke AdjustWindowRectEx,addr WindowRect,dwStyle,FALSE,dwExStyle
    mov eax, WindowRect.left
    sub WindowRect.right,eax
    mov eax, WindowRect.top
    sub WindowRect.bottom, eax

    mov eax,dwStyle
    or eax,WS_CLIPSIBLINGS or WS_CLIPCHILDREN
    invoke CreateWindowEx,dwExStyle,addr txt_OpenGL,addr txt_Window_Title,eax,
        0,0,WindowRect.right,WindowRect.bottom,NULL,NULL,hInstance,NULL
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,txt_WindowCreateError,txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax, FALSE
        ret
    .ENDIF
     mov hWnd,eax

    ZeroMemory &pfd,sizeof(PIXELFORMATDESCRIPTOR)
    mov pfd.nSize,sizeof(PIXELFORMATDESCRIPTOR)
    mov pfd.nVersion,1
    mov pfd.dwFlags,PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER
    mov pfd.iPixelType,PFD_TYPE_RGBA
    mov pfd.cColorBits,16
    mov pfd.cDepthBits,16
    mov pfd.dwLayerMask,PFD_MAIN_PLANE

    ; Try to get a device context
    invoke GetDC,hWnd
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,addr txt_GL_Context_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF
    mov hDC,eax

    ; Select pixel format
    invoke ChoosePixelFormat,hDC,addr pfd
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,addr txt_PixelFormat_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF
    mov PixelFormat,eax

    ; Set pixel format
    invoke SetPixelFormat,hDC,PixelFormat,addr pfd
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,addr txt_SetPixelFormat_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF

    ; Get rendering context
    invoke wglCreateContext,hDC
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,addr txt_RC_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF
    mov hRC,eax

    ; Activate rendering context
    invoke wglMakeCurrent,hDC,hRC
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,addr txt_ActivateRC_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF

    invoke ShowWindow,hWnd,SW_SHOW
    invoke SetForegroundWindow,hWnd
    invoke SetFocus,hWnd
    invoke ReSizeGLScene,WinWidth,WinHeight

    invoke InitGL
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,addr txt_GLInit_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF

    mov eax,TRUE
    ret
CreateGLWindow endp

; PROC KillGLWindow * * * * * * * * * * * * * * * * * * * * * * * * *
KillGLWindow proc
    .IF fullscreen
        invoke ChangeDisplaySettings,NULL,0
        invoke ShowCursor,TRUE
    .ENDIF

    .IF hRC
        invoke wglMakeCurrent,NULL,NULL
        invoke wglDeleteContext,hRC
        mov hRC,NULL
    .ENDIF

    .IF hDC
        invoke ReleaseDC,hWnd,hDC
        mov hDC,NULL
    .ENDIF

    .IF hWnd
        invoke DestroyWindow,hWnd
        mov hWnd,NULL
    .ENDIF

    invoke UnregisterClass,addr txt_OpenGL,hInstance
    mov hInstance,NULL

    ret
KillGLWindow endp

; PROC WndProc * * * * * * * * * * * * * * * * * * * * * * * * * * * *
WndProc proc hWind:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg == WM_ACTIVATE
        mov eax,wParam
        .IF !ah
            mov active,TRUE
        .ELSE
            mov active,FALSE
        .ENDIF
        xor eax, eax
        ret
    .ELSEIF uMsg==WM_SYSCOMMAND
        .IF wParam == SC_SCREENSAVE or SC_MONITORPOWER
            xor eax, eax
            ret
        .ENDIF
    .ELSEIF uMsg==WM_CLOSE
        invoke PostQuitMessage,0
        xor eax, eax
        ret
    .ELSEIF uMsg==WM_KEYDOWN
        mov eax,wParam
        mov keys[eax],TRUE
        xor eax, eax
        ret
    .ELSEIF uMsg==WM_KEYUP
        mov eax,wParam
        mov keys[eax],FALSE
        xor eax, eax
        ret
    .ELSEIF uMsg==WM_SIZE
        mov eax,lParam
        mov ebx,eax
        and eax,00000000000000001111111111111111b
        shr ebx,16
        invoke ReSizeGLScene,eax,ebx
        xor eax, eax
        ret
    .ENDIF

    invoke DefWindowProc,hWind,uMsg,wParam,lParam
    ret
WndProc endp

; PROC LoadBMP * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; This procedure only works with 24-bit BMP files.
; But it is small and avoids the use of glaux.
; A check should probably be added here to make sure we're dealing with a 24bit BMP
LoadBMP proc FileName:DWORD
    LOCAL FileHandle:DWORD, FileSize:DWORD, BytesRead:DWORD, BMP:DWORD, ImageSize:DWORD

    ; Open the file
    invoke CreateFile, FileName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0
    mov FileHandle, eax

    .IF eax==INVALID_HANDLE_VALUE   ; get outta here if we failed to open the file
        xor eax, eax
        ret
    .ENDIF

    ; Determine size of the file
    invoke GetFileSize, FileHandle, 0
    mov FileSize, eax

    ; Allocate space in memory for the file.
    invoke GlobalAlloc, GPTR, FileSize
    mov BMP, eax ; BMP = pointer to allocated space

    ; Read the file into memory
    invoke ReadFile, FileHandle, BMP, FileSize, addr BytesRead, 0
    invoke CloseHandle, FileHandle
    mov eax, BytesRead
    .IF (eax != FileSize)   ; Free memory & exit if the readfile failed
        invoke GlobalFree, BMP
        xor eax, eax
        ret
    .ENDIF

    ; BMP file has the colours as BGR instead of RGB. Swap the R & B colours.
    mov ebx, BMP    ; Pointer to start of BMPFILE structure
    mov eax, (BMPFILE PTR [ebx]).info.biWidth   ; eax = BMPwidth * BMPheight
    mul (BMPFILE PTR [ebx]).info.biHeight
    lea ebx, (BMPFILE PTR [ebx]).data           ; ebx = address of BMP data, ie: past the BMP header

    mov ecx, eax        ; Loop thru BMP data, swapping R & G colours
@@: mov al, [ebx]       ; al = Red value
    xchg al, [ebx+2]    ; Swap Green value with al, and al value with Green
    mov [ebx], al       ; Write back Green value
    add ebx, 3          ; Move to next block
    loop @B

    ; Return a handle to the BMP
    mov eax, BMP
    ret
LoadBMP endp

; PROC LoadGLTextures * * * * * * * * * * * * * * * * * * * * * * * *
LoadGLTextures proc
    invoke LoadBMP, addr txt_Crate_BMP  ; Load the BMP from disk & return a pointer to a BMPFILE structure
    mov BMPTexture, eax
    .IF (eax == NULL)   ; Quit if file load failed
        ret
    .ENDIF

    invoke glGenTextures, 3, addr texture   ; Create three textures

    ; Create Nearest Filtered Texture
    invoke glBindTexture, GL_TEXTURE_2D, texture[0]
    mov eax, BMPTexture
    lea ebx, (BMPFILE PTR [eax]).data
    invoke glTexImage2D, GL_TEXTURE_2D, 0, 3, (BMPFILE PTR [eax]).info.biWidth, (BMPFILE PTR [eax]).info.biHeight,\
        0, GL_RGB, GL_UNSIGNED_BYTE, ebx
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST

    ; Create Linear Filtered Texture
    invoke glBindTexture, GL_TEXTURE_2D, texture[1]
    mov eax, BMPTexture
    lea ebx, (BMPFILE PTR [eax]).data
    invoke glTexImage2D, GL_TEXTURE_2D, 0, 3, (BMPFILE PTR [eax]).info.biWidth, (BMPFILE PTR [eax]).info.biHeight,\
        0, GL_RGB, GL_UNSIGNED_BYTE, ebx
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR

    ; Create MipMapped Texture
    invoke glBindTexture, GL_TEXTURE_2D, texture[2]
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST
    mov eax, BMPTexture
    lea ebx, (BMPFILE PTR [eax]).data
    invoke gluBuild2DMipmaps, GL_TEXTURE_2D, 3, (BMPFILE PTR [eax]).info.biWidth, (BMPFILE PTR [eax]).info.biHeight,\
        GL_RGB, GL_UNSIGNED_BYTE, ebx

    .IF (BMPTexture != NULL)    ; Remember to free the memory for the BMPFILE allocated in LoadBMP
        invoke GlobalFree, BMPTexture
        mov eax, 1
    .ELSE
        xor eax, eax
    .ENDIF

    ret
LoadGLTextures endp

; PROC ReSizeGLScene * * * * * * * * * * * * * * * * * * * * * * * * *
ReSizeGLScene proc widthx:DWORD, heighty:DWORD
    LOCAL ratio:GLdouble

    .IF heighty == 0
        mov heighty, 1 ; prevent div by zero
    .ENDIF
    invoke glViewport, 0, 0, widthx, heighty
    invoke glMatrixMode, GL_PROJECTION
    invoke glLoadIdentity

    fild widthx
    fild heighty
    fdivp st(1),st(0)
    fstp ratio
    _gluPerspective 45.0f, ratio, 0.1f, 100.0f
    invoke glMatrixMode, GL_MODELVIEW
    invoke glLoadIdentity
    ret
ReSizeGLScene endp

; PROC DrawGLScene * * * * * * * * * * * * * * * * * * * * * * * * * *
DrawGLScene proc
    invoke glClear,GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT
    invoke glLoadIdentity
    _glTranslatef 0.0f, 0.0f, z

    _glRotatef xrot, 1.0f, 0.0f, 0.0f
    _glRotatef yrot, 0.0f, 1.0f, 0.0f

    mov eax, filter
    invoke glBindTexture, GL_TEXTURE_2D, texture[eax]

    invoke glBegin, GL_QUADS
    _glNormal3f 0.0f,0.0f,1.0f
    _glTexCoord2f 0.0f,0.0f
    _glVertex3f -1.0f,-1.0f, 1.0f
    _glTexCoord2f 1.0f,0.0f
    _glVertex3f  1.0f,-1.0f, 1.0f
    _glTexCoord2f 1.0f,1.0f
    _glVertex3f  1.0f, 1.0f, 1.0f
    _glTexCoord2f 0.0f,1.0f
    _glVertex3f -1.0f, 1.0f, 1.0f
  ;back
    _glNormal3f 0.0f,0.0f,-1.0f
    _glTexCoord2f 1.0f,0.0f
    _glVertex3f -1.0f,-1.0f,-1.0f
    _glTexCoord2f 1.0f,1.0f
    _glVertex3f -1.0f, 1.0f,-1.0f
    _glTexCoord2f 0.0f,1.0f
    _glVertex3f  1.0f, 1.0f,-1.0f
    _glTexCoord2f 0.0f,0.0f
    _glVertex3f  1.0f,-1.0f,-1.0f
  ;top
    _glNormal3f 0.0f,1.0f,0.0f
    _glTexCoord2f 0.0f,1.0f
    _glVertex3f -1.0f, 1.0f,-1.0f
    _glTexCoord2f 0.0f,0.0f
    _glVertex3f -1.0f, 1.0f, 1.0f
    _glTexCoord2f 1.0f,0.0f
    _glVertex3f  1.0f, 1.0f, 1.0f
    _glTexCoord2f 1.0f,1.0f
    _glVertex3f  1.0f, 1.0f,-1.0f
  ;bottom
    _glNormal3f 0.0f,-1.0f,0.0f
    _glTexCoord2f 1.0f,1.0f
    _glVertex3f -1.0f,-1.0f,-1.0f
    _glTexCoord2f 0.0f,1.0f
    _glVertex3f  1.0f,-1.0f,-1.0f
    _glTexCoord2f 0.0f,0.0f
    _glVertex3f  1.0f,-1.0f, 1.0f
    _glTexCoord2f 1.0f,0.0f
    _glVertex3f -1.0f,-1.0f, 1.0f
  ;right
    _glNormal3f 1.0f,0.0f,0.0f
    _glTexCoord2f 1.0f,0.0f
    _glVertex3f  1.0f,-1.0f,-1.0f
    _glTexCoord2f 1.0f,1.0f
    _glVertex3f  1.0f,1.0f,-1.0f
    _glTexCoord2f 0.0f,1.0f
    _glVertex3f  1.0f,1.0f, 1.0f
    _glTexCoord2f 0.0f,0.0f
    _glVertex3f  1.0f,-1.0f, 1.0f
  ;left
    _glNormal3f -1.0f,0.0f,0.0f
    _glTexCoord2f 0.0f,0.0f
    _glVertex3f -1.0f,-1.0f,-1.0f
    _glTexCoord2f 1.0f,0.0f
    _glVertex3f -1.0f,-1.0f, 1.0f
    _glTexCoord2f 1.0f,1.0f
    _glVertex3f -1.0f, 1.0f, 1.0f
    _glTexCoord2f 0.0f,1.0f
    _glVertex3f -1.0f, 1.0f,-1.0f
    invoke glEnd

    ; Rotation
    fld xrot    ; xrot += xspeed
    fadd xspeed
    fstp xrot

    fld yrot    ; yrot += yspeed
    fadd yspeed
    fstp yrot

    mov eax, 1
    ret
DrawGLScene endp

end start


Lesson 17

; Attempt at NeHe's tutorial 17 : Font display lists.
;
; Author: Greg Helps [x86@ozducati.com]
; Date: July 30 2003
;
; With thanks to NeHe for the OpenGL tutorials
; Inspiration for x86 Assembler from Nico
; OpenGL x86 includes originally from hardcode
; although the site is now offline.
; I've included the OpenGL includes needed to compile this program.
;
; Bugs are mine.
;
; I'm a beginner. If you've any comments / suggestions / questions - bring em on
;
; Please note: Assembled with MASM32V8 and windows.inc (Version 1.26e)
; Both available from
;
.586
.model flat, stdcall
option casemap:none

; INCLUDES * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
include c:\masm32\include\windows.inc
include c:\masm32\include\kernel32.inc
include c:\masm32\include\user32.inc
include c:\masm32\include\gdi32.inc
include c:\masm32\include\masm32.inc
include .\gl.def
include .\glu.def
include .\winextra.def

includelib c:\masm32\lib\kernel32.lib
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\gdi32.lib
includelib c:\masm32\lib\opengl32.lib
includelib c:\masm32\lib\glu32.lib
includelib c:\masm32\lib\masm32.lib

; Missing from include files * * * * * * * * * * * * * * * * * * * * *
DM_BITSPERPEL       = 00040000h
DM_PELSWIDTH        = 00080000h
DM_PELSHEIGHT       = 00100000h
ANTIALIASED_QUALITY = 4
WGL_FONT_POLYGONS 	= 1

; PROTOTYPES * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
WinMain 		PROTO :HWND, :UINT, :WPARAM, :LPARAM
CreateGLWindow 	PROTO :DWORD, :DWORD, :DWORD, :UINT, :BOOL
WndProc 		PROTO :HWND, :UINT, :WPARAM, :LPARAM
KillGLWindow 	PROTO
ReSizeGLScene 	PROTO :GLsizei, :GLsizei
InitGL 			PROTO
BuildFont		PROTO
KillFont		PROTO
glPrint			PROTO :GLint, :GLint, :DWORD, :UINT
DrawGLScene 	PROTO
LoadGLTextures	PROTO

; STRUCTURES * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; Define BMP structure
; I use this to reference width/height & data section from BMP files
BMPFILE STRUCT
    header BITMAPFILEHEADER <>
    info BITMAPINFOHEADER <>
    data DWORD ?
BMPFILE ENDS

; INITIALISED DATA * * * * * * * * * * * * * * * * * * * * * * * * * *
.data
gld_10_0						GLdouble	10.0f
r4_0_0							REAL4	0.0f
r4_0_01							REAL4	0.01f
r4_0_02							REAL4	0.02f
r4_0_0625						REAL4	0.0625f
r4_16_0							REAL4	16.0f
r4_1_0							REAL4	1.0f
r4_0_0081						REAL4	0.0081f
r4_235_0						REAL4	235.0f
r4_280_0						REAL4	280.0f
r4_30_0							REAL4	30.0f
r4_200_0						REAL4	200.0f
r4_250_0						REAL4	250.0f
r4_0_5							REAL4	0.5f
r4_230_0						REAL4	230.0f
r4_240_0						REAL4	240.0f
r4_m0_5							REAL4	-0.5f
r4_5_0							REAL4	2.0f
;
txt_Query_Fullscn				db		"Do you want to run in fullscreen?",0
txt_Title_Fullscn				db		"Graphics mode",0
txt_Window_Title				db		"NeHe's Font Display List Tutorial",0
txt_OpenGL						db		"OpenGL",0
txt_WC_Failed					db		"Failed to registed window class",0
txt_GL_Context_Failed			db		"Failed to create a GL device context",0
txt_RC_Failed					db		"Failed to create a GL rendering context",0
txt_PixelFormat_Failed			db		"Failed to find suitable PixelFormat",0
txt_ActivateRC_Failed			db		"Failed to activate GL rendering context",0
txt_GLInit_Failed				db		"Initialisation failed",0
txt_SetPixelFormat_Failed		db		"Failed to set PixelFormat",0
txt_WindowCreateError			db		"Window creation error",0
txt_Error						db		"Error",0
txt_NeHe						db		"NeHe",0
txt_Giueseppe					db		"Giuseppe D'Agata",0
;
txt_Font_BMP					db		"Font.BMP",0
txt_Bumps_BMP					db		"Bumps.BMP",0

; UNINITIALISED DATA * * * * * * * * * * * * * * * * * * * * * * * * *
.data?
hRC				HGLRC     	?	;
hDC           	HDC       	?	; Open GL window structures
hWnd          	HWND      	?	;
hInstance     	HINSTANCE	?	;
;
keys          	db 256 dup (?)	; keypress status
fullscreen    	BOOL		?	; fullscreen toggle
active        	BOOL     	?	; window active toggle/status
;
texture			GLuint 		2 dup (?)	; GL Texture handle x 2
BMPTexture		DWORD		2 dup (?)	; Texture pointer x 2
;
base			GLuint		?	; Base display list for the font
cnt1			GLfloat		?	; 1st temp used to move text & for colouring
cnt2			GLfloat		?	; you guessed it.. second temp

; BUGS BEGIN HERE * * * * * * * * * * * * * * * * * * * * * * * * * *
.code

start:
invoke GetModuleHandle,0
mov hInstance, eax
invoke WinMain,hInstance,0,0,0
invoke ExitProcess, eax
;** END

; PROC WinMain * * * * * * * * * * * * * * * * * * * * * * * * * * * *
WinMain	proc hInst:HWND, hPrevInst:UINT, CmdLine:WPARAM, CmdShow:LPARAM
    LOCAL msg:MSG, done:UINT

    ; Check	if we should start in full screen
    invoke MessageBox,NULL,addr	txt_Query_Fullscn,addr txt_Title_Fullscn,MB_YESNO or MB_ICONQUESTION

    .IF	eax	== IDNO
        mov	fullscreen,FALSE
    .ELSE
        mov	fullscreen,TRUE
    .ENDIF

    invoke CreateGLWindow,addr txt_Window_Title,640,480,16,fullscreen
    .IF	!eax
        ret
    .ENDIF

    mov	done,FALSE

    .WHILE !done
        invoke PeekMessage,addr	msg,NULL,0,0,PM_REMOVE
        .IF	eax
            .IF	msg.message	== WM_QUIT
                mov	done,TRUE
            .ELSE
                invoke TranslateMessage,addr msg
                invoke DispatchMessage,addr	msg
            .ENDIF
        .ELSE
            invoke DrawGLScene
            .IF ((active) && (!eax)) || (keys[VK_ESCAPE])
                mov done, 1
            .ELSE
                invoke SwapBuffers,hDC
            .ENDIF

            .IF	keys[VK_F1]
                mov	keys[VK_F1],FALSE
                invoke KillGLWindow
                xor	fullscreen,	1
                invoke CreateGLWindow,addr txt_Window_Title,640,480,16,fullscreen
                .IF	!eax
                    mov	eax,FALSE
                .ENDIF
            .ENDIF
        .ENDIF
    .ENDW
    invoke KillGLWindow

    mov	eax,msg.wParam
    ret
WinMain	endp

; PROC InitGL * * * * * * * * * * * * * * * * * * * * * * * * * * * *
InitGL proc

    invoke LoadGLTextures	; Load textures
    .IF (eax == NULL)		; Exit if the textures failed to load
        ret
    .ENDIF

    invoke BuildFont		; Build the font

    _glClearColor 0.0f, 0.0f, 0.0f, 0.0f						; Clear the background colour to black
    _glClearDepth 1.0f											; Depth buffer setup
    invoke glDepthFunc, GL_LEQUAL								; Set type of depth test
    invoke glBlendFunc, GL_SRC_ALPHA, GL_ONE					; Select the type of blending
    invoke glShadeModel,GL_SMOOTH								; Enable smooth shading
    invoke glEnable, GL_TEXTURE_2D								; Enable texture mapping

    mov eax, 1
      ret
InitGL endp

; PROC BuildFont * * * * * * * * * * * * * * * * * * * * * * * * * * *
BuildFont proc
    LOCAL charx:GLfloat, chary:GLfloat, temp:UINT, counter:UINT

    invoke glGenLists, 256		; Create 256 display lists
    mov base, eax

    invoke glBindTexture, GL_TEXTURE_2D, [texture]	; Select the font texture

    mov counter, 0

    .WHILE (counter < 256)
        mov eax, counter	; charx = (temp2 mod 16)/16		chary = 1 - int(temp2/16)/16
        xor edx, edx
        mov ebx, 16
        div ebx

        mov temp, eax
        fild temp
        fdiv r4_16_0
        fsubr r4_1_0
        fstp chary

        mov temp, edx
        fild temp
        fdiv r4_16_0
        fstp charx

        mov eax, [counter]
        add eax, base
        invoke glNewList, eax, GL_COMPILE		; Start building a list

            invoke glBegin, GL_QUADS	; Use a quad for each character

                fld chary	; Texture coord, bottom left
                fsub r4_0_0625
                fstp chary
                invoke glTexCoord2f, charx, chary
                invoke glVertex2i, 0, 0

                fld charx	; Texture coord, bottom right
                fadd r4_0_0625
                fstp charx
                invoke glTexCoord2f, charx, chary
                invoke glVertex2i, 16, 0

                fld chary	; Texture coord, top right
                fadd r4_0_0625
                fstp chary
                invoke glTexCoord2f, charx, chary
                invoke glVertex2i, 16, 16

                fld charx	; Texture coord, top left
                fsub r4_0_0625
                fstp charx
                invoke glTexCoord2f, charx, chary
                invoke glVertex2i, 0, 16

            invoke glEnd	; Done building the quad

            invoke glTranslated, (DWORD PTR [gld_10_0]),(DWORD PTR [gld_10_0+4]), \
                (DWORD PTR [r4_0_0]),(DWORD PTR [r4_0_0]),  (DWORD PTR [r4_0_0]),(DWORD PTR [r4_0_0])

        invoke glEndList	; Done building the display list

        inc counter
    .ENDW
    ret
BuildFont endp

; PROC KillFont * * * * * * * * * * * * * * * * * * * * * * * * * * *
KillFont proc
    invoke glDeleteLists, base, 256
    ret
KillFont endp

; PROC glPrint * * * * * * * * * * * * * * * * * * * * * * * * * * * *
glPrint proc x:GLint, y:GLint, string:DWORD, set:UINT
    LOCAL gld_x:GLdouble, gld_y:GLdouble

    fild x		; Convert the GLuint's to GLdoubles for use with glTranslated
    fstp gld_x
    fild y		; Convert the GLuint's to GLdoubles for use with glTranslated
    fstp gld_y

    .IF (set > 1)
        mov set, 1
    .ENDIF

    invoke glBindTexture, GL_TEXTURE_2D, texture			; Select the font texture
    invoke glDisable, GL_DEPTH_TEST							; Disable depth testing
    invoke glMatrixMode, GL_PROJECTION						; Select projection matrix
    invoke glPushMatrix										; Store the projection matrix
    invoke glLoadIdentity									; Reset projection matrix
    _glOrtho 0.0f, 640.0f, 0.0f, 480.0f, -1.0f, 1.0f		; Set up an Ortho screen
    invoke glMatrixMode, GL_MODELVIEW						; Select modelview matrix
    invoke glPushMatrix										; Store the modelview matrix
    invoke glLoadIdentity									; Reset the modelview matrix

    invoke glTranslated, (DWORD PTR [gld_x]),(DWORD PTR [gld_x+4]),   (DWORD PTR [gld_y]),(DWORD PTR [gld_y+4]), \
        (DWORD PTR [r4_0_0]),(DWORD PTR [r4_0_0])

    mov eax, 128											; Choose the font set
    mul set													; eax= (128*set)+base-32
    add eax, base
    sub eax, 32
    invoke glListBase, eax

    invoke StrLen, string
    invoke glCallLists, eax, GL_BYTE, string				; Write the text to the screen

    invoke glMatrixMode, GL_PROJECTION						; Select projection matrix
    invoke glPopMatrix										; Restore the old matrix
    invoke glMatrixMode, GL_MODELVIEW						; Select modelview matrix
    invoke glPopMatrix										; Restore the old matrix
    invoke glEnable, GL_DEPTH_TEST							; Enable depth testing

    ret
glPrint endp

; PROC CreateGLWindow * * * * * * * * * * * * * * * * * * * * * * * *
CreateGLWindow proc WinTitle:DWORD, WinWidth:DWORD, WinHeight:DWORD, WinBits:UINT, WinFullscreen:BOOL
    LOCAL dwExStyle:DWORD, dwStyle:DWORD, PixelFormat:GLuint
    LOCAL WindowRect:RECT, dmScreenSettings:DEVMODE, wc:WNDCLASS, pfd:PIXELFORMATDESCRIPTOR

    mov WindowRect.left,0
    mov WindowRect.top,0
    push WinWidth
    pop WindowRect.right
    push WinHeight
    pop WindowRect.bottom

    push WinFullscreen
    pop fullscreen

    invoke GetModuleHandle,NULL
    mov hInstance,eax
    mov wc.style,CS_HREDRAW or CS_VREDRAW or CS_OWNDC
    mov wc.lpfnWndProc, offset WndProc
    mov wc.cbClsExtra,0
    mov wc.cbWndExtra,0
    push hInstance
    pop wc.hInstance
    mov wc.hbrBackground,NULL
    mov wc.lpszMenuName,NULL
    mov wc.lpszClassName,offset txt_OpenGL
    invoke LoadIcon,NULL,IDI_WINLOGO
    mov wc.hIcon,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov wc.hCursor,eax

    ; Register the window class
    invoke RegisterClass,addr wc
    .IF !eax
        invoke MessageBox,NULL,addr txt_WC_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF

    ; Set fullscreen, if appropriate
    .IF fullscreen
        ZeroMemory &dmScreenSettings,sizeof(dmScreenSettings)
        mov dmScreenSettings.dmSize,sizeof dmScreenSettings
        push WinWidth
        pop dmScreenSettings.dmPelsWidth
        push WinHeight
        pop dmScreenSettings.dmPelsHeight
        push WinBits
        pop dmScreenSettings.dmBitsPerPel
        mov dmScreenSettings.dmFields,DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT

        ; Try to set selected mode
        invoke ChangeDisplaySettings,addr dmScreenSettings,CDS_FULLSCREEN
        .IF eax != DISP_CHANGE_SUCCESSFUL
            ; Original code prompts user, and asks to set to window or exit. I'll just set to window mode
            mov fullscreen,FALSE
        .ENDIF
    .ENDIF

    ; Check if we are (still?) in fullscreen
    .IF fullscreen
        mov dwExStyle, WS_EX_APPWINDOW
        mov dwStyle,WS_POPUP
        invoke ShowCursor,FALSE
    .ELSE
        mov dwExStyle,WS_EX_APPWINDOW or WS_EX_WINDOWEDGE
        mov dwStyle, WS_OVERLAPPEDWINDOW
    .ENDIF

    invoke AdjustWindowRectEx,addr WindowRect,dwStyle,FALSE,dwExStyle
    mov eax, WindowRect.left
    sub WindowRect.right,eax
    mov eax, WindowRect.top
    sub WindowRect.bottom, eax

    mov eax,dwStyle
    or eax,WS_CLIPSIBLINGS or WS_CLIPCHILDREN
    invoke CreateWindowEx,dwExStyle,addr txt_OpenGL,addr txt_Window_Title,eax,
        0,0,WindowRect.right,WindowRect.bottom,NULL,NULL,hInstance,NULL
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,txt_WindowCreateError,txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax, FALSE
        ret
    .ENDIF
     mov hWnd,eax

    ZeroMemory &pfd,sizeof(PIXELFORMATDESCRIPTOR)
    mov pfd.nSize,sizeof(PIXELFORMATDESCRIPTOR)
    mov pfd.nVersion,1
    mov pfd.dwFlags,PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER
    mov pfd.iPixelType,PFD_TYPE_RGBA
    mov pfd.cColorBits,16
    mov pfd.cDepthBits,16
    mov pfd.dwLayerMask,PFD_MAIN_PLANE

    ; Try to get a device context
    invoke GetDC,hWnd
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,addr txt_GL_Context_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF
    mov hDC,eax

    ; Select pixel format
    invoke ChoosePixelFormat,hDC,addr pfd
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,addr txt_PixelFormat_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF
    mov PixelFormat,eax

    ; Set pixel format
    invoke SetPixelFormat,hDC,PixelFormat,addr pfd
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,addr txt_SetPixelFormat_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF

    ; Get rendering context
    invoke wglCreateContext,hDC
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,addr txt_RC_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF
    mov hRC,eax

    ; Activate rendering context
    invoke wglMakeCurrent,hDC,hRC
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,addr txt_ActivateRC_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF

    invoke ShowWindow,hWnd,SW_SHOW
    invoke SetForegroundWindow,hWnd
    invoke SetFocus,hWnd
    invoke ReSizeGLScene,WinWidth,WinHeight

    invoke InitGL
    .IF !eax
        invoke KillGLWindow
        invoke MessageBox,NULL,addr txt_GLInit_Failed,addr txt_Error,MB_OK or MB_ICONEXCLAMATION
        mov eax,FALSE
        ret
    .ENDIF

    mov eax,TRUE
    ret
CreateGLWindow endp

; PROC KillGLWindow * * * * * * * * * * * * * * * * * * * * * * * * *
KillGLWindow proc
    .IF fullscreen
        invoke ChangeDisplaySettings,NULL,0
        invoke ShowCursor,TRUE
    .ENDIF

    .IF hRC
        invoke wglMakeCurrent,NULL,NULL
        invoke wglDeleteContext,hRC
        mov hRC,NULL
    .ENDIF

    .IF hDC
        invoke ReleaseDC,hWnd,hDC
        mov hDC,NULL
    .ENDIF

    .IF hWnd
        invoke DestroyWindow,hWnd
        mov hWnd,NULL
    .ENDIF

    invoke UnregisterClass,addr txt_OpenGL,hInstance
    mov hInstance,NULL

    invoke KillFont

    ret
KillGLWindow endp

; PROC WndProc * * * * * * * * * * * * * * * * * * * * * * * * * * * *
WndProc proc hWind:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg == WM_ACTIVATE
        mov eax,wParam
        .IF !ah
            mov active,TRUE
        .ELSE
              mov active,FALSE
        .ENDIF
        xor eax, eax
        ret
    .ELSEIF uMsg==WM_SYSCOMMAND
          .IF wParam == SC_SCREENSAVE or SC_MONITORPOWER
              xor eax, eax
              ret
        .ENDIF
    .ELSEIF uMsg==WM_CLOSE
          invoke PostQuitMessage,0
        xor eax, eax
        ret
      .ELSEIF uMsg==WM_KEYDOWN
        mov eax,wParam
        mov keys[eax],TRUE
        xor eax, eax
        ret
      .ELSEIF uMsg==WM_KEYUP
        mov eax,wParam
        mov keys[eax],FALSE
        xor eax, eax
        ret
      .ELSEIF uMsg==WM_SIZE
        mov eax,lParam
        mov ebx,eax
        and eax,00000000000000001111111111111111b
        shr ebx,16
        invoke ReSizeGLScene,eax,ebx
        xor eax, eax
        ret
      .ENDIF

      invoke DefWindowProc,hWind,uMsg,wParam,lParam
      ret
WndProc endp

; PROC LoadBMP * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; This procedure only works with 24-bit BMP files.
; But it is small and avoids the use of glaux.
; A check should probably be added here to make sure we're dealing with a 24bit BMP
LoadBMP proc FileName:DWORD
    LOCAL FileHandle:DWORD, FileSize:DWORD, BytesRead:DWORD, BMP:DWORD, ImageSize:DWORD

    ; Open the file
    invoke CreateFile, FileName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0
     mov FileHandle, eax

     .IF eax==INVALID_HANDLE_VALUE	; get outta here if we failed to open the file
         xor eax, eax
         ret
     .ENDIF

     ; Determine size of the file
     invoke GetFileSize, FileHandle, 0
     mov FileSize, eax

     ; Allocate space in memory for the file.
     invoke GlobalAlloc, GPTR, FileSize
     mov BMP, eax ; BMP = pointer to allocated space

     ; Read the file into memory
     invoke ReadFile, FileHandle, BMP, FileSize, addr BytesRead, 0
     invoke CloseHandle, FileHandle
     mov eax, BytesRead
     .IF (eax != FileSize)	; Free memory & exit if the readfile failed
         invoke GlobalFree, BMP
         xor eax, eax
         ret
     .ENDIF

    ; BMP file has the colours as BGR instead of RGB. Swap the R & B colours.
    mov ebx, BMP	; Pointer to start of BMPFILE structure
    mov eax, (BMPFILE PTR [ebx]).info.biWidth	; eax = BMPwidth * BMPheight
    mul (BMPFILE PTR [ebx]).info.biHeight
    lea ebx, (BMPFILE PTR [ebx]).data			; ebx = address of BMP data, ie: past the BMP header

    mov ecx, eax		; Loop thru BMP data, swapping R & G colours
@@:	mov al, [ebx]		; al = Red value
    xchg al, [ebx+2]	; Swap Green value with al, and al value with Green
    mov [ebx], al		; Write back Green value
    add ebx, 3			; Move to next block
    loop @B

    ; Return a handle to the BMP
    mov eax, BMP
    ret
LoadBMP endp

; PROC LoadGLTextures * * * * * * * * * * * * * * * * * * * * * * * *
LoadGLTextures proc

    ; Load the first texture, "Font.bmp"
    invoke LoadBMP, addr txt_Font_BMP
    mov BMPTexture, eax
    .IF (eax == NULL)	; Quit if file load failed
        ret
    .ENDIF

    ; Load the second texture, "Bumps.bmp"
    invoke LoadBMP, addr txt_Bumps_BMP
    mov [BMPTexture+4], eax
    .IF (eax == NULL)	; Quit if file load failed
        ret
    .ENDIF

    invoke glGenTextures, 2, addr texture	; Create two textures handles

    ; Build "Font" texture
    invoke glBindTexture, GL_TEXTURE_2D, texture[0]
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR
    mov eax, BMPTexture
    lea ebx, (BMPFILE PTR [eax]).data
    invoke glTexImage2D, GL_TEXTURE_2D, 0, 3, (BMPFILE PTR [eax]).info.biWidth, (BMPFILE PTR [eax]).info.biHeight,\
        0, GL_RGB, GL_UNSIGNED_BYTE, ebx

    ; Build "Bumps" texture
    invoke glBindTexture, GL_TEXTURE_2D, texture[1]
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR
    invoke glTexParameteri, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR
    mov eax, [BMPTexture+4]
    lea ebx, (BMPFILE PTR [eax]).data
    invoke glTexImage2D, GL_TEXTURE_2D, 0, 3, (BMPFILE PTR [eax]).info.biWidth, (BMPFILE PTR [eax]).info.biHeight,\
        0, GL_RGB, GL_UNSIGNED_BYTE, ebx

    ; Remember to free the memory for the BMPFILE allocated in LoadBMP
    ; I assume that if we don't need to free it, then the load failed & I set exit flag accordingly.
    .IF (BMPTexture != NULL)	;
        invoke GlobalFree, BMPTexture
        .IF ([BMPTexture+4] != NULL)
            invoke GlobalFree, [BMPTexture+4]
            mov eax, 1
        .ENDIF
    .ELSE
        xor eax, eax
    .ENDIF

    ret
LoadGLTextures endp

; PROC ReSizeGLScene * * * * * * * * * * * * * * * * * * * * * * * * *
ReSizeGLScene proc widthx:DWORD, heighty:DWORD
    LOCAL ratio:GLdouble

      .IF heighty == 0
        mov heighty, 1 ; prevent div by zero
      .ENDIF
    invoke glViewport, 0, 0, widthx, heighty
      invoke glMatrixMode, GL_PROJECTION
      invoke glLoadIdentity

      fild widthx
      fild heighty
      fdivp st(1),st(0)
      fstp ratio
      _gluPerspective 45.0f, ratio, 0.1f, 100.0f
      invoke glMatrixMode, GL_MODELVIEW
      invoke glLoadIdentity
      ret
ReSizeGLScene endp

; PROC DrawGLScene * * * * * * * * * * * * * * * * * * * * * * * * * *
DrawGLScene proc
    LOCAL red:GLfloat, green:GLfloat, blue:GLfloat	; Used for float calculations with colours
    LOCAL x:UINT, y:UINT	; co-ordinates for printing text

      invoke glClear,GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT
    invoke glLoadIdentity

    invoke glBindTexture, GL_TEXTURE_2D, [texture+1]					; Select second texture
    _glTranslatef 0.0f, 0.0f, -5.0f										; Move into screen 5 units
    _glRotatef 45.0f, 0.0f, 0.0f, 1.0f									; Rotate on Z axis 45 degrees clockwise

    fld r4_30_0
    fmul cnt1
    fstp red															; naughty. red is a bad variable name here.
    _glRotatef red, 1.0f, 1.0f, 0.0f									; Rotate on X&Y axis by cnt1.

    invoke glDisable, GL_BLEND											; Disable blending
    _glColor3f 1.0f, 1.0f, 1.0f											; Bright white
    invoke glBegin, GL_QUADS
    _glTexCoord2f 0.0f, 0.0f
    _glVertex2f -1.0f, 1.0f
    _glTexCoord2f 1.0f, 0.0f
    _glVertex2f 1.0f, 1.0f
    _glTexCoord2f 1.0f, 1.0f
    _glVertex2f 1.0f, -1.0f
    _glTexCoord2f 0.0f, 1.0f
    _glVertex2f -1.0f, -1.0f
    invoke glEnd

    _glRotatef 90.0f, 1.0f, 1.0f, 0.0f
    invoke glBegin, GL_QUADS
    _glTexCoord2f 0.0f, 0.0f
    _glVertex2f -1.0f, 1.0f
    _glTexCoord2f 1.0f, 0.0f
    _glVertex2f 1.0f, 1.0f
    _glTexCoord2f 1.0f, 1.0f
    _glVertex2f 1.0f, -1.0f
    _glTexCoord2f 0.0f, 1.0f
    _glVertex2f -1.0f, -1.0f
    invoke glEnd

    invoke glEnable, GL_BLEND
    invoke glLoadIdentity

    fld cnt1
    fcos
    fstp red
    fld cnt2
    fsin
    fstp green
    fld cnt1
    fadd cnt2
    fcos
    fmul r4_0_5
    fsubr r4_1_0
    fstp blue
    _glColor3f red, green, blue

    fld cnt1
    fcos
    fmul r4_250_0
    fadd r4_280_0
    fistp x

    fld cnt2
    fsin
    fmul r4_200_0
    fadd r4_235_0
    fistp y

    invoke glPrint, x, y, addr txt_NeHe, 0		; Print NeHe on the screen

    fld cnt2
    fsin
    fstp red	; Calc red for OpenGL

    fld cnt1
    fadd cnt2
    fcos
    fmul r4_m0_5
    fsubr r4_1_0
    fstp green	; Calc green for OpenGL

    fld cnt1
    fcos
    fstp blue	; Calc blue for OpenGl
    invoke glColor3f, red, green, blue

    fld cnt2
    fcos
    fmul r4_230_0
    fadd r4_280_0
    fistp x		; X Coord for OpenGL

    fld cnt1
    fsin
    fmul r4_200_0
    fadd r4_235_0
    fistp y		; Y Coord for OpenGL

    invoke glPrint, x, y, addr txt_OpenGL, 1	; Print OpenGL on the screen

    invoke glColor3f, r4_0_0, r4_0_0, r4_1_0	; Set the colour to a dark blue
    fld cnt1
    fadd cnt2
    fdiv r4_5_0
    fcos
    fmul r4_200_0
    fadd r4_240_0
    fistp x
    invoke glPrint, x, 2, addr txt_Giueseppe, 0

    invoke glColor3f, r4_1_0, r4_1_0, r4_1_0	; Set the colour to a dark blue
    add x, 2
    invoke glPrint, x, 2, addr txt_Giueseppe, 0

    fld cnt1	; Increase cnt1
    fadd r4_0_01
    fstp cnt1

    fld cnt2	; Increase cnt2
    fadd r4_0_0081
    fstp cnt2

    mov eax, 1
    ret
DrawGLScene endp

end start


Lesson 18

;-------------------------------------------------------------------------------------------;
; MASM32 conversion of Nehe's OpenGL tutorial 18 by Christophe Jeannin (Chrishka)           ;
; I'm sorry about the poor indentation in MASM32 QEDITOR, I wrote this with RadASM,         ;
; I've tried to fix that, but it still could be a bit messy                                 ;
; float and double parameters for GL functions are directly pushed as immediate.            ;
; I wrote a little program (Conv.exe) to know what value to push.                           ;
; doubles are pushed as 2 dwords, most significant dword first, then less significant one   ;
; but with stdcall arguments Right to Left you have to give invoke less significant dword   ;
; first, and then the most significant dword.                                               ;
; ------------------------------------------------------------------------------------------;

include Lesson18.inc

.data

    hWnd        dd      NULL
    hDc         dd      NULL
    hRc         dd      NULL
    z           REAL4   -5.0f

    Ambient     REAL4   0.5f
                REAL4   0.5f
                REAL4   0.5f
                REAL4   1.0f

    Diffuse     REAL4   1.0f
                REAL4   1.0f
                REAL4   1.0f
                REAL4   1.0f

    Position    REAL4   0.0f
                REAL4   0.0f
                REAL4   2.0f
                REAL4   1.0f

    filter      dd      0

    p1          dd      0
    p2          dd      1

    object      dd      0

    active      db      1

    light       db      0

.data?

    hInst       dd      ?

    xrot        REAL4   ?
    yrot        REAL4   ?
    xspeed      REAL4   ?
    yspeed      REAL4   ?

    part1       dd      ?
    part2       dd      ?

    texture     dd      3 dup(?)

    quadratic   dd      ?

    lp          db      ?
    fp          db      ?
    space       db      ?

    fullscreen  db      ?

    keys        db      256 dup(?)

.const

    szClass     db      "OpenGL",0

    szErr       db      "Error",0
    szTitle     db      "Lesson 18",0

    szErr1      db      "Failed to register the window class",0
    szErr2      db      "Requested fullscreen mode not supported",13,"Use windowed mode instead ?",0
    szErr3      db      "Window creation error",0
    szErr4      db      "Unable to get device context",0
    szErr5      db      "Can't find a suitable pixel format",0
    szErr6      db      "Unable to set the pixel format",0
    szErr7      db      "Unable to create a rendering context",0
    szErr8      db      "Unable to activate the rendering context",0
    szErr9      db      "Unable to initialize OpenGL",0

    szMsg1      db      "Run in fullscreen ?",0

    szTexture   db      "Wall.bmp",0

.code

Start:

    invoke	GetModuleHandle,NULL
    mov		hInst,eax
    invoke	WinMain,eax,NULL,0
    invoke	ExitProcess,eax

glDrawCube PROC

    invoke	glBegin,GL_QUADS
        invoke	glNormal3f,0,0,3F800000h
        invoke	glTexCoord2f,0,0
        invoke	glVertex3f,0BF800000h,0BF800000h,3F800000h
        invoke	glTexCoord2f,3F800000h,0
        invoke	glVertex3f,3F800000h,0BF800000h,3F800000h
        invoke	glTexCoord2f,3F800000h,3F800000h
        invoke	glVertex3f,3F800000h,3F800000h,3F800000h
        invoke	glTexCoord2f,0,3F800000h
        invoke	glVertex3f,0BF800000h,3F800000h,3F800000h

        invoke	glNormal3f,0,0,0BF800000h
        invoke	glTexCoord2f,3F800000h,0
        invoke	glVertex3f,0BF800000h,0BF800000h,0BF800000h
        invoke	glTexCoord2f,3F800000h,3F800000h
        invoke	glVertex3f,0BF800000h,3F800000h,0BF800000h
        invoke	glTexCoord2f,0,3F800000h
        invoke	glVertex3f,3F800000h,3F800000h,0BF800000h
        invoke	glTexCoord2f,0,0
        invoke	glVertex3f,3F800000h,0BF800000h,0BF800000h

        invoke	glNormal3f,0,3F800000h,0
        invoke	glTexCoord2f,0,3F800000h
        invoke	glVertex3f,0BF800000h,3F800000h,0BF800000h
        invoke	glTexCoord2f,0,0
        invoke	glVertex3f,0BF800000h,3F800000h,3F800000h
        invoke	glTexCoord2f,3F800000h,0
        invoke	glVertex3f,3F800000h,3F800000h,3F800000h
        invoke	glTexCoord2f,3F800000h,3F800000h
        invoke	glVertex3f,3F800000h,3F800000h,0BF800000h

        invoke	glNormal3f,0,0BF800000h,0
        invoke	glTexCoord2f,3F800000h,3F800000h
        invoke	glVertex3f,0BF800000h,0BF800000h,0BF800000h
        invoke	glTexCoord2f,0,3F800000h
        invoke	glVertex3f,3F800000h,0BF800000h,0BF800000h
        invoke	glTexCoord2f,0,0
        invoke	glVertex3f,3F800000h,0BF800000h,3F800000h
        invoke	glTexCoord2f,3F800000h,0
        invoke	glVertex3f,0BF800000h,0BF800000h,3F800000h

        invoke	glNormal3f,3F800000h,0,0
        invoke	glTexCoord2f,3F800000h,0
        invoke	glVertex3f,3F800000h,0BF800000h,0BF800000h
        invoke	glTexCoord2f,3F800000h,3F800000h
        invoke	glVertex3f,3F800000h,3F800000h,0BF800000h
        invoke	glTexCoord2f,0,3F800000h
        invoke	glVertex3f,3F800000h,3F800000h,3F800000h
        invoke	glTexCoord2f,0,0
        invoke	glVertex3f,3F800000h,0BF800000h,3F800000h

        invoke	glNormal3f,0BF800000h,0,0
        invoke	glTexCoord2f,0,0
        invoke	glVertex3f,0BF800000h,0BF800000h,0BF800000h
        invoke	glTexCoord2f,3F800000h,0
        invoke	glVertex3f,0BF800000h,0BF800000h,3F800000h
        invoke	glTexCoord2f,3F800000h,3F800000h
        invoke	glVertex3f,0BF800000h,3F800000h,3F800000h
        invoke	glTexCoord2f,0,3F800000h
        invoke	glVertex3f,0BF800000h,3F800000h,0BF800000h
    invoke	glEnd
    ret

glDrawCube ENDP

LoadGLTexture PROC
  LOCAL info:BITMAP,hBmp:DWORD

    invoke	LoadImage,NULL,addr szTexture,IMAGE_BITMAP,0,0,LR_LOADFROMFILE or LR_CREATEDIBSECTION
    .if eax
        mov		hBmp,eax
        invoke	glGenTextures,3,addr texture
        invoke	GetObject,hBmp,sizeof info,addr info

        invoke	glBindTexture,GL_TEXTURE_2D,texture[0*4]
        invoke	glTexParameteri,GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST
              invoke	glTexParameteri,GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST
        invoke	glTexImage2D,GL_TEXTURE_2D,0,3,info.bmWidth,info.bmHeight,0,GL_BGR_EXT,GL_UNSIGNED_BYTE,info.bmBits

        invoke	glBindTexture,GL_TEXTURE_2D,texture[1*4]
        invoke	glTexParameteri,GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR
            invoke	glTexParameteri,GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR
            invoke	glTexImage2D,GL_TEXTURE_2D,0,3,info.bmWidth,info.bmHeight,0,GL_BGR_EXT,GL_UNSIGNED_BYTE,info.bmBits

            invoke	glBindTexture,GL_TEXTURE_2D,texture[2*4]
            invoke	glTexParameteri,GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST
            invoke	glTexParameteri,GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR
            invoke	gluBuild2DMipmaps,GL_TEXTURE_2D,3,info.bmWidth,info.bmHeight,GL_BGR_EXT,GL_UNSIGNED_BYTE,info.bmBits

            invoke      DeleteObject,hBmp

            mov		eax,1
            ret
    .endif
    ret

LoadGLTexture ENDP

ResizeGLScene PROC w:DWORD,h:DWORD

    .if h == 0
        mov		h,1
    .endif
    invoke  glViewport,0,0,w,h
    invoke  glMatrixMode,GL_PROJECTION
    invoke  glLoadIdentity
    push	40590000h		; 100.0
    push	0			; double
    push	3FB99999h		; 0.1
    push	9999999Ah		; double
    fild	dword ptr w
    fidiv	dword ptr h
    sub	esp,8
    fstp	qword ptr [esp]
    push	40468000h		; 45.0
    push	0			; double
    call	gluPerspective
    invoke  glMatrixMode,GL_MODELVIEW
    invoke  glLoadIdentity
    ret

ResizeGLScene ENDP

InitGL PROC

    invoke	LoadGLTexture
    .if !eax
        ret
    .endif
    invoke	glEnable,GL_TEXTURE_2D
    invoke	glShadeModel,GL_SMOOTH
    invoke	glClearColor,0,0,0,0
    invoke	glClearDepth,0,3FF00000h    ;1.0 double
    invoke	glEnable,GL_DEPTH_TEST
    invoke	glDepthFunc,GL_LEQUAL
    invoke	glHint,GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST
    invoke	glLightfv,GL_LIGHT1,GL_AMBIENT,addr Ambient
    invoke	glLightfv,GL_LIGHT1,GL_DIFFUSE,addr Diffuse
    invoke	glLightfv,GL_LIGHT1,GL_POSITION,addr Position
    invoke	glEnable,GL_LIGHT1
    invoke	gluNewQuadric
    mov		quadratic,eax
    invoke	gluQuadricNormals,eax,GLU_SMOOTH
    invoke	gluQuadricTexture,quadratic,GL_TRUE
    mov		eax,quadratic
    ret

InitGL ENDP

DrawGLScene PROC
  LOCAL d1:REAL8,d2:REAL8,tmp:DWORD

    invoke	glClear,GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT
    invoke	glLoadIdentity
    invoke	glTranslatef,0,0,z
    invoke	glRotatef,xrot,3F800000h,0,0						; 1.0f,0.0f,0.0f
    invoke	glRotatef,yrot,0,3F800000h,0						; 0.0f,1.0f,0.0f
    mov		eax,filter
    invoke	glBindTexture,GL_TEXTURE_2D,texture[eax*4]
    .if object == 0
        invoke	glDrawCube
    .elseif object == 1
        invoke	glTranslatef,0,0,0BFC00000h						      ; 0.0f,0.0f,-1.5f
        invoke	gluCylinder,quadratic,0,3FF00000h,0,3FF00000h,0,40080000h,32,32	; 1.0,1.0,3.0
    .elseif object == 2
        invoke	gluDisk,quadratic,0,3FE00000h,0,3FF80000h,32,32
    .elseif object == 3
        invoke	gluSphere,quadratic,0CCCCCCCDh,3FF4CCCCh,32,32
    .elseif object == 4
        invoke	glTranslatef,0,0,0BFC00000h
        invoke	gluCylinder,quadratic,0,3FF00000h,0,0,0,40080000h,32,32
    .elseif object == 5
        mov		eax,p1
        mov		edx,p2
        add		part1,eax
        add		part2,edx
        .if part1 > 359
            mov		p1,0
            mov		part1,0
            mov		p2,1
            mov		part2,0
        .endif
        .if part2 > 359
            mov		p1,1
            mov		p2,0
        .endif
        fild	      part1
        fstp	      d1
        mov		eax,part2
        sub		eax,part1
        mov		tmp,eax
        fild	      tmp
        fstp	      d2
        invoke	gluPartialDisk,quadratic,0,3FE00000h,0,3FF80000h,32,32,dword ptr d1,dword ptr d1+4,dword ptr d2,dword ptr d2+4
    .endif
    fld	xrot
    fadd	xspeed
    fstp	xrot
    fld	yrot
    fadd	yspeed
    fstp	yrot
    mov		eax,1
    ret

DrawGLScene ENDP

KillGLWindow PROC

    invoke	gluDeleteQuadric,quadratic
    .if fullscreen
        invoke	ChangeDisplaySettings,NULL,0
        invoke	ShowCursor,TRUE
    .endif
    .if hRc
        invoke	wglMakeCurrent,NULL,NULL
        invoke	wglDeleteContext,hRc
        mov		hRc,NULL
    .endif
    .if hDc
        invoke	ReleaseDC,hWnd,hDc
        mov		hDc,NULL
    .endif
    .if hWnd
        invoke	DestroyWindow,hWnd
        mov		hWnd,NULL
    .endif
    invoke	UnregisterClass,addr szClass,hInst
    ret

KillGLWindow ENDP

CreateGLWindow PROC titre:DWORD,w:DWORD,h:DWORD,bpp:DWORD
  LOCAL wc:WNDCLASS,style:DWORD
  LOCAL exstyle:DWORD,wrect:RECT,dm:DEVMODE
  LOCAL pfd:PIXELFORMATDESCRIPTOR

    mov		wrect.left,0
    mov		eax,w
    mov		wrect.right,eax
    mov		wrect.top,0
    mov		eax,h
    mov		wrect.bottom,eax
    mov		wc.style,CS_HREDRAW or CS_VREDRAW or CS_OWNDC
    mov		wc.lpfnWndProc,WndProc
    mov		wc.cbClsExtra,0
    mov		wc.cbWndExtra,0
    mov		eax,hInst
    mov		wc.hInstance,eax
    invoke	LoadIcon,NULL,IDI_WINLOGO
    mov		wc.hIcon,eax
    invoke	LoadCursor,NULL,IDC_ARROW
    mov		wc.hCursor,eax
    mov		wc.hbrBackground,0
    mov		wc.lpszMenuName,NULL
    mov		wc.lpszClassName,offset szClass
    invoke	RegisterClass,addr wc
    .if !eax
        invoke	MessageBox,NULL,addr szErr1,addr szErr,MB_ICONERROR or MB_OK
        xor		eax,eax
        ret
    .endif
    .if fullscreen
        ZeroMem	dm,sizeof dm
        mov		dm.dmSize,sizeof dm
        mov		eax,w
        mov		dm.dmPelsWidth,eax
        mov		eax,h
        mov		dm.dmPelsHeight,eax
        mov		eax,bpp
        mov		dm.dmBitsPerPel,eax
        mov		dm.dmFields,DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT
        invoke	ChangeDisplaySettings,addr dm,CDS_FULLSCREEN
        .if eax != DISP_CHANGE_SUCCESSFUL
            invoke	MessageBox,NULL,addr szErr2,addr szErr,MB_YESNO or MB_ICONQUESTION
            .if eax == IDYES
                mov		fullscreen,0
            .else
                xor		eax,eax
                ret
            .endif
        .endif
    .endif
    .if fullscreen
        mov		exstyle,WS_EX_APPWINDOW
        mov		style,WS_POPUP
        invoke	ShowCursor,FALSE
    .else
        mov		exstyle,WS_EX_APPWINDOW or WS_EX_WINDOWEDGE
        mov		style,WS_OVERLAPPEDWINDOW
    .endif
    invoke	AdjustWindowRectEx,addr wrect,style,FALSE,exstyle
    mov		eax,wrect.right
    mov		ebx,wrect.bottom
    mov		ecx,style
    sub		eax,wrect.left
    sub		ebx,wrect.top
    or		ecx,WS_CLIPSIBLINGS or WS_CLIPCHILDREN
    invoke	CreateWindowEx,exstyle,addr szClass,titre,ecx,0,0,eax,ebx,NULL,NULL,hInst,NULL
    .if !eax
        invoke	KillGLWindow
        invoke	MessageBox,NULL,addr szErr3,addr szErr,MB_ICONERROR or MB_OK
        xor		eax,eax
        ret
    .endif
    mov		hWnd,eax
    ZeroMem	pfd,sizeof pfd
    mov		pfd.nSize,sizeof pfd
    mov		pfd.nVersion,1
    mov		pfd.dwFlags,PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER
    mov		pfd.iPixelType,PFD_TYPE_RGBA
    mov		eax,bpp
    mov		pfd.cColorBits,al
    mov		pfd.cDepthBits,16
    mov		pfd.iLayerType,PFD_MAIN_PLANE
    invoke	GetDC,hWnd
    .if	!eax
        invoke	KillGLWindow
        invoke	MessageBox,NULL,addr szErr4,addr szErr,MB_OK or MB_ICONERROR
        xor		eax,eax
        ret
    .endif
    mov		hDc,eax
    invoke	ChoosePixelFormat,hDc,addr pfd
    .if	!eax
        invoke	KillGLWindow
        invoke	MessageBox,NULL,addr szErr5,addr szErr,MB_OK or MB_ICONERROR
        xor		eax,eax
        ret
    .endif
    lea		ebx,pfd
    push	ebx
    push	eax
    push	hDc
    call	SetPixelFormat
    .if !eax
        invoke	KillGLWindow
        invoke	MessageBox,NULL,addr szErr6,addr szErr,MB_OK or MB_ICONERROR
        xor		eax,eax
        ret
    .endif
    invoke	wglCreateContext,hDc
    .if !eax
        invoke	KillGLWindow
        invoke	MessageBox,NULL,addr szErr7,addr szErr,MB_OK or MB_ICONERROR
        xor		eax,eax
        ret
    .endif
    mov		hRc,eax
    invoke	wglMakeCurrent,hDc,eax
    .if !eax
        invoke	KillGLWindow
        invoke	MessageBox,NULL,addr szErr8,addr szErr,MB_OK or MB_ICONERROR
        xor		eax,eax
        ret
    .endif
    invoke	ShowWindow,hWnd,SW_SHOW
    invoke	SetForegroundWindow,hWnd
    invoke	SetFocus,hWnd
    invoke	ResizeGLScene,w,h
    invoke	InitGL
    .if !eax
        invoke	KillGLWindow
        invoke	MessageBox,NULL,addr szErr9,addr szErr,MB_OK or MB_ICONERROR
        xor		eax,eax
        ret
    .endif
    mov		eax,1
    ret

CreateGLWindow ENDP

WndProc	PROC hW:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD

    .if uMsg == WM_ACTIVATE
        mov		eax,wParam
        shr		eax,16
        .if !ax
            mov		active,1
        .else
            mov		active,0
        .endif
        xor		eax,eax
        ret
    .elseif uMsg == WM_SYSCOMMAND
        .if (wParam == SC_SCREENSAVE) || (wParam == SC_MONITORPOWER)
            xor		eax,eax
            ret
        .endif
    .elseif uMsg == WM_CLOSE
        invoke	PostQuitMessage,0
        xor		eax,eax
        ret
    .elseif uMsg == WM_KEYDOWN
        mov		eax,wParam
        mov		keys[eax],1
        xor		eax,eax
        ret
    .elseif uMsg == WM_KEYUP
        mov		eax,wParam
        mov		keys[eax],0
        xor		eax,eax
        ret
    .elseif uMsg == WM_SIZE
        mov		eax,lParam
        movzx	ebx,ax
        shr		eax,16
        invoke	ResizeGLScene,ebx,eax
        xor		eax,eax
        ret
    .endif
    invoke	DefWindowProc,hW,uMsg,wParam,lParam
    ret

WndProc ENDP

WinMain PROC hInstance:DWORD,lpCmdLine:DWORD,nCmdShow:DWORD
  LOCAL msg:MSG,done:DWORD

    mov		done,0
    invoke	MessageBox,NULL,addr szMsg1,addr szTitle,MB_YESNO or MB_ICONQUESTION
    .if eax == IDYES
        mov		fullscreen,1
    .else
        mov		fullscreen,0
    .endif
    invoke		CreateGLWindow,addr szTitle,640,480,16
    .if !eax
        xor	eax,eax
        ret
    .endif
    .while !done
        invoke	PeekMessage,addr msg,NULL,0,0,PM_REMOVE
        .if eax
            .if	msg.message == WM_QUIT
                mov		done,1
            .else
                invoke	TranslateMessage,addr msg
                invoke	DispatchMessage,addr msg
            .endif
        .else
            .if active
                .if keys[VK_ESCAPE]
                    mov		done,1
                .else
                    invoke	DrawGLScene
                    invoke	SwapBuffers,hDc
                    .if keys[VK_L] && !lp
                        mov		lp,1
                        xor		light,1
                        .if !light
                            invoke	glDisable,GL_LIGHTING
                        .else
                            invoke	glEnable,GL_LIGHTING
                        .endif
                    .endif
                    .if !keys[VK_L]
                        mov		lp,0
                    .endif
                    .if keys[VK_F] && !fp
                        mov		fp,1
                        inc		filter
                        .if filter > 2
                            mov		filter,0
                        .endif
                    .endif
                    .if !keys[VK_F]
                        mov		fp,0
                    .endif
                    .if keys[VK_SPACE] && !space
                        mov		space,1
                        inc		object
                        .if object > 5
                            mov		object,0
                        .endif
                    .endif
                    .if !keys[VK_SPACE]
                        mov		space,0
                    .endif
                    .if keys[VK_PRIOR]
                        fld	z
                        fsub	fpc(0.02f)
                        fstp	z
                    .endif
                    .if keys[VK_NEXT]
                        fld	z
                        fadd	fpc(0.02f)
                        fstp	z
                    .endif
                    .if keys[VK_UP]
                        fld	xspeed
                        fsub	fpc(0.01f)
                        fstp	xspeed
                    .endif
                    .if keys[VK_DOWN]
                        fld	xspeed
                        fadd	fpc(0.01f)
                        fstp	xspeed
                    .endif
                    .if keys[VK_LEFT]
                        fld	yspeed
                        fsub	fpc(0.01f)
                        fstp	yspeed
                    .endif
                    .if keys[VK_RIGHT]
                        fld	yspeed
                        fadd	fpc(0.01f)
                        fstp	yspeed
                    .endif
                    .if keys[VK_F1]
                        mov		keys[VK_F1],0
                        invoke	KillGLWindow
                        xor		fullscreen,1
                        invoke	CreateGLWindow,addr szTitle,640,480,16
                        .if !eax
                            xor	eax,eax
                            ret
                        .endif
                    .endif
                .endif
            .endif
        .endif
    .endw
    invoke	KillGLWindow
    mov		eax,msg.wParam
    ret

WinMain ENDP

END Start


Lesson 19

;-------------------------------------------------------------------------------------------;
; MASM32 conversion of Nehe's OpenGL tutorial 19 by Christophe Jeannin (Chrishka)           ;
;                                                                                           ;
; float and double parameters for GL functions are directly pushed as immediate.            ;
; I wrote a little program (Conv.exe) to know what value to push.                           ;
; doubles are pushed as 2 dwords, most significant dword first, then less significant one   ;
; but with stdcall arguments Right to Left you have to give invoke less significant dword   ;
; first, and then the most significant dword.                                               ;
;-------------------------------------------------------------------------------------------;

include Lesson19.inc

.data

    hWnd        dd          NULL
    hDc         dd          NULL
    hRc         dd          NULL
    zoom        REAL4       -40.0f

    active      db          1

    rainbow     db          1

    slowdown    REAL4       2.0f

    colors      REAL4       36 dup( 1.0f,0.5f,0.5f, 1.0f,0.75f,0.5f, 1.0f,1.0f,0.5f, 0.75f,1.0f,0.5f,
                                    0.5f,1.0f,0.5f, 0.5f,1.0f,0.75f, 0.5f,1.0f,1.0f, 0.5f,0.75f,1.0f,
                                    0.5f,0.5f,1.0f, 0.75f,0.5f,1.0f, 1.0f,0.5f,1.0f, 1.0f,0.5f,0.75f )

.data?

    hInst       dd          ?

    xspeed      REAL4       ?
    yspeed      REAL4       ?

    col         dd          ?
    delay       dd          ?

    texture     dd          ?

    nseed       dd          ?

    rp          db          ?
    space       db          ?

    fullscreen  db          ?

    keys        db          256 dup(?)

    particle    particles   MAX_PARTICLES dup(<>)

.const

    szClass     db          "NeHe's OpenGL",0

    szErr       db          "Error",0
    szTitle     db          "Lesson 19",0

    szErr1      db          "Failed to register the window class",0
    szErr2      db          "Requested fullscreen mode not supported",13,"Use windowed mode instead ?",0
    szErr3      db          "Window creation error",0
    szErr4      db          "Unable to get device context",0
    szErr5      db          "Can't find a suitable pixel format",0
    szErr6      db          "Unable to set the pixel format",0
    szErr7      db          "Unable to create a rendering context",0
    szErr8      db          "Unable to activate the rendering context",0
    szErr9      db          "Unable to initialize OpenGL",0

    szMsg1      db          "Would you like to run in fullscreen mode ?",0

    szTexture   db          "particle.bmp",0

.code

Start:
    invoke      GetModuleHandle,NULL
    mov         hInst,eax
    invoke      WinMain,eax,NULL,0
    invoke      ExitProcess,eax

LoadGLTexture PROC
  LOCAL info:BITMAP,hBmp:DWORD

    invoke  LoadImage,NULL,addr szTexture,IMAGE_BITMAP,0,0,LR_LOADFROMFILE or LR_CREATEDIBSECTION
    .if eax
        mov     hBmp,eax
        invoke  glGenTextures,1,addr texture
        invoke  GetObject,hBmp,sizeof info,addr info

        invoke  glBindTexture,GL_TEXTURE_2D,texture
        invoke  glTexParameteri,GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR
        invoke  glTexParameteri,GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR
        invoke  glTexImage2D,GL_TEXTURE_2D,0,3,info.bmWidth,info.bmHeight,0,GL_BGR_EXT,GL_UNSIGNED_BYTE,info.bmBits

        invoke  DeleteObject,hBmp

        mov     eax,1
    .endif
    ret

LoadGLTexture ENDP

ResizeGLScene PROC w:DWORD,h:DWORD

    .if h == 0
        mov     h,1
    .endif
    invoke  glViewport,0,0,w,h
    invoke  glMatrixMode,GL_PROJECTION
    invoke  glLoadIdentity
    push    40690000h               ; 200.0
    push    0                       ; double
    push    3FB99999h               ; 0.1
    push    9999999Ah               ; double
    fild    w
    fidiv   h                       ; dividing w by h and
    sub     esp,8                   ; pushing it on the
    fstp    qword ptr [esp]         ; stack as a double
    push    40468000h               ; 45.0
    push    0                       ; double
    call    gluPerspective
    invoke  glMatrixMode,GL_MODELVIEW
    invoke  glLoadIdentity
    ret

ResizeGLScene ENDP

InitGL PROC
  LOCAL tmp:DWORD,colormul:REAL4

    invoke  LoadGLTexture
    .if !eax
        ret
    .endif

    invoke  glShadeModel,GL_SMOOTH
    invoke  glClearColor,0,0,0,0
    invoke  glClearDepth,0,3FF00000h        ;1.0 double
    invoke  glDisable,GL_DEPTH_TEST
    invoke  glEnable,GL_BLEND
    invoke  glBlendFunc,GL_SRC_ALPHA,GL_ONE
    invoke  glHint,GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST
    invoke  glHint,GL_POINT_SMOOTH_HINT,GL_NICEST
    invoke  glEnable,GL_TEXTURE_2D
    invoke  glBindTexture,GL_TEXTURE_2D,texture

    invoke  GetTickCount
    mov     nseed,eax
    xor     ebx,ebx
    xor     edi,edi

    .while ebx < MAX_PARTICLES
        mov     particle[edi].active,1
        mov     particle[edi].life,3F800000h

        invoke  nrandom,100
        mov     tmp,eax
        fild    tmp
        fdiv    fpc(1000.0f)
        fadd    fpc(0.003f)
        fstp    particle[edi].fade

        fld     fpc(12.0f)                  ; compute the color index
        mov     tmp,MAX_PARTICLES           ; ( loopcount*(12/MAX_PARTICLES) )
        fidiv   tmp                         ; with loopcount = ebx here
        fstp    colormul
        mov     tmp,ebx
        fild    tmp
        fmul    colormul
        fistp   tmp
        mov     ecx,tmp                     ; multiply the color index by 12 to point to the
        mov     eax,tmp                     ; right color, because each color is made of 3
        shl     ecx,3                       ; floats (r,g,b), and floats are 4 bytes so 3*4 = 12.
        shl     eax,2                       ; I'm using left shifts to multiply
        add     eax,ecx
        mov     ecx,colors[eax]
        mov     particle[edi].r,ecx
        mov     ecx,colors[eax+4]
        mov     particle[edi].g,ecx
        mov     ecx,colors[eax+8]
        mov     particle[edi].b,ecx

        invoke  nrandom,50                  ; randomize each color component
        mov     tmp,eax
        fild    tmp
        fsub    fpc(26.0f)
        fmul    fpc(10.0f)
        fstp    particle[edi].xi

        invoke  nrandom,50
        mov     tmp,eax
        fild    tmp
        fsub    fpc(25.0f)
        fmul    fpc(10.0f)
        fstp    particle[edi].yi

        invoke  nrandom,50
        mov     tmp,eax
        fild    tmp
        fsub    fpc(25.0f)
        fmul    fpc(10.0f)
        fstp    particle[edi].zi

        mov     particle[edi].xg,0
        mov     particle[edi].yg,0BF4CCCCDh             ; -0.8f
        mov     particle[edi].zg,0

        add     edi,sizeof particles
        inc     ebx
    .endw

    mov		eax,1
    ret

InitGL ENDP

DrawGLScene PROC
  LOCAL x:REAL4,y:REAL4,z:REAL4
  LOCAL tmp:DWORD

    invoke  glClear,GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT
    invoke  glLoadIdentity

    xor     ebx,ebx
    xor     edi,edi

    .while ebx < MAX_PARTICLES
        .if particle[edi].active
            mov     eax,particle[edi].x
            mov     x,eax
            mov     eax,particle[edi].y
            mov     y,eax
            fld     particle[edi].z
            fadd    zoom
            fstp    z

            invoke  glColor4f,particle[edi].r,particle[edi].g,particle[edi].b,particle[edi].life
            invoke  glBegin,GL_TRIANGLE_STRIP
                invoke  glTexCoord2d,0,3FF00000h,0,3FF00000h
                push    z                                       ; since parameters are not very easy
                sub     esp,4                                   ; to compute, I don't use invoke here
                fld     y                                       ; to have better control over the stack
                fadd    fpc(0.5f)
                fstp    dword ptr [esp]
                sub     esp,4
                fld     x
                fadd    fpc(0.5f)
                fstp    dword ptr [esp]
                call    glVertex3f

                invoke  glTexCoord2d,0,0,0,3FF00000h
                push    z
                sub     esp,4
                fld     y
                fadd    fpc(0.5f)
                fstp    dword ptr [esp]
                sub     esp,4
                fld     x
                fsub    fpc(0.5f)
                fstp    dword ptr [esp]
                call    glVertex3f

                invoke  glTexCoord2d,0,3FF00000h,0,0
                push    z
                sub     esp,4
                fld     y
                fsub    fpc(0.5f)
                fstp    dword ptr [esp]
                sub     esp,4
                fld     x
                fadd    fpc(0.5f)
                fstp    dword ptr [esp]
                call    glVertex3f

                invoke  glTexCoord2d,0,0,0,0
                push    z
                sub     esp,4
                fld     y
                fsub    fpc(0.5f)
                fstp    dword ptr [esp]
                sub     esp,4
                fld     x
                fsub    fpc(0.5f)
                fstp    dword ptr [esp]
                call    glVertex3f
            invoke  glEnd

            fld     slowdown                        ; store slowdown*1000 in tmp so we don't
            fmul    fpc(1000.0f)                    ; have to compute it each time
            fstp    tmp

            fld     particle[edi].xi
            fdiv    tmp
            fadd    particle[edi].x
            fstp    particle[edi].x
            fld     particle[edi].yi
            fdiv    tmp
            fadd    particle[edi].y
            fstp    particle[edi].y
            fld     particle[edi].zi
            fdiv    tmp
            fadd    particle[edi].z
            fstp    particle[edi].z

            fld     particle[edi].xi
            fadd    particle[edi].xg
            fstp    particle[edi].xi
            fld     particle[edi].yi
            fadd    particle[edi].yg
            fstp    particle[edi].yi
            fld     particle[edi].zi
            fadd    particle[edi].zg
            fstp    particle[edi].zi

            fld     particle[edi].life
            fsub    particle[edi].fade
            fstp    particle[edi].life

            fldz
            fld     particle[edi].life
            fcomip  st,st(1)                            ; compare 0 and particle.life, set al
            setc    al                                  ; if life is less than 0
            ffree   st
            fincstp
            .if al                                      ; if particle.life < 0
                mov     particle[edi].life,3F800000h
                invoke  nrandom,100
                mov     tmp,eax
                fild    tmp
                fdiv    fpc(1000.0f)
                fadd    fpc(0.003f)
                fstp    particle[edi].fade
                mov     particle[edi].x,0
                mov     particle[edi].y,0
                mov     particle[edi].z,0
                invoke  nrandom,60
                mov     tmp,eax
                fild    tmp
                fsub    fpc(32.0f)
                fadd    xspeed
                fstp    particle[edi].xi
                invoke  nrandom,60
                mov     tmp,eax
                fild    tmp
                fsub    fpc(30.0f)
                fadd    yspeed
                fstp    particle[edi].yi
                invoke   nrandom,60
                mov     tmp,eax
                fild    tmp
                fsub    fpc(30.0f)
                fstp    particle[edi].zi

                mov     eax,col
                mov     ecx,col
                shl     eax,3
                shl     ecx,2
                add     eax,ecx
                mov     edx,colors[eax]
                mov     particle[edi].r,edx
                mov     edx,colors[eax+4]
                mov     particle[edi].g,edx
                mov     edx,colors[eax+8]
                mov     particle[edi].b,edx
            .endif

            fld     fpc(1.5f)
            fld     particle[edi].yg
            fcomip  st,st(1)
            setc    al
            ffree   st
            fincstp
            .if keys[VK_NUMPAD8] && al
                fld     particle[edi].yg
                fadd    fpc(0.01f)
                fstp    particle[edi].yg
            .endif

            fld		particle[edi].yg
            fld		fpc(-1.5f)
            fcomip	st,st(1)
            setc	al
            ffree	st
            fincstp
            .if keys[VK_NUMPAD2] && al
                fld     particle[edi].yg
                fsub    fpc(0.01f)
                fstp    particle[edi].yg
            .endif

            fld     fpc(1.5f)
            fld     particle[edi].xg
            fcomip  st,st(1)
            setc    al
            ffree   st
            fincstp
            .if keys[VK_NUMPAD6] && al
                fld		particle[edi].xg
                fadd	fpc(0.01f)
                fstp	particle[edi].xg
            .endif

            fld		particle[edi].xg
            fld		fpc(-1.5f)
            fcomip	st,st(1)
            setc	al
            ffree	st
            fincstp
            .if keys[VK_NUMPAD4] && al
                fld     particle[edi].xg
                fsub    fpc(0.01f)
                fstp    particle[edi].xg
            .endif

            .if keys[VK_TAB]
                mov     particle[edi].x,0
                mov     particle[edi].y,0
                mov     particle[edi].z,0
                invoke  nrandom,50
                mov     tmp,eax
                fild    tmp
                fsub    fpc(26.0f)
                fmul    fpc(10.0f)
                fstp    particle[edi].xi
                invoke  nrandom,50
                mov     tmp,eax
                fild    tmp
                fsub    fpc(25.0f)
                fmul    fpc(10.0f)
                fstp    particle[edi].yi
                invoke  nrandom,50
                mov     tmp,eax
                fild    tmp
                fsub    fpc(25.0f)
                fmul    fpc(10.0f)
                fstp    particle[edi].zi
            .endif

        .endif

        add     edi,sizeof particles
        inc     ebx
    .endw

    mov     eax,1
    ret

DrawGLScene ENDP

KillGLWindow PROC

    .if fullscreen
        invoke  ChangeDisplaySettings,NULL,0
        invoke  ShowCursor,TRUE
    .endif
    .if hRc
        invoke  wglMakeCurrent,NULL,NULL
        invoke  wglDeleteContext,hRc
        mov     hRc,NULL
    .endif
    .if hDc
        invoke  ReleaseDC,hWnd,hDc
        mov     hDc,NULL
    .endif
    .if hWnd
        invoke  DestroyWindow,hWnd
        mov     hWnd,NULL
    .endif
    invoke      UnregisterClass,addr szClass,hInst
    ret

KillGLWindow ENDP

CreateGLWindow PROC titre:DWORD,w:DWORD,h:DWORD,bpp:DWORD
  LOCAL wc:WNDCLASS,style:DWORD
  LOCAL exstyle:DWORD,wrect:RECT,dm:DEVMODE
  LOCAL pfd:PIXELFORMATDESCRIPTOR

    mov     wrect.left,0
    mov     eax,w
    mov     wrect.right,eax
    mov     wrect.top,0
    mov     eax,h
    mov     wrect.bottom,eax
    mov     wc.style,CS_HREDRAW or CS_VREDRAW or CS_OWNDC
    mov     wc.lpfnWndProc,WndProc
    mov     wc.cbClsExtra,0
    mov     wc.cbWndExtra,0
    mov     eax,hInst
    mov     wc.hInstance,eax
    invoke  LoadIcon,NULL,IDI_WINLOGO
    mov     wc.hIcon,eax
    invoke  LoadCursor,NULL,IDC_ARROW
    mov     wc.hCursor,eax
    mov     wc.hbrBackground,0
    mov     wc.lpszMenuName,NULL
    mov     wc.lpszClassName,offset szClass
    invoke  RegisterClass,addr wc
    .if !eax
        invoke  MessageBox,NULL,addr szErr1,addr szErr,MB_ICONERROR or MB_OK
        xor     eax,eax
        ret
    .endif
    .if fullscreen
        ZeroMem dm,sizeof dm
        mov     dm.dmSize,sizeof dm
        mov     eax,w
        mov     dm.dmPelsWidth,eax
        mov     eax,h
        mov     dm.dmPelsHeight,eax
        mov     eax,bpp
        mov     dm.dmBitsPerPel,eax
        mov     dm.dmFields,DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT
        invoke  ChangeDisplaySettings,addr dm,CDS_FULLSCREEN
        .if eax != DISP_CHANGE_SUCCESSFUL
            invoke  MessageBox,NULL,addr szErr2,addr szErr,MB_YESNO or MB_ICONQUESTION
            .if eax == IDYES
                mov     fullscreen,0
            .else
                xor     eax,eax
                ret
            .endif
        .endif
    .endif
    .if fullscreen
        mov     exstyle,WS_EX_APPWINDOW
        mov     style,WS_POPUP
        invoke  ShowCursor,FALSE
    .else
        mov     exstyle,WS_EX_APPWINDOW or WS_EX_WINDOWEDGE
        mov style,WS_OVERLAPPEDWINDOW
    .endif
    invoke  AdjustWindowRectEx,addr wrect,style,FALSE,exstyle
    mov     eax,wrect.right
    mov     ebx,wrect.bottom
    mov     ecx,style
    sub     eax,wrect.left
    sub     ebx,wrect.top
    or      ecx,WS_CLIPSIBLINGS or WS_CLIPCHILDREN
    invoke  CreateWindowEx,exstyle,addr szClass,titre,ecx,0,0,eax,ebx,NULL,NULL,hInst,NULL
    .if !eax
        invoke  KillGLWindow
        invoke  MessageBox,NULL,addr szErr3,addr szErr,MB_ICONERROR or MB_OK
        xor     eax,eax
        ret
    .endif
    mov     hWnd,eax
    ZeroMem pfd,sizeof pfd
    mov     pfd.nSize,sizeof pfd
    mov     pfd.nVersion,1
    mov     pfd.dwFlags,PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER
    mov     pfd.iPixelType,PFD_TYPE_RGBA
    mov     eax,bpp
    mov     pfd.cColorBits,al
    mov     pfd.cDepthBits,16
    mov     pfd.iLayerType,PFD_MAIN_PLANE
    invoke  GetDC,hWnd
    .if !eax
        invoke  KillGLWindow
        invoke  MessageBox,NULL,addr szErr4,addr szErr,MB_OK or MB_ICONERROR
        xor     eax,eax
        ret
    .endif
     mov     hDc,eax
     invoke  ChoosePixelFormat,hDc,addr pfd
     .if !eax
        invoke  KillGLWindow
        invoke  MessageBox,NULL,addr szErr5,addr szErr,MB_OK or MB_ICONERROR
        xor     eax,eax
        ret
    .endif
    mov     ebx,eax
    invoke  SetPixelFormat,hDc,ebx,addr pfd
    .if !eax
        invoke  KillGLWindow
        invoke  MessageBox,NULL,addr szErr6,addr szErr,MB_OK or MB_ICONERROR
        xor     eax,eax
        ret
    .endif
    invoke  wglCreateContext,hDc
    .if !eax
        invoke  KillGLWindow
        invoke  MessageBox,NULL,addr szErr7,addr szErr,MB_OK or MB_ICONERROR
        xor     eax,eax
        ret
    .endif
    mov     hRc,eax
    invoke  wglMakeCurrent,hDc,eax
    .if !eax
        invoke  KillGLWindow
        invoke  MessageBox,NULL,addr szErr8,addr szErr,MB_OK or MB_ICONERROR
        xor     eax,eax
        ret
    .endif
    invoke  ShowWindow,hWnd,SW_SHOW
    invoke  SetForegroundWindow,hWnd
    invoke  SetFocus,hWnd
    invoke  ResizeGLScene,w,h
    invoke  InitGL
    .if !eax
        invoke  KillGLWindow
        invoke  MessageBox,NULL,addr szErr9,addr szErr,MB_OK or MB_ICONERROR
        xor     eax,eax
        ret
    .endif
    mov     eax,1
    ret

CreateGLWindow ENDP

WndProc PROC hW:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD

    .if uMsg == WM_ACTIVATE
        mov     eax,wParam
        shr     eax,16
        .if !ax
            mov     active,1
        .else
            mov     active,0
        .endif
            xor     eax,eax
            ret
    .elseif uMsg == WM_SYSCOMMAND
        .if (wParam == SC_SCREENSAVE) || (wParam == SC_MONITORPOWER)
            xor     eax,eax
            ret
        .endif
    .elseif uMsg == WM_CLOSE
        invoke  PostQuitMessage,0
        xor     eax,eax
        ret
    .elseif uMsg == WM_KEYDOWN
        mov     eax,wParam
        mov     keys[eax],1
        xor     eax,eax
        ret
    .elseif uMsg == WM_KEYUP
        mov     eax,wParam
        mov     keys[eax],0
        xor     eax,eax
        ret
    .elseif uMsg == WM_SIZE
        mov     eax,lParam
        movzx   ebx,ax
        shr     eax,16
        invoke  ResizeGLScene,ebx,eax
        xor     eax,eax
        ret
    .endif
    invoke  DefWindowProc,hW,uMsg,wParam,lParam
    ret

WndProc ENDP

WinMain PROC hInstance:DWORD,lpCmdLine:DWORD,nCmdShow:DWORD
  LOCAL msg:MSG,done:DWORD

    mov     done,0

    invoke  MessageBox,NULL,addr szMsg1,addr szTitle,MB_YESNO or MB_ICONQUESTION
    .if eax == IDYES
        mov     fullscreen,1
    .else
        mov     fullscreen,0
    .endif

    invoke  CreateGLWindow,addr szTitle,640,480,16
    .if !eax
        xor     eax,eax
        ret
    .endif

    .if fullscreen
        mov     slowdown,3F800000h
    .endif

    .while !done
        invoke  PeekMessage,addr msg,NULL,0,0,PM_REMOVE
        .if eax
            .if msg.message == WM_QUIT
                mov     done,1
            .else
                invoke  TranslateMessage,addr msg
                invoke  DispatchMessage,addr msg
            .endif
        .else
            .if active
                invoke  DrawGLScene
                .if !eax || keys[VK_ESCAPE]
                    mov     done,1
                .else
                    invoke  SwapBuffers,hDc

                    fld     slowdown
                    fld1
                    fcomip  st,st(1)
                    setc    al
                    ffree   st
                    fincstp
                    .if keys[VK_ADD] && al
                        fld     slowdown
                        fsub    fpc(0.01f)
                        fstp    slowdown
                    .endif

                    fld     fpc(4.0f)
                    fld     slowdown
                    fcomip  st,st(1)
                    setc    al
                    ffree   st
                    fincstp
                    .if keys[VK_SUBTRACT] && al
                        fld     slowdown
                        fadd    fpc(0.01f)
                        fstp    slowdown
                    .endif

                    .if keys[VK_PRIOR]
                        fld     zoom
                        fadd    fpc(0.1f)
                        fstp    zoom
                    .endif

                    .if keys[VK_NEXT]
                        fld     zoom
                        fsub    fpc(0.1f)
                        fstp    zoom
                    .endif

                    .if keys[VK_RETURN] && !rp
                        mov     rp,1
                        xor     rainbow,1
                    .endif
                    .if !keys[VK_RETURN]
                        mov     rp,0
                    .endif

                    .if (keys[VK_SPACE] && !space) || (rainbow && (delay > 25))
                        .if keys[VK_SPACE]
                            mov     rainbow,0
                        .endif
                        mov     space,1
                        mov     delay,0
                        inc     col
                        .if col > 11
                            mov     col,0
                        .endif
                    .endif
                    .if !keys[VK_SPACE]
                        mov     space,0
                    .endif

                    fld     fpc(200.0f)
                    fld     yspeed
                    fcomip  st,st(1)
                    setc    al
                    ffree   st
                    fincstp
                    .if keys[VK_UP] && al
                        fld     yspeed
                        fadd    fpc(1.0f)
                        fstp    yspeed
                    .endif

                    fld     yspeed
                    fld     fpc(-200.0f)
                    fcomip  st,st(1)
                    setc    al
                    ffree   st
                    fincstp
                    .if keys[VK_DOWN] && al
                        fld     yspeed
                        fsub    fpc(1.0f)
                        fstp    yspeed
                    .endif

                    fld     fpc(200.0f)
                    fld     xspeed
                    fcomip  st,st(1)
                    setc    al
                    ffree   st
                    fincstp
                    .if keys[VK_RIGHT]
                        fld     xspeed
                        fadd    fpc(1.0f)
                        fstp    xspeed
                    .endif

                    fld     xspeed
                    fld     fpc(-200.0f)
                    fcomip  st,st(1)
                    setc    al
                    ffree   st
                    fincstp
                    .if keys[VK_LEFT]
                        fld     xspeed
                        fsub    fpc(1.0f)
                        fstp    xspeed
                    .endif

                    inc     delay

                    .if keys[VK_F1]
                        mov     keys[VK_F1],0
                        invoke  KillGLWindow
                        xor     fullscreen,1
                        invoke  CreateGLWindow,addr szTitle,640,480,16
                        .if !eax
                            xor     eax,eax
                            ret
                        .endif
                    .endif

                .endif
            .endif
        .endif
    .endw

    invoke  KillGLWindow
    mov     eax,msg.wParam
    ret

WinMain ENDP

nrandom PROC base:DWORD

;  #########################################################################
;
;                     Park Miller random number algorithm.
;
;                      Written by Jaymeson Trudgen (NaN)
;                   Optimized by Rickey Bowers Jr. (bitRAKE)
;
;  #########################################################################

    mov eax,nseed

    xor edx,edx
    mov ecx,127773
    div ecx
    mov ecx,eax
    mov eax,16807
    mul edx
    mov edx,ecx
    mov ecx,eax
    mov eax,2836
    mul edx
    sub ecx,eax
    xor edx,edx
    mov eax,ecx
    mov nseed,ecx
    div base

    mov eax,edx
    ret

nrandom ENDP

END Start


Lesson 20

;-------------------------------------------------------------------------------------------;
; MASM32 conversion of NeHe's OpenGL tutorial 20 by Christophe Jeannin (Chrishka)           ;
;                                                                                           ;
; I converted "Image2.bmp" to 24 bits/pixel cause my way of loading bitmap doesn't handle   ;
; 8 bits/pixel bmp. I couldn't find a way to do it (I didn't try very hard, that's true)    ;
; If someone feels like showing me a way to do it, my address is :                          ;
; chris.j84@free.fr (you can use this address for comments, suggestions, corrections,       ;
; criticism, etc... too, fell free to mail me)                                              ;
;                                                                                           ;
; float and double parameters are directly pushed as immediate, it's better than macro in   ;
; some cases, cause it's avoiding some memory references, but fpu instructions don't accept ;
; immediate, so it's not always possible.                                                   ;
; I wrote a little program (Conv.exe) to have a fast way to find hex representation of      ;
; single and double precision floating point numbers.                                       ;
; doubles (coded on 8 bytes) are pushed as 2 dwords, most significant dword first, then     ;
; less significant one, but with stdcall arguments right to left you have to give invoke    ;
; less significant dword first, and then the most significant dword.                        ;
;-------------------------------------------------------------------------------------------;

include Lesson20.inc

.data

    hWnd        dd          NULL
    hDc         dd          NULL
    hRc         dd          NULL

    active      db          1

    masking     db          1

.data?

    hInst       dd          ?

    roll        REAL4       ?

    texture     dd          5 dup(?)

    mp          db          ?
    space       db          ?

    fullscreen  db          ?

    keys        db          256 dup(?)

    scene       db          ?

.const

    szClass     db          "NeHe's OpenGL",0

    szErr       db          "Error",0
    szTitle     db          "NeHe's masking tutorial",0

    szErr1      db          "Failed to register the window class",0
    szErr2      db          "Requested fullscreen mode not supported",13,"Use windowed mode instead ?",0
    szErr3      db          "Window creation error",0
    szErr4      db          "Unable to get device context",0
    szErr5      db          "Can't find a suitable pixel format",0
    szErr6      db          "Unable to set the pixel format",0
    szErr7      db          "Unable to create a rendering context",0
    szErr8      db          "Unable to activate the rendering context",0
    szErr9      db          "Unable to initialize OpenGL",0

    szMsg1      db          "Would you like to run in fullscreen mode ?",0

    szTexture1  db          "logo.bmp",0
    szTexture2  db          "mask1.bmp",0
    szTexture3  db          "image1.bmp",0
    szTexture4  db          "mask2.bmp",0
    szTexture5  db          "image2.bmp",0

.code

Start:

    invoke      GetModuleHandle,NULL
    mov         hInst,eax
    invoke      WinMain,eax,NULL,0
    invoke      ExitProcess,eax

LoadGLTexture PROC
  LOCAL info:BITMAP,hBmp1:DWORD
  LOCAL hBmp2:DWORD,hBmp3:DWORD
  LOCAL hBmp4:DWORD,hBmp5:DWORD

    invoke  LoadImage,NULL,addr szTexture1,IMAGE_BITMAP,0,0,LR_LOADFROMFILE or LR_CREATEDIBSECTION
    .if !eax
        ret
    .endif
    mov     hBmp1,eax
    invoke  LoadImage,NULL,addr szTexture2,IMAGE_BITMAP,0,0,LR_LOADFROMFILE or LR_CREATEDIBSECTION
    .if !eax
        ret
    .endif
    mov     hBmp2,eax
    invoke  LoadImage,NULL,addr szTexture3,IMAGE_BITMAP,0,0,LR_LOADFROMFILE or LR_CREATEDIBSECTION
    .if !eax
        ret
    .endif
    mov     hBmp3,eax
    invoke  LoadImage,NULL,addr szTexture4,IMAGE_BITMAP,0,0,LR_LOADFROMFILE or LR_CREATEDIBSECTION
    .if !eax
        ret
    .endif
    mov     hBmp4,eax
    invoke  LoadImage,NULL,addr szTexture5,IMAGE_BITMAP,0,0,LR_LOADFROMFILE or LR_CREATEDIBSECTION
    .if !eax
        ret
    .endif
    mov     hBmp5,eax

    invoke  glGenTextures,5,addr texture
    xor     ebx,ebx
    lea     edi,hBmp1

    .while ebx < 20
        invoke  glBindTexture,GL_TEXTURE_2D,texture[ebx]
        invoke  glTexParameteri,GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR
        invoke  glTexParameteri,GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR
        invoke  GetObject,[edi],sizeof info,addr info
        invoke  glTexImage2D,GL_TEXTURE_2D,0,3,info.bmWidth,info.bmHeight,0,GL_BGR_EXT,GL_UNSIGNED_BYTE,info.bmBits
        sub     edi,4
        add     ebx,4
    .endw

    invoke  DeleteObject,hBmp1
    invoke  DeleteObject,hBmp2
    invoke  DeleteObject,hBmp3
    invoke  DeleteObject,hBmp4
    invoke  DeleteObject,hBmp5

    mov     eax,1
    ret

LoadGLTexture ENDP

ResizeGLScene PROC w:DWORD,h:DWORD

    .if h == 0
        mov     h,1
    .endif
    invoke  glViewport,0,0,w,h
    invoke  glMatrixMode,GL_PROJECTION
    invoke  glLoadIdentity
    push    40590000h               ; 100.0
    push    0                       ; double
    push    3FB99999h               ; 0.1
    push    9999999Ah               ; double
    fild    w
    fidiv   h                       ; dividing w by h and
    sub     esp,8                   ; pushing it on the
    fstp    qword ptr [esp]         ; stack as a double
    push    40468000h               ; 45.0
    push    0                       ; double
    call    gluPerspective
    invoke  glMatrixMode,GL_MODELVIEW
    invoke  glLoadIdentity
    ret

ResizeGLScene ENDP

InitGL PROC

    invoke  LoadGLTexture
    .if !eax
        ret
    .endif

    invoke  glClearColor,0,0,0,0
    invoke  glClearDepth,0,3FF00000h        ;1.0 double
    invoke  glEnable,GL_DEPTH_TEST
    invoke  glShadeModel,GL_SMOOTH
    invoke  glEnable,GL_TEXTURE_2D
    mov     eax,1
    ret

InitGL ENDP

DrawGLScene PROC
  LOCAL tmp:REAL4

    invoke  glClear,GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT
    invoke  glLoadIdentity
    invoke  glTranslatef,0,0,0C0000000h

    invoke  glBindTexture,GL_TEXTURE_2D,texture[0*4]
    invoke  glBegin,GL_QUADS
        fldz
        fsub    roll
        fstp    tmp
        invoke  glTexCoord2f,0,tmp
        invoke  glVertex3f,0BF8CCCCDh,0BF8CCCCDh,0
        invoke  glTexCoord2f,40400000h,tmp
        invoke  glVertex3f,3F8CCCCDh,0BF8CCCCDh,0
        fld     fpc(3.0f)
        fsub    roll
        fstp    tmp
        invoke  glTexCoord2f,40400000h,tmp
        invoke  glVertex3f,3F8CCCCDh,3F8CCCCDh,0
        invoke  glTexCoord2f,0,tmp
        invoke  glVertex3f,0BF8CCCCDh,3F8CCCCDh,0
    invoke  glEnd

    invoke  glEnable,GL_BLEND
    invoke  glDisable,GL_DEPTH_TEST

    .if masking
        invoke  glBlendFunc,GL_DST_COLOR,GL_ZERO
    .endif

    .if scene
        invoke  glTranslatef,0,0,0BF800000h
        push    3F800000h
        push    0
        push    0
        fld     roll
        fmul    fpc(360.0f)
        sub     esp,4
        fstp    dword ptr [esp]
        call    glRotatef

        .if masking
            invoke  glBindTexture,GL_TEXTURE_2D,texture[3*4]
            invoke  glBegin,GL_QUADS
                invoke  glTexCoord2f,0,0
                invoke  glVertex3f,0BF8CCCCDh,0BF8CCCCDh,0
                invoke  glTexCoord2f,3F800000h,0
                invoke  glVertex3f,3F8CCCCDh,0BF8CCCCDh,0
                invoke  glTexCoord2f,3F800000h,3F800000h
                invoke  glVertex3f,3F8CCCCDh,3F8CCCCDh,0
                invoke  glTexCoord2f,0,3F800000h
                invoke  glVertex3f,0BF8CCCCDh,3F8CCCCDh,0
            invoke  glEnd
        .endif

        invoke  glBlendFunc,GL_ONE,GL_ONE
        invoke  glBindTexture,GL_TEXTURE_2D,texture[4*4]
        invoke  glBegin,GL_QUADS
            invoke  glTexCoord2f,0,0
            invoke  glVertex3f,0BF8CCCCDh,0BF8CCCCDh,0
            invoke  glTexCoord2f,3F800000h,0
            invoke  glVertex3f,3F8CCCCDh,0BF8CCCCDh,0
            invoke  glTexCoord2f,3F800000h,3F800000h
            invoke  glVertex3f,3F8CCCCDh,3F8CCCCDh,0
            invoke  glTexCoord2f,0,3F800000h
            invoke  glVertex3f,0BF8CCCCDh,3F8CCCCDh,0
        invoke  glEnd
    .else
        .if masking
            invoke  glBindTexture,GL_TEXTURE_2D,texture[1*4]
            invoke  glBegin,GL_QUADS
                invoke  glTexCoord2f,roll,0
                invoke  glVertex3f,0BF8CCCCDh,0BF8CCCCDh,0
                fld     roll
                fadd    fpc(4.0f)
                fstp    tmp
                invoke  glTexCoord2f,tmp,0
                invoke  glVertex3f,3F8CCCCDh,0BF8CCCCDh,0
                invoke  glTexCoord2f,tmp,40800000h
                invoke  glVertex3f,3F8CCCCDh,3F8CCCCDh,0
                invoke  glTexCoord2f,roll,40800000h
                invoke  glVertex3f,0BF8CCCCDh,3F8CCCCDh,0
            invoke  glEnd
        .endif

        invoke  glBlendFunc,GL_ONE,GL_ONE
        invoke  glBindTexture,GL_TEXTURE_2D,texture[2*4]
        invoke  glBegin,GL_QUADS
            invoke  glTexCoord2f,roll,0
            invoke  glVertex3f,0BF8CCCCDh,0BF8CCCCDh,0
            fld     roll
            fadd    fpc(4.0f)
            fstp    tmp
            invoke  glTexCoord2f,tmp,0
            invoke  glVertex3f,3F8CCCCDh,0BF8CCCCDh,0
            invoke  glTexCoord2f,tmp,40800000h
            invoke  glVertex3f,3F8CCCCDh,3F8CCCCDh,0
            invoke  glTexCoord2f,roll,40800000h
            invoke  glVertex3f,0BF8CCCCDh,3F8CCCCDh,0
        invoke  glEnd
    .endif

    invoke  glEnable,GL_DEPTH_TEST
    invoke  glDisable,GL_BLEND

    fld     roll
    fadd    fpc(0.002f)
    fld1
    fcomip  st,st(1)                ; comparing (roll+0.002) with 1
    setc    al
    .if al                          ; if (roll+0.002) > 1
        fsub    fpc(1.0)            ; then substract 1 from it
    .endif
    fstp    roll

    mov     eax,1
    ret

DrawGLScene ENDP

KillGLWindow PROC

    .if fullscreen
        invoke  ChangeDisplaySettings,NULL,0
        invoke  ShowCursor,TRUE
    .endif
    .if hRc
        invoke  wglMakeCurrent,NULL,NULL
        invoke  wglDeleteContext,hRc
        mov     hRc,NULL
    .endif
    .if hDc
        invoke  ReleaseDC,hWnd,hDc
        mov     hDc,NULL
    .endif
    .if hWnd
        invoke  DestroyWindow,hWnd
        mov     hWnd,NULL
    .endif
    invoke      UnregisterClass,addr szClass,hInst
    ret

KillGLWindow ENDP

CreateGLWindow PROC titre:DWORD,w:DWORD,h:DWORD,bpp:DWORD
  LOCAL wc:WNDCLASS,style:DWORD
  LOCAL exstyle:DWORD,wrect:RECT,dm:DEVMODE
  LOCAL pfd:PIXELFORMATDESCRIPTOR

    mov     wrect.left,0
    mov     eax,w
    mov     wrect.right,eax
    mov     wrect.top,0
    mov     eax,h
    mov     wrect.bottom,eax
    mov     wc.style,CS_HREDRAW or CS_VREDRAW or CS_OWNDC
    mov     wc.lpfnWndProc,WndProc
    mov     wc.cbClsExtra,0
    mov     wc.cbWndExtra,0
    mov     eax,hInst
    mov     wc.hInstance,eax
    invoke  LoadIcon,NULL,IDI_WINLOGO
    mov     wc.hIcon,eax
    invoke  LoadCursor,NULL,IDC_ARROW
    mov     wc.hCursor,eax
    mov     wc.hbrBackground,0
    mov     wc.lpszMenuName,NULL
    mov     wc.lpszClassName,offset szClass
    invoke  RegisterClass,addr wc
    .if !eax
        invoke  MessageBox,NULL,addr szErr1,addr szErr,MB_ICONERROR or MB_OK
        xor     eax,eax
        ret
    .endif
    .if fullscreen
        ZeroMem dm,sizeof dm
        mov     dm.dmSize,sizeof dm
        mov     eax,w
        mov     dm.dmPelsWidth,eax
        mov     eax,h
        mov     dm.dmPelsHeight,eax
        mov     eax,bpp
        mov     dm.dmBitsPerPel,eax
        mov     dm.dmFields,DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT
        invoke  ChangeDisplaySettings,addr dm,CDS_FULLSCREEN
        .if eax != DISP_CHANGE_SUCCESSFUL
            invoke  MessageBox,NULL,addr szErr2,addr szErr,MB_YESNO or MB_ICONQUESTION
            .if eax == IDYES
                mov     fullscreen,0
            .else
                xor     eax,eax
                ret
            .endif
        .endif
    .endif
    .if fullscreen
        mov     exstyle,WS_EX_APPWINDOW
        mov     style,WS_POPUP
        invoke  ShowCursor,FALSE
    .else
        mov     exstyle,WS_EX_APPWINDOW or WS_EX_WINDOWEDGE
        mov style,WS_OVERLAPPEDWINDOW
    .endif
    invoke  AdjustWindowRectEx,addr wrect,style,FALSE,exstyle
    mov     eax,wrect.right
    mov     ebx,wrect.bottom
    mov     ecx,style
    sub     eax,wrect.left
    sub     ebx,wrect.top
    or      ecx,WS_CLIPSIBLINGS or WS_CLIPCHILDREN
    invoke  CreateWindowEx,exstyle,addr szClass,titre,ecx,0,0,eax,ebx,NULL,NULL,hInst,NULL
    .if !eax
        invoke  KillGLWindow
        invoke  MessageBox,NULL,addr szErr3,addr szErr,MB_ICONERROR or MB_OK
        xor     eax,eax
        ret
    .endif
    mov     hWnd,eax
    ZeroMem pfd,sizeof pfd
    mov     pfd.nSize,sizeof pfd
    mov     pfd.nVersion,1
    mov     pfd.dwFlags,PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER
    mov     pfd.iPixelType,PFD_TYPE_RGBA
    mov     eax,bpp
    mov     pfd.cColorBits,al
    mov     pfd.cDepthBits,16
    mov     pfd.iLayerType,PFD_MAIN_PLANE
    invoke  GetDC,hWnd
    .if !eax
        invoke  KillGLWindow
        invoke  MessageBox,NULL,addr szErr4,addr szErr,MB_OK or MB_ICONERROR
        xor     eax,eax
        ret
    .endif
     mov     hDc,eax
     invoke  ChoosePixelFormat,hDc,addr pfd
     .if !eax
        invoke  KillGLWindow
        invoke  MessageBox,NULL,addr szErr5,addr szErr,MB_OK or MB_ICONERROR
        xor     eax,eax
        ret
    .endif
    mov     ebx,eax
    invoke  SetPixelFormat,hDc,ebx,addr pfd
    .if !eax
        invoke  KillGLWindow
        invoke  MessageBox,NULL,addr szErr6,addr szErr,MB_OK or MB_ICONERROR
        xor     eax,eax
        ret
    .endif
    invoke  wglCreateContext,hDc
    .if !eax
        invoke  KillGLWindow
        invoke  MessageBox,NULL,addr szErr7,addr szErr,MB_OK or MB_ICONERROR
        xor     eax,eax
        ret
    .endif
    mov     hRc,eax
    invoke  wglMakeCurrent,hDc,eax
    .if !eax
        invoke  KillGLWindow
        invoke  MessageBox,NULL,addr szErr8,addr szErr,MB_OK or MB_ICONERROR
        xor     eax,eax
        ret
    .endif
    invoke  ShowWindow,hWnd,SW_SHOW
    invoke  SetForegroundWindow,hWnd
    invoke  SetFocus,hWnd
    invoke  ResizeGLScene,w,h
    invoke  InitGL
    .if !eax
        invoke  KillGLWindow
        invoke  MessageBox,NULL,addr szErr9,addr szErr,MB_OK or MB_ICONERROR
        xor     eax,eax
        ret
    .endif
    mov     eax,1
    ret

CreateGLWindow ENDP

WndProc PROC hW:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD

    .if uMsg == WM_ACTIVATE
        mov     eax,wParam
        shr     eax,16
        .if !ax
            mov     active,1
        .else
            mov     active,0
        .endif
            xor     eax,eax
            ret
    .elseif uMsg == WM_SYSCOMMAND
        .if (wParam == SC_SCREENSAVE) || (wParam == SC_MONITORPOWER)
            xor     eax,eax
            ret
        .endif
    .elseif uMsg == WM_CLOSE
        invoke  PostQuitMessage,0
        xor     eax,eax
        ret
    .elseif uMsg == WM_KEYDOWN
        mov     eax,wParam
        mov     keys[eax],1
        xor     eax,eax
        ret
    .elseif uMsg == WM_KEYUP
        mov     eax,wParam
        mov     keys[eax],0
        xor     eax,eax
        ret
    .elseif uMsg == WM_SIZE
        mov     eax,lParam
        movzx   ebx,ax
        shr     eax,16
        invoke  ResizeGLScene,ebx,eax
        xor     eax,eax
        ret
    .endif
    invoke  DefWindowProc,hW,uMsg,wParam,lParam
    ret

WndProc ENDP

WinMain PROC hInstance:DWORD,lpCmdLine:DWORD,nCmdShow:DWORD
  LOCAL msg:MSG,done:DWORD

    mov     done,0

    invoke  MessageBox,NULL,addr szMsg1,addr szTitle,MB_YESNO or MB_ICONQUESTION
    .if eax == IDYES
        mov     fullscreen,1
    .else
        mov     fullscreen,0
    .endif

    invoke  CreateGLWindow,addr szTitle,640,480,16
    .if !eax
        xor     eax,eax
        ret
    .endif

    .while !done
        invoke  PeekMessage,addr msg,NULL,0,0,PM_REMOVE
        .if eax
            .if msg.message == WM_QUIT
                mov     done,1
            .else
                invoke  TranslateMessage,addr msg
                invoke  DispatchMessage,addr msg
            .endif
        .else
            invoke  DrawGLScene
            .if (active && !eax) || keys[VK_ESCAPE]
                mov     done,1
            .else
                invoke  SwapBuffers,hDc

                .if keys[VK_SPACE] && !space
                    mov     space,1
                    xor     scene,1
                .endif

                .if !keys[VK_SPACE]
                    mov     space,0
                .endif

                .if keys[VK_M] && !mp
                    mov     mp,1
                    xor     masking,1
                .endif

                .if !keys[VK_M]
                    mov     mp,0
                .endif

                .if keys[VK_F1]
                    mov     keys[VK_F1],0
                    invoke  KillGLWindow
                    xor     fullscreen,1
                    invoke  CreateGLWindow,addr szTitle,640,480,16
                    .if !eax
                        ret
                    .endif
                .endif

            .endif
        .endif
    .endw

    invoke  KillGLWindow
    mov     eax,msg.wParam
    ret

WinMain ENDP

END Start


Lesson 21

;-------------------------------------------------------------------------------------------;
;															  ;
;     MASM32 conversion of NeHe's OpenGL tutorial 21 by Christophe Jeannin (Chrishka)       ;
;                                                                                           ;
;-------------------------------------------------------------------------------------------;

include Lesson21.inc

.data

    hWnd        dd          NULL
    hDc         dd          NULL
    hRc         dd          NULL

    adjust      dd          3
    lives       dd          5
    level       dd          1
    level2      dd          1
    stage       dd          1

    steps       dd          6 dup(1,2,4,5,10,20)

    active      db          1
    anti        db          1

.data?

    hInst       dd          ?

    delay       dd          ?
    texture     dd          2 dup(?)
    base        dd          ?
    nseed       dd          ?

    vsprintf    dd          ?
    hDll        dd          ?

    player      object      <>
    enemy       object      <>
                object      <>
                object      <>
                object      <>
                object      <>
                object      <>
                object      <>
                object      <>
                object      <>
    hourglass   object      <>

    timer       _timer      <>

    ap          db          ?
    filled      db          ?
    gameover    db          ?
    fullscreen  db          ?
    keys        db          256 dup(?)
    vline       db          110 dup(?)  ;[11][10]
    hline       db          110 dup(?)  ;[10][11]

.const

    szClass     db          "NeHe's OpenGL",0

    szErr       db          "Error",0
    szTitle     db          "NeHe's masking tutorial",0

    szErr1      db          "Failed to register the window class",0
    szErr2      db          "Requested fullscreen mode not supported",13,"Use windowed mode instead ?",0
    szErr3      db          "Window creation error",0
    szErr4      db          "Unable to get device context",0
    szErr5      db          "Can't find a suitable pixel format",0
    szErr6      db          "Unable to set the pixel format",0
    szErr7      db          "Unable to create a rendering context",0
    szErr8      db          "Unable to activate the rendering context",0
    szErr9      db          "Unable to initialize OpenGL",0
    szErr10     db          "Unable to load crtdll.dll",0

    szMsg1      db          "Would you like to run in fullscreen mode ?",0
    szMsg2      db          "GRID CRAZY",0
    szMsg3      db          "Level:%2i",0
    szMsg4      db          "Stage:%2i",0
    szMsg5      db          "GAME OVER",0
    szMsg6      db          "PRESS SPACE",0

    szTex1      db          "Font.bmp",0
    szTex2      db          "Image.bmp",0

    szSnd1      db          "Die.wav",0
    szSnd2      db          "Complete.wav",0
    szSnd3      db          "Freeze.wav",0
    szSnd4      db          "Hourglass.wav",0

    crt         db          "crtdll.dll",0
    vspr        db          "vsprintf",0

.code

Start:

    invoke  GetModuleHandle,NULL
    mov     hInst,eax
    invoke  WinMain,eax,NULL,0
    invoke  ExitProcess,eax

TimerInit PROC

    ZeroMem timer,sizeof timer
    invoke  QueryPerformanceFrequency,addr timer.frequency
    .IF !eax
        mov     timer.performance_timer,0
        invoke  timeGetTime
        mov     timer.mm_timer_start,eax
        mov     timer.mm_timer_elapsed,eax
        mov     timer.resolution,3A83126Fh          ; 0.001f
        mov     dword ptr [timer.frequency],1000
        mov     dword ptr [timer.frequency+4],0
    .ELSE
        invoke  QueryPerformanceCounter,addr timer.performance_timer_start
        mov     timer.performance_timer,1
        fild    timer.frequency
        fdivr   fpc(1.0f)
        fstp    timer.resolution
        mov     eax,dword ptr [timer.performance_timer_start]
        mov     ecx,dword ptr [timer.performance_timer_start+4]
        mov     dword ptr [timer.performance_timer_elapsed],eax
        mov     dword ptr [timer.performance_timer_elapsed+4],ecx
    .ENDIF

TimerInit ENDP

TimerGetTime PROC
  LOCAL time:QWORD,tmp:DWORD

    .IF timer.performance_timer
        invoke  QueryPerformanceCounter,addr time
        fild    time
        fild    timer.performance_timer_start
        fsubp   st(1),st
        fmul    timer.resolution
        fmul    fpc(1000.0f)
        fstp    tmp
        mov     eax,tmp
        ret
    .ELSE
        invoke  timeGetTime
        mov     tmp,eax
        fild    tmp
        fild    timer.mm_timer_start
        fsubp   st(1),st
        fmul    timer.resolution
        fmul    fpc(1000.0f)
        fstp    tmp
        mov     eax,tmp
        ret
    .ENDIF

TimerGetTime ENDP

ResetObjects PROC
  LOCAL loop1:DWORD
  LOCAL lmt:DWORD

    mov     player.x,0
    mov     player.y,0
    mov     player.fx,0
    mov     player.fy,0

    mov     eax,level
    mul     stage
    mov     lmt,eax
    mov     loop1,0

    .WHILE loop1 < eax
        mov     eax,loop1
        imul    eax,eax,sizeof enemy
        mov     edi,eax
        invoke  nrandom,6
        add     eax,5
        mov     enemy[edi].x,eax
        invoke  nrandom,11
        mov     enemy[edi].y,eax
        mov     eax,enemy[edi].x
        imul    eax,eax,60
        mov     enemy[edi].fx,eax
        mov     eax,enemy[edi].y
        imul    eax,eax,40
        mov     enemy[edi].fy,eax
        mov     eax,lmt
        inc     loop1
    .ENDW

    ret

ResetObjects ENDP

LoadGLTexture PROC
  LOCAL info:BITMAP
  LOCAL hBmp1:DWORD
  LOCAL hBmp2:DWORD

    invoke  LoadImage,NULL,addr szTex1,IMAGE_BITMAP,0,0,LR_LOADFROMFILE or LR_CREATEDIBSECTION
    .if !eax
        ret
    .endif
    mov     hBmp1,eax
    invoke  LoadImage,NULL,addr szTex2,IMAGE_BITMAP,0,0,LR_LOADFROMFILE or LR_CREATEDIBSECTION
    .if !eax
        ret
    .endif
    mov     hBmp2,eax

    invoke  glGenTextures,2,addr texture
    invoke  glBindTexture,GL_TEXTURE_2D,texture[0*4]
    invoke  GetObject,hBmp1,sizeof info,addr info
    invoke  glTexImage2D,GL_TEXTURE_2D,0,3,info.bmWidth,info.bmHeight,0,GL_BGR_EXT,GL_UNSIGNED_BYTE,info.bmBits
    invoke  glTexParameteri,GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR
    invoke  glTexParameteri,GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR

    invoke  glBindTexture,GL_TEXTURE_2D,texture[1*4]
    invoke  GetObject,hBmp2,sizeof info,addr info
    invoke  glTexImage2D,GL_TEXTURE_2D,0,3,info.bmWidth,info.bmHeight,0,GL_BGR_EXT,GL_UNSIGNED_BYTE,info.bmBits
    invoke  glTexParameteri,GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR
    invoke  glTexParameteri,GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR

    invoke  DeleteObject,hBmp1
    invoke  DeleteObject,hBmp2

    mov     eax,1
    ret

LoadGLTexture ENDP

BuildFont PROC
  LOCAL loop1:DWORD
  LOCAL tmp:DWORD
  LOCAL _cx:REAL4
  LOCAL _cy:REAL4

    invoke  glGenLists,256
    mov     base,eax
    invoke  glBindTexture,GL_TEXTURE_2D,texture[0*4]
    mov     loop1,0

    .WHILE loop1 < 256
        xor     edx,edx
        mov     eax,loop1
        mov     ecx,16
        div     ecx
        mov     tmp,edx
        fild    tmp
        fdiv    fpc(16.0f)
        fstp    _cx
        mov     tmp,eax
        fild    tmp
        fdiv    fpc(16.0f)
        fstp    _cy
        mov     eax,base
        add     eax,loop1
        invoke  glNewList,eax,GL_COMPILE
            invoke  glBegin,GL_QUADS
                fld1
                fsub    _cy
                fsub    fpc(0.0625f)
                fstp    tmp
                invoke  glTexCoord2f,_cx,tmp
                invoke  glVertex2i,0,16
                fld     _cx
                fadd    fpc(0.0625f)
                mov     eax,tmp
                fstp    tmp
                invoke  glTexCoord2f,tmp,eax
                invoke  glVertex2i,16,16
                fld1
                fsub    _cy
                mov     eax,tmp
                fstp    tmp
                invoke  glTexCoord2f,eax,tmp
                invoke  glVertex2i,16,0
                invoke  glTexCoord2f,_cx,tmp
                invoke  glVertex2i,0,0
            invoke  glEnd
            invoke  glTranslatef,fpc(15.0f),0,0
        invoke  glEndList
        inc     loop1
    .ENDW

    ret

BuildFont ENDP

glPrint PROC C x:DWORD,y:DWORD,set:DWORD,fmt:DWORD,params:VARARG
  LOCAL text[256]:BYTE,tx:REAL4,ty:REAL4

    .IF fmt == NULL
        ret
    .ENDIF

    lea     ecx,params
    lea     eax,text
    push    ecx
    push    fmt
    push    eax
    call    vsprintf

    .IF set > 1
        mov     set,1
    .ENDIF

    invoke  glEnable,GL_TEXTURE_2D
    invoke  glLoadIdentity
    fild    x
    fstp    tx
    fild    y
    fstp    ty
    invoke  glTranslatef,tx,ty,0
    mov     eax,set
    imul    eax,eax,128
    mov     edx,base
    sub     edx,32
    add     edx,eax
    invoke  glListBase,edx

    .IF set == 0
        invoke  glScalef,fpc(1.5f),fpc(2.0f),fpc(1.0f)
    .ENDIF

    lea     ecx,text
    invoke  strlen,ecx
    lea     ecx,text
    invoke  glCallLists,eax,GL_UNSIGNED_BYTE,ecx
    invoke  glDisable,GL_TEXTURE_2D

    ret

glPrint ENDP

ResizeGLScene PROC w:DWORD,h:DWORD

    .IF h == 0
        mov     h,1
    .ENDIF
    invoke  glViewport,0,0,w,h
    invoke  glMatrixMode,GL_PROJECTION
    invoke  glLoadIdentity

    push    3FF00000h
    push    0
    push    0BFF00000h
    push    0
    push    0
    push    0
    sub     esp,8
    fild    h
    fstp    qword ptr [esp]
    sub     esp,8
    fild    w
    fstp    qword ptr [esp]
    push    0
    push    0
    call    glOrtho

    invoke  glMatrixMode,GL_MODELVIEW
    invoke  glLoadIdentity

    ret

ResizeGLScene ENDP

InitGL PROC

    invoke  LoadLibrary,addr crt
    .IF eax == NULL
        invoke  MessageBox,NULL,addr szErr10,addr szErr,MB_ICONERROR or MB_OK
        xor     eax,eax
        ret
    .ENDIF
    mov     hDll,eax
    invoke  GetProcAddress,eax,addr vspr
    .IF eax == NULL
        invoke  MessageBox,NULL,addr szErr10,addr szErr,MB_ICONERROR or MB_OK
        xor     eax,eax
        ret
    .ENDIF
    mov     vsprintf,eax

    invoke  LoadGLTexture
    .IF !eax
        ret
    .ENDIF

    invoke  BuildFont

    invoke  glShadeModel,GL_SMOOTH
    invoke  glClearColor,0,0,0,3F000000h
    invoke  glClearDepth,0,3FF00000h        ;1.0 double
    invoke  glHint,GL_LINE_SMOOTH_HINT,GL_NICEST
    invoke  glEnable,GL_BLEND
    invoke  glBlendFunc,GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA

    invoke  GetTickCount
    mov     nseed,eax

    mov     eax,1
    ret

InitGL ENDP

DrawGLScene PROC
  LOCAL loop1:DWORD
  LOCAL loop2:DWORD
  LOCAL tmp:DWORD
  LOCAL tmp2:DWORD

    invoke  glClear,GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT
    invoke  glBindTexture,GL_TEXTURE_2D,texture[0*4]
    invoke  glColor3f,3F800000h,3F000000h,3F800000h
    invoke  glPrint,207,24,0,addr szMsg2
    invoke  glColor3f,3F800000h,3F800000h,0
    invoke  glPrint,20,20,1,addr szMsg3,level2
    invoke  glPrint,20,40,1,addr szMsg4,stage

    .IF gameover
        invoke  nrandom,255
        mov     ecx,eax
        invoke  nrandom,255
        mov     edx,eax
        invoke  nrandom,255
        invoke  glColor3ub,eax,ecx,edx
        invoke  glPrint,472,20,1,addr szMsg5
        invoke  glPrint,472,40,1,addr szMsg6
    .ENDIF

    mov     eax,lives
    sub     eax,1
    .IF eax == 0FFFFFFFFh
        jmp		@f
    .ENDIF
    mov     loop1,0

    .WHILE loop1 < eax
        invoke  glLoadIdentity
        fild    loop1
        fmul    fpc(40.0f)
        fadd    fpc(490.0f)
        fstp    tmp
        invoke  glTranslatef,tmp,42200000h,0
        fldz
        fsub    player.spin
        fstp    tmp
        invoke  glRotatef,tmp,0,0,3F800000h
        invoke  glColor3f,0,3F800000h,0

        invoke  glBegin,GL_LINES
            invoke  glVertex2i,-5,-5
            invoke  glVertex2i,5,5
            invoke  glVertex2i,5,-5
            invoke  glVertex2i,-5,5
        invoke  glEnd

        fldz
        fsub    player.spin
        fmul    fpc(0.5f)
        fstp    tmp
        invoke  glRotatef,tmp,0,0,3F800000h
        invoke  glColor3f,0,3F400000h,0

        invoke  glBegin,GL_LINES
            invoke  glVertex2i,-7,0
            invoke  glVertex2i,7,0
            invoke  glVertex2i,0,-7
            invoke  glVertex2i,0,7
        invoke  glEnd

        mov     eax,lives
        sub     eax,1
        inc     loop1
    .ENDW
@@:
    mov     filled,1
    invoke  glLineWidth,40000000h
    invoke  glDisable,GL_LINE_SMOOTH
    invoke  glLoadIdentity
    mov     loop1,0

    .WHILE loop1 < 11
        mov     loop2,0

        .WHILE loop2 < 11
            invoke  glColor3f,0,3F000000h,3F800000h
            mov     eax,loop1
            imul    eax,eax,11
            mov     ecx,loop2

            .IF hline[eax+ecx]
                invoke  glColor3f,3F800000h,3F800000h,3F800000h
            .ENDIF

            .IF loop1 < 10
                mov     eax,loop1
                imul    eax,eax,11
                mov     ecx,loop2

                .IF !hline[eax+ecx]
                    mov     filled,0
                .ENDIF

                invoke  glBegin,GL_LINES
                    mov     eax,60
                    mul     loop1
                    add     eax,20
                    mov     ecx,eax
                    mov     eax,loop2
                    imul    eax,eax,40
                    add     eax,70
                    mov     tmp,eax
                    invoke  glVertex2i,ecx,eax
                    mov     eax,loop1
                    imul    eax,eax,60
                    add     eax,80
                    invoke  glVertex2i,eax,tmp
                invoke  glEnd
            .ENDIF

            invoke  glColor3f,0,3F000000h,3F800000h
            mov     eax,loop1
            imul    eax,eax,10
            mov     ecx,loop2

            .IF vline[eax+ecx]
                invoke  glColor3f,3F800000h,3F800000h,3F800000h
            .ENDIF

            .IF loop2 < 10
                mov     eax,loop1
                imul    eax,eax,10
                mov     ecx,loop2

                .IF !vline[eax+ecx]
                    mov     filled,0
                .ENDIF

                invoke  glBegin,GL_LINES
                    mov     eax,loop1
                    imul    eax,eax,60
                    add     eax,20
                    mov     tmp,eax
                    mov     eax,loop2
                    imul    eax,eax,40
                    add     eax,70
                    invoke  glVertex2i,tmp,eax
                    mov     eax,loop2
                    imul    eax,eax,40
                    add     eax,110
                    invoke  glVertex2i,tmp,eax
                invoke  glEnd

            .ENDIF

            invoke  glEnable,GL_TEXTURE_2D
            invoke  glColor3f,3F800000h,3F800000h,3F800000h
            invoke  glBindTexture,GL_TEXTURE_2D,texture[1*4]

            .IF (loop1 < 10) && (loop2 < 10)
                mov     eax,loop1
                imul    eax,eax,11
                mov     ecx,eax
                add     ecx,loop2
                mov     eax,loop1
                imul    eax,eax,10
                mov     edx,eax
                add     eax,10
                add     edx,loop2
                add     eax,loop2

                .IF hline[ecx] && hline[ecx+1] && vline[edx] && vline[eax]
                    invoke  glBegin,GL_QUADS
                        fild    loop1
                        fdiv    fpc(10.0f)
                        fadd    fpc(0.1f)
                        fstp    tmp
                        fild    loop2
                        fdiv    fpc(10.0f)
                        fld1
                        fsubrp  st(1),st
                        fstp    tmp2
                        invoke  glTexCoord2f,tmp,tmp2
                        mov     eax,loop1
                        imul    eax,eax,60
                        add     eax,79
                        mov     ecx,eax
                        mov     eax,loop2
                        imul    eax,eax,40
                        add     eax,71
                        invoke  glVertex2i,ecx,eax
                        fild    loop1
                        fdiv    fpc(10.0f)
                        fstp    tmp
                        invoke  glTexCoord2f,tmp,tmp2
                        mov     eax,loop1
                        imul    eax,eax,60
                        add     eax,21
                        mov     ecx,eax
                        mov     eax,loop2
                        imul    eax,eax,40
                        add     eax,71
                        invoke  glVertex2i,ecx,eax
                        fild    loop2
                        fdiv    fpc(10.0f)
                        fstp    tmp2
                        fld1
                        fsub    tmp2
                        fsub    fpc(0.1f)
                        fstp    tmp2
                        invoke  glTexCoord2f,tmp,tmp2
                        mov     eax,loop1
                        imul    eax,eax,60
                        add     eax,21
                        mov     ecx,eax
                        mov     eax,loop2
                        imul    eax,eax,40
                        add     eax,109
                        invoke  glVertex2i,ecx,eax
                        fild    loop1
                        fdiv    fpc(10.0f)
                        fadd    fpc(0.1f)
                        fstp    tmp
                        invoke  glTexCoord2f,tmp,tmp2
                        mov     eax,loop1
                        imul    eax,eax,60
                        add     eax,79
                        mov     ecx,eax
                        mov     eax,loop2
                        imul    eax,eax,40
                        add     eax,109
                        invoke  glVertex2i,ecx,eax
                    invoke  glEnd
                .ENDIF

            .ENDIF

            invoke  glDisable,GL_TEXTURE_2D

            inc     loop2
        .ENDW

        inc     loop1
    .ENDW

    invoke  glLineWidth,3F800000h

    .IF anti
        invoke  glEnable,GL_LINE_SMOOTH
    .ENDIF

    .IF hourglass.fx == 1

        invoke  glLoadIdentity
        fild    hourglass.x
        fmul    fpc(60.0f)
        fadd    fpc(20.0f)
        fstp    tmp
        fild    hourglass.y
        fmul    fpc(40.0f)
        fadd    fpc(70.0f)
        fstp    tmp2
        invoke  glTranslatef,tmp,tmp2,0
        invoke  glRotatef,hourglass.spin,0,0,3F800000h
        invoke  nrandom,255
        mov     ecx,eax
        invoke  nrandom,255
        mov     edx,eax
        invoke  nrandom,255
        invoke  glColor3ub,eax,ecx,edx

        invoke  glBegin,GL_LINES
            invoke  glVertex2i,-5,-5
            invoke  glVertex2i,5,5
            invoke  glVertex2i,5,-5
            invoke  glVertex2i,-5,5
            invoke  glVertex2i,-5,5
            invoke  glVertex2i,5,5
            invoke  glVertex2i,-5,-5
            invoke  glVertex2i,5,-5
        invoke  glEnd

    .ENDIF

    invoke  glLoadIdentity
    fild    player.fx
    fadd    fpc(20.0f)
    fstp    tmp
    fild    player.fy
    fadd    fpc(70.0f)
    fstp    tmp2
    invoke  glTranslatef,tmp,tmp2,0
    invoke  glRotatef,player.spin,0,0,3F800000h
    invoke  glColor3f,0,3F800000h,0

    invoke  glBegin,GL_LINES
        invoke  glVertex2i,-5,-5
        invoke  glVertex2i,5,5
        invoke  glVertex2i,5,-5
        invoke  glVertex2i,-5,5
    invoke  glEnd

    fld     player.spin
    fmul    fpc(0.5f)
    fstp    tmp
    invoke  glRotatef,tmp,0,0,3F800000h
    invoke  glColor3f,0,3F400000h,0

    invoke  glBegin,GL_LINES
        invoke  glVertex2i,-7,0
        invoke  glVertex2i,7,0
        invoke  glVertex2i,0,-7
        invoke  glVertex2i,0,7
    invoke  glEnd

    mov     loop1,0
    mov     eax,stage
    xor     edx,edx
    mul     level

    .WHILE loop1 < eax
        invoke  glLoadIdentity
        mov     eax,loop1
        imul    eax,eax,sizeof enemy
        fild    enemy[eax].fx
        fadd    fpc(20.0f)
        fstp    tmp
        fild    enemy[eax].fy
        fadd    fpc(70.0f)
        fstp    tmp2
        invoke  glTranslatef,tmp,tmp2,0
        invoke  glColor3f,3F800000h,3F000000h,3F000000h

        invoke  glBegin,GL_LINES
            invoke  glVertex2i,0,-7
            invoke  glVertex2i,-7,0
            invoke  glVertex2i,-7,0
            invoke  glVertex2i,0,7
            invoke  glVertex2i,0,7
            invoke  glVertex2i,7,0
            invoke  glVertex2i,7,0
            invoke  glVertex2i,0,-7
        invoke  glEnd

        mov     eax,loop1
        imul    eax,eax,sizeof enemy
        invoke  glRotatef,enemy[eax].spin,0,0,3F800000h
        invoke  glColor3f,3F800000h,0,0

        invoke  glBegin,GL_LINES
            invoke  glVertex2i,-7,-7
            invoke  glVertex2i,7,7
            invoke  glVertex2i,-7,7
            invoke  glVertex2i,7,-7
        invoke  glEnd

        inc     loop1
        mov     eax,stage
        xor     edx,edx
        mul     level
    .ENDW

    mov     eax,1
    ret

DrawGLScene ENDP

KillGLWindow PROC

    .if fullscreen
        invoke  ChangeDisplaySettings,NULL,0
        invoke  ShowCursor,TRUE
    .endif
    .if hRc
        invoke  wglMakeCurrent,NULL,NULL
        invoke  wglDeleteContext,hRc
        mov     hRc,NULL
    .endif
    .if hDc
        invoke  ReleaseDC,hWnd,hDc
        mov     hDc,NULL
    .endif
    .if hWnd
        invoke  DestroyWindow,hWnd
        mov     hWnd,NULL
    .endif
    invoke  UnregisterClass,addr szClass,hInst
    invoke  glDeleteLists,base,256
    .IF hDll != NULL
        invoke  FreeLibrary,hDll
    .ENDIF
    ret

KillGLWindow ENDP

CreateGLWindow PROC titre:DWORD,w:DWORD,h:DWORD,bpp:DWORD
  LOCAL wc:WNDCLASS,style:DWORD
  LOCAL exstyle:DWORD,wrect:RECT,dm:DEVMODE
  LOCAL pfd:PIXELFORMATDESCRIPTOR

    mov     wrect.left,0
    mov     eax,w
    mov     wrect.right,eax
    mov     wrect.top,0
    mov     eax,h
    mov     wrect.bottom,eax
    mov     wc.style,CS_HREDRAW or CS_VREDRAW or CS_OWNDC
    mov     wc.lpfnWndProc,WndProc
    mov     wc.cbClsExtra,0
    mov     wc.cbWndExtra,0
    mov     eax,hInst
    mov     wc.hInstance,eax
    invoke  LoadIcon,NULL,IDI_WINLOGO
    mov     wc.hIcon,eax
    invoke  LoadCursor,NULL,IDC_ARROW
    mov     wc.hCursor,eax
    mov     wc.hbrBackground,0
    mov     wc.lpszMenuName,NULL
    mov     wc.lpszClassName,offset szClass
    invoke  RegisterClass,addr wc
    .if !eax
        invoke  MessageBox,NULL,addr szErr1,addr szErr,MB_ICONERROR or MB_OK
        xor     eax,eax
        ret
    .endif
    .if fullscreen
        ZeroMem dm,sizeof dm
        mov     dm.dmSize,sizeof dm
        mov     eax,w
        mov     dm.dmPelsWidth,eax
        mov     eax,h
        mov     dm.dmPelsHeight,eax
        mov     eax,bpp
        mov     dm.dmBitsPerPel,eax
        mov     dm.dmFields,DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT
        invoke  ChangeDisplaySettings,addr dm,CDS_FULLSCREEN
        .if eax != DISP_CHANGE_SUCCESSFUL
            invoke  MessageBox,NULL,addr szErr2,addr szErr,MB_YESNO or MB_ICONQUESTION
            .if eax == IDYES
                mov     fullscreen,0
            .else
                xor     eax,eax
                ret
            .endif
        .endif
    .endif
    .if fullscreen
        mov     exstyle,WS_EX_APPWINDOW
        mov     style,WS_POPUP
        invoke  ShowCursor,FALSE
    .else
        mov     exstyle,WS_EX_APPWINDOW or WS_EX_WINDOWEDGE
        mov style,WS_OVERLAPPEDWINDOW
    .endif
    invoke  AdjustWindowRectEx,addr wrect,style,FALSE,exstyle
    mov     eax,wrect.right
    mov     edx,wrect.bottom
    mov     ecx,style
    sub     eax,wrect.left
    sub     edx,wrect.top
    or      ecx,WS_CLIPSIBLINGS or WS_CLIPCHILDREN
    invoke  CreateWindowEx,exstyle,addr szClass,titre,ecx,0,0,eax,edx,NULL,NULL,hInst,NULL
    .if !eax
        invoke  KillGLWindow
        invoke  MessageBox,NULL,addr szErr3,addr szErr,MB_ICONERROR or MB_OK
        xor     eax,eax
        ret
    .endif
    mov     hWnd,eax
    ZeroMem pfd,sizeof pfd
    mov     pfd.nSize,sizeof pfd
    mov     pfd.nVersion,1
    mov     pfd.dwFlags,PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER
    mov     pfd.iPixelType,PFD_TYPE_RGBA
    mov     eax,bpp
    mov     pfd.cColorBits,al
    mov     pfd.cDepthBits,16
    mov     pfd.iLayerType,PFD_MAIN_PLANE
    invoke  GetDC,hWnd
    .if !eax
        invoke  KillGLWindow
        invoke  MessageBox,NULL,addr szErr4,addr szErr,MB_OK or MB_ICONERROR
        xor     eax,eax
        ret
    .endif
     mov     hDc,eax
     invoke  ChoosePixelFormat,hDc,addr pfd
     .if !eax
        invoke  KillGLWindow
        invoke  MessageBox,NULL,addr szErr5,addr szErr,MB_OK or MB_ICONERROR
        xor     eax,eax
        ret
    .endif
    mov     ecx,eax
    invoke  SetPixelFormat,hDc,ecx,addr pfd
    .if !eax
        invoke  KillGLWindow
        invoke  MessageBox,NULL,addr szErr6,addr szErr,MB_OK or MB_ICONERROR
        xor     eax,eax
        ret
    .endif
    invoke  wglCreateContext,hDc
    .if !eax
        invoke  KillGLWindow
        invoke  MessageBox,NULL,addr szErr7,addr szErr,MB_OK or MB_ICONERROR
        xor     eax,eax
        ret
    .endif
    mov     hRc,eax
    invoke  wglMakeCurrent,hDc,eax
    .if !eax
        invoke  KillGLWindow
        invoke  MessageBox,NULL,addr szErr8,addr szErr,MB_OK or MB_ICONERROR
        xor     eax,eax
        ret
    .endif
    invoke  ShowWindow,hWnd,SW_SHOW
    invoke  SetForegroundWindow,hWnd
    invoke  SetFocus,hWnd
    invoke  ResizeGLScene,w,h
    invoke  InitGL
    .if !eax
        invoke  KillGLWindow
        invoke  MessageBox,NULL,addr szErr9,addr szErr,MB_OK or MB_ICONERROR
        xor     eax,eax
        ret
    .endif
    mov     eax,1
    ret

CreateGLWindow ENDP

WndProc PROC hW:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD

    .if uMsg == WM_ACTIVATE
        mov     eax,wParam
        shr     eax,16
        .if !ax
            mov     active,1
        .else
            mov     active,0
        .endif
            xor     eax,eax
            ret
    .elseif uMsg == WM_SYSCOMMAND
        .if (wParam == SC_SCREENSAVE) || (wParam == SC_MONITORPOWER)
            xor     eax,eax
            ret
        .endif
    .elseif uMsg == WM_CLOSE
        invoke  PostQuitMessage,0
        xor     eax,eax
        ret
    .elseif uMsg == WM_KEYDOWN
        mov     eax,wParam
        mov     keys[eax],1
        xor     eax,eax
        ret
    .elseif uMsg == WM_KEYUP
        mov     eax,wParam
        mov     keys[eax],0
        xor     eax,eax
        ret
    .elseif uMsg == WM_SIZE
        mov     eax,lParam
        movzx   ecx,ax
        shr     eax,16
        invoke  ResizeGLScene,ecx,eax
        xor     eax,eax
        ret
    .endif
    invoke  DefWindowProc,hW,uMsg,wParam,lParam
    ret

WndProc ENDP

WinMain PROC hInstance:DWORD,lpCmdLine:DWORD,nCmdShow:DWORD
  LOCAL msg:MSG,done:DWORD,start:REAL4,tmp:DWORD
  LOCAL loop1:DWORD,loop2:DWORD

    mov     done,0

    invoke  MessageBox,NULL,addr szMsg1,addr szTitle,MB_YESNO or MB_ICONQUESTION
    .IF eax == IDYES
        mov     fullscreen,1
    .ELSE
        mov     fullscreen,0
    .ENDIF

    invoke  CreateGLWindow,addr szTitle,640,480,16
    .IF !eax
        xor     eax,eax
        ret
    .ENDIF

    invoke  ResetObjects
    invoke  TimerInit

    .WHILE !done
        invoke  PeekMessage,addr msg,NULL,0,0,PM_REMOVE
        .IF eax
            .IF msg.message == WM_QUIT
                mov     done,1
            .ELSE
                invoke  TranslateMessage,addr msg
                invoke  DispatchMessage,addr msg
            .ENDIF
        .ELSE
            invoke  TimerGetTime
            mov     start,eax
            invoke  DrawGLScene

            .IF (active && !eax) || keys[VK_ESCAPE]
                mov     done,1
            .ELSE
                invoke  SwapBuffers,hDc
            .ENDIF

@@:         mov     eax,adjust
            fld     steps[eax*4]
            fmul    fpc(2.0f)
            fadd    start
            fstp    tmp
            invoke  TimerGetTime
            fld     tmp
            mov     tmp,eax
            fld     tmp
            fcomip  st,st(1)
            fstp    tmp
            jc      @b

            .IF keys[VK_F1]
                    mov     keys[VK_F1],0
                    invoke  KillGLWindow
                    xor     fullscreen,1
                    invoke  CreateGLWindow,addr szTitle,640,480,16
                    .IF !eax
                        ret
                    .ENDIF
            .ENDIF

            .IF keys[VK_A] && !ap
                mov     ap,1
                xor     anti,1
            .ENDIF

            .IF !keys[VK_A]
                mov     ap,0
            .ENDIF

            .IF !gameover && active
                mov     loop1,0
                mov     eax,stage
                mul     level
                .WHILE loop1 < eax

                    mov     eax,loop1
                    imul    eax,eax,sizeof enemy
                    mov     esi,eax
                    mov     eax,enemy[esi].y
                    imul    eax,eax,40
                    mov     ecx,player.x
                    .IF (enemy[esi].x < ecx) && (enemy[esi].fy == eax)
                        inc     enemy[esi].x
                    .ENDIF

                    .IF (enemy[esi].x > ecx) && (enemy[esi].fy == eax)
                        dec     enemy[esi].x
                    .ENDIF

                    mov     eax,enemy[esi].x
                    imul    eax,eax,60
                    mov     ecx,player.y
                    .IF (enemy[esi].y < ecx) && (enemy[esi].fx == eax)
                        inc     enemy[esi].y
                    .ENDIF

                    .IF (enemy[esi].y > ecx) && (enemy[esi].fx == eax)
                        dec     enemy[esi].y
                    .ENDIF

                    mov     eax,3
                    sub     eax,level
                    .IF (delay > eax) && (hourglass.fx != 2)
                        mov     delay,0
                        mov     loop2,0
                        mov     eax,stage
                        xor     edx,edx
                        mul     level
                        .WHILE loop2 < eax

                            mov     eax,loop2
                            imul    eax,eax,sizeof enemy
                            mov     esi,eax
                            mov     eax,enemy[esi].x
                            imul    eax,eax,60
                            .IF enemy[esi].fx < eax
                                mov     ecx,adjust
                                mov     ecx,steps[ecx*4]
                                add     enemy[esi].fx,ecx
                                mov     tmp,ecx
                                fld     enemy[esi].spin
                                fiadd   tmp
                                fstp    enemy[esi].spin
                            .ENDIF

                            .IF enemy[esi].fx > eax
                                mov     ecx,adjust
                                mov     ecx,steps[ecx*4]
                                sub     enemy[esi].fx,ecx
                                mov     tmp,ecx
                                fld     enemy[esi].spin
                                fisub   tmp
                                fstp    enemy[esi].spin
                            .ENDIF

                            mov     eax,enemy[esi].y
                            imul    eax,eax,40
                            .IF enemy[esi].fy < eax
                                mov     ecx,adjust
                                mov     ecx,steps[ecx*4]
                                add     enemy[esi].fy,ecx
                                mov     tmp,ecx
                                fld     enemy[esi].spin
                                fiadd   tmp
                                fstp    enemy[esi].spin
                            .ENDIF

                            .IF enemy[esi].fy > eax
                                mov     ecx,adjust
                                mov     ecx,steps[ecx*4]
                                sub     enemy[esi].fy,ecx
                                mov     tmp,ecx
                                fld     enemy[esi].spin
                                fisub   tmp
                                fstp    enemy[esi].spin
                            .ENDIF

                            inc     loop2
                            mov     eax,stage
                            mul     level
                        .ENDW
                    .ENDIF

                    mov     eax,loop1
                    imul    eax,eax,sizeof enemy
                    mov     ecx,enemy[eax].fy
                    mov     eax,enemy[eax].fx
                    .IF (eax == player.fx) && (ecx == player.fy)
                        dec     lives
                        .IF lives == 0
                            mov     gameover,1
                        .ENDIF
                        invoke  ResetObjects
                        invoke  PlaySound,addr szSnd1,NULL,SND_SYNC
                    .ENDIF

                    inc     loop1
                    mov     eax,stage
                    mul     level
                .ENDW

                mov     eax,player.y
                imul    eax,eax,40
                mov     ecx,eax
                mov     eax,player.x
                imul    eax,eax,60
                .IF keys[VK_RIGHT] && (player.x < 10) && (player.fx == eax) && (player.fy == ecx)
                    mov     eax,player.x
                    imul    eax,eax,11
                    add     eax,player.y
                    mov     hline[eax],1
                    inc     player.x
                .ENDIF

                mov     eax,player.y
                imul    eax,eax,40
                mov     ecx,eax
                mov     eax,player.x
                imul    eax,eax,60
                .IF keys[VK_LEFT] && (player.x > 0) && (player.fx == eax) && (player.fy == ecx)
                    dec     player.x
                    mov     eax,player.x
                    imul    eax,eax,11
                    add     eax,player.y
                    mov     hline[eax],1
                .ENDIF

                mov     eax,player.y
                imul    eax,eax,40
                mov     ecx,eax
                mov     eax,player.x
                imul    eax,eax,60
                .IF keys[VK_DOWN] && (player.y < 10) && (player.fx == eax) && (player.fy == ecx)
                    mov     eax,player.x
                    imul    eax,eax,10
                    add     eax,player.y
                    mov     vline[eax],1
                    inc     player.y
                .ENDIF

                mov     eax,player.y
                imul    eax,eax,40
                mov     ecx,eax
                mov     eax,player.x
                imul    eax,eax,60
                .IF keys[VK_UP] && (player.y > 0) && (player.fx == eax) && (player.fy == ecx)
                    dec     player.y
                    mov     eax,player.x
                    imul    eax,eax,10
                    add     eax,player.y
                    mov     vline[eax],1
                .ENDIF

                mov     eax,player.x
                imul    eax,eax,60
                .IF player.fx < eax
                    mov     eax,adjust
                    mov     eax,steps[eax*4]
                    add     player.fx,eax
                .ENDIF

                mov     eax,player.x
                imul    eax,eax,60
                .IF player.fx > eax
                    mov     eax,adjust
                    mov     eax,steps[eax*4]
                    sub     player.fx,eax
                .ENDIF

                mov     eax,player.y
                imul    eax,eax,40
                .IF player.fy < eax
                    mov     eax,adjust
                    mov     eax,steps[eax*4]
                    add     player.fy,eax
                .ENDIF

                mov     eax,player.y
                imul    eax,eax,40
                .IF player.fy > eax
                    mov     eax,adjust
                    mov     eax,steps[eax*4]
                    sub     player.fy,eax
                .ENDIF

            .ELSE

                .IF keys[VK_SPACE]
                    mov     gameover,0
                    mov     filled,1
                    mov     level,1
                    mov     level2,1
                    mov     stage,0
                    mov     lives,5
                .ENDIF

            .ENDIF

            .IF filled
                invoke  PlaySound,addr szSnd2,NULL,SND_SYNC
                inc     stage
                .IF stage > 3
                    mov     stage,1
                    inc     level
                    inc     level2
                    .IF level > 3
                        mov     level,3
                        inc     lives
                        .IF lives > 5
                            mov     lives,5
                        .ENDIF
                    .ENDIF
                .ENDIF

                invoke  ResetObjects
                mov     loop1,0
                .WHILE loop1 < 11
                    mov     loop2,0
                    .WHILE loop2 < 11

                        .IF loop1 < 10
                            mov     eax,loop1
                            imul    eax,eax,11
                            add     eax,loop2
                            mov     hline[eax],0
                        .ENDIF

                        .IF loop2 < 10
                            mov     eax,loop1
                            imul    eax,eax,10
                            add     eax,loop2
                            mov     vline[eax],0
                        .ENDIF

                        inc     loop2
                    .ENDW

                    inc     loop1
                .ENDW

            .ENDIF

            mov     eax,hourglass.y
            imul    eax,eax,40
            mov     ecx,eax
            mov     eax,hourglass.x
            imul    eax,eax,60
            .IF (player.fx == eax) && (player.fy == ecx) && (hourglass.fx == 1)
                invoke  PlaySound,addr szSnd3,NULL,SND_ASYNC or SND_LOOP
                mov     hourglass.fx,2
                mov     hourglass.fy,0
            .ENDIF

            mov     eax,adjust
            mov     ecx,steps[eax*4]
            mov     tmp,ecx
            fild    tmp
            fmul    fpc(0.5f)
            fadd    player.spin
            fst     player.spin
            fld     fpc(360.0f)
            fcomip  st,st(1)
            setc    al
            .IF al
                fsub    fpc(360.0f)
                fstp    player.spin
            .ELSE
                fstp    tmp
            .ENDIF

            fldz
            mov     tmp,ecx
            fild    tmp
            fmul    fpc(0.25f)
            fld     hourglass.spin
            fsubrp  st(1),st
            fst     hourglass.spin
            fcomi   st,st(1)
            setc    al
            .IF al
                fadd    fpc(360.0f)
                fstp    hourglass.spin
                fstp    tmp
            .ELSE
                fstp    tmp
                fstp    tmp
            .ENDIF

            add     hourglass.fy,ecx
            xor		edx,edx
            mov     eax,6000
            div     level
            mov     tmp,eax
            .IF (hourglass.fx == 0) && (hourglass.fy > eax)
                invoke  PlaySound,addr szSnd4,NULL,SND_ASYNC
                invoke  nrandom,10
                inc     eax
                mov     hourglass.x,eax
                invoke  nrandom,11
                mov     hourglass.y,eax
                mov     hourglass.fx,1
                mov     hourglass.fy,0
            .ENDIF

            mov     eax,tmp
            .IF (hourglass.fx == 1) && (hourglass.fy > eax)
                mov     hourglass.fx,0
                mov     hourglass.fy,0
            .ENDIF

            mov     eax,level
            imul    eax,eax,500
            add     eax,500
            .IF (hourglass.fx == 2) && (hourglass.fy > eax)
                invoke  PlaySound,NULL,NULL,0
                mov     hourglass.fx,0
                mov     hourglass.fy,0
            .ENDIF

            inc     delay

        .ENDIF
    .ENDW

    invoke  KillGLWindow
    mov     eax,msg.wParam
    ret

WinMain ENDP

nrandom PROC _base:DWORD

;##########################################################################
;
;                     Park Miller random number algorithm.
;
;                      Written by Jaymeson Trudgen (NaN)
;                   Optimized by Rickey Bowers Jr. (bitRAKE)
;
;##########################################################################

    mov eax,nseed

    xor edx,edx
    mov ecx,127773
    div ecx
    mov ecx,eax
    mov eax,16807
    mul edx
    mov edx,ecx
    mov ecx,eax
    mov eax,2836
    mul edx
    sub ecx,eax
    xor edx,edx
    mov eax,ecx
    mov nseed,ecx
    div _base

    mov eax,edx
    ret

nrandom ENDP

strlen PROC cstr:DWORD

    mov     ecx,cstr
    xor     eax,eax
    .WHILE byte ptr [ecx] != 0
        inc     ecx
        inc     eax
    .ENDW
    ret

strlen ENDP

END Start


Instructions

The tutorials are written in C++. My assumption is that the reader is experienced in that language and in software development in general.
The primary requirement for running the tutorials is a GPU that supports version 3.3 of the OpenGL spec at least (though some of the tutorials, e.g. Tessellation, require 4.x support). The code provided by the tutorials was developed on Ubuntu Linux using NetBeans as a primary development platform (the C/C++ version is enough to be able to build the tutorials). You should be able to compile it without NetBeans by simply running 'make' from the command line. In order to install the required packages run the following commands:
  1. On Ubuntu: apt-get install g++ freeglut3-dev glew1.5-dev libmagick++-dev libassimp-dev libglfw-dev
  2. On Fedora: yum install gcc-c++ glew-devel freeglut-devel ImageMagick-c++-devel ImageMagick-c++ assimp-devel glfw-devel

Another thing that I did on Ubuntu to make my life easier is to run the following command as root:

ln /usr/lib/pkgconfig/libglfw.pc /usr/lib/pkgconfig/glfw3.pc

The reason is to use the same name for the package config file of GLFW in Ubuntu as in Fedora. This allows me to work with the same Netbeans project file for both distributions.

Note: since Nov-14, 2013, the source to all the tutorials is provided in a single zip package, instead of a package per tutorial. The package contains Netbeans projects for Linux and a Visual Studio solution for Windows (<ROOT>/Windows/ogldev/ogldev.sln). You can get the source package here.

To decrease the development time I'm maintaining only the 'Debug' build in both NetBeans and Visual Studio projects. Getting the 'Release' version up and running is simply a matter of copying the configuration there.


OpenGL Step by Step - OpenGL Development

  • Facebook
  • Instructions
  • FAQ
  • News
  • Contact
  • Donate
  • Get the source
  • Français
  • Pусский

Tutorial 1:

Open a window

Tutorial 2:

Hello dot!

Tutorial 3:

First triangle

Tutorial 4:

Shaders

Tutorial 5:

Uniform variables

Tutorial 6:

Translation transformation

Tutorial 7:

Rotation transformation

Tutorial 8:

Scaling transformation

Tutorial 9:

Interpolation

Tutorial 10:

Indexed draws

Tutorial 11:

Concatenating transformations

Tutorial 12:

Perspective Projection

Tutorial 13:

Camera Space

Tutorial 14:

Camera Control - Part 1

Tutorial 15:

Camera Control - Part 2

Tutorial 16:

Basic Texture Mapping

Tutorial 17:

Ambient Lighting

Tutorial 18:

Diffuse Lighting

Tutorial 19:

Specular Lighting

Tutorial 20:

Point Light

Tutorial 21:

Spot Light

Tutorial 22:

Loading models using the Open Asset Import Library

Tutorial 23:

Shadow Mapping - Part 1

Tutorial 24:

Shadow Mapping - Part 2

Tutorial 25:

Skybox

Tutorial 26:

Normal Mapping

Tutorial 27:

Billboarding and the Geometry Shader

Tutorial 28:

Particle System Using Transform Feedback

Tutorial 29:

3D Picking

Tutorial 30:

Basic Tessellation

Tutorial 31:

PN Triangles Tessellation

Tutorial 32:

Vertex Array Objects

Tutorial 33:

Instanced Rendering

Tutorial 34:

GLFX - An OpenGL Effects Library

Tutorial 35:

Deferred Shading - Part 1

Tutorial 36:

Deferred Shading - Part 2

Tutorial 37:

Deferred Shading - Part 3

Tutorial 38:

Skeletal Animation With Assimp

Tutorial 39:

Silhouette Detection

Tutorial 40:

Stencil Shadow Volume

Tutorial 41:

Object Motion Blur

Tutorial 42:

Percentage Closer Filtering

Tutorial 43:

Multipass Shadow Mapping With Point Lights

Tutorial 44:

GLFW

Tutorial 45:

Screen Space Ambient Occlusion

Tutorial 46:

SSAO With Depth Reconstruction

Tutorial 47:

Shadow Mapping With Directional Lights

Tutorial 48:

User Interface with Ant Tweak Bar

Tutorial 49:

Cascaded Shadow Mapping

Theme by Wojciech Mleczek


Tutorial 01 - Create a window

Tutorial 1:

Open a window

Background

Thanks to Mariano Suligoy you can read this tutorial in Spanish.

The OpenGL spec does not specify any API in order to create and manipulate windows. Modern windowing systems that support OpenGL include a sub-system that provides the binding between an OpenGL context and the windowing system. In the X Window system that interface is called GLX. Microsoft provides WGL (pronounced: Wiggle) for Windows and MacOS has CGL. Working directly with these interfaces in order to create a window in which to display graphics is usually grunt work which is why we use a high level library that abstracts away the fine details. The library we use here is called the 'OpenGL utility library', or GLUT. It provides a simplified API for window management as well as event handling, IO control and a few other services. In addition, GLUT is cross platform which makes portability easier. Alternatives to GLUT include SDL and GLFW.

Source walkthru

glutInit(&argc, argv);

This call initializes GLUT. The parameters can be provided directly from the command line and include useful options such as '-sync' and '-gldebug' which disable the asynchronous nature of X and automatically checks for GL errors and displays them (respectively).

glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);

Here we configure some GLUT options. GLUT_DOUBLE enables double buffering (drawing to a background buffer while another buffer is displayed) and the color buffer where most rendering ends up (i.e. the screen). We will usually want these two as well as other options which we will see later.

glutInitWindowSize(1024, 768);
glutInitWindowPosition(100, 100);
glutCreateWindow("Tutorial 01");

These calls specify the window parameters and create it. You also have the option to specify the window title.

glutDisplayFunc(RenderSceneCB);

Since we are working in a windowing system most of the interaction with the running program occurs via event callback functions. GLUT takes care of interacting with the underlying windowing system and provides us with a few callback options. Here we use just one - a "main" callback to do all the rendering of one frame. This function is continuously called by GLUT internal loop.

glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

This is our first encounter with the concept of state in OpenGL. The idea behind state is that rendering is such a complex task that it cannot be treated as a function call that receives a few parameters (and correctly designed functions never receive a lot of parameters). You need to specify shaders, buffers and various flags that affect how rendering will take place. In addition, you would often want to keep the same piece of configuration across several rendering operations (e.g. if you never disable the depth test then there is no point in specifying it for every render call). That is why most of the configuration of rendering operations is done by setting flags and values in the OpenGL state machine and the rendering calls themselves are usually limited to the few parameters that revolve around the number of vertices to draw and their starting offset. After calling a state changing function that particular configuration remains intact until the next call to the same function with a different value. The call above sets the color that will be used when clearing the framebuffer (described later). The color has four channels (RGBA) and it is specified as a normalized value between 0.0 and 1.0.

glutMainLoop();

This call passes control to GLUT which now begins its own internal loop. In this loop it listens to events from the windowing system and passes them via the callbacks that we configured. In our case GLUT will only call the function we registered as a display callback (RenderSceneCB) to give us a chace to render the frame.

glClear(GL_COLOR_BUFFER_BIT);
glutSwapBuffers();

The only thing we do in our render function is to clear the framebuffer (using the color specified above - try changing it). The second call tells GLUT to swap the roles of the backbuffer and the frontbuffer. In the next round through the render callback we will render into the current frames front buffer and the current backbuffer will be displayed.

Next tutorial


Tutorial 02 - Hello dot!

Tutorial 2:

Hello dot!

Background

This is our first encounter with GLEW, the OpenGL Extension Wrangler Library. GLEW helps you deal with the headache that can accompany the management of extensions in OpenGL. Once initialized it queries for all the available extensions on your platform, dynamically loads them and provides easy access via a single header file.

In this tutorial we will see the usage of vertex buffer objects (VBOs) for the first time. As the name implies, they are used to store vertices. The objects that exist in the 3D world you are trying to visualize, be it monsters, castles or a simple revolving cube, are always built by connecting together a group of vertices. VBOs are the most efficient way to load vertices into the GPU. They are buffers that can be stored in video memory and provide the shortest access time to the GPU so they are definitely recommended.

This tutorial and the next are the only ones in this series where we will rely on the fixed function pipeline instead of the programmable one. Actually, no transformations at all take place in both these tutorials. We simply rely on the way data flows through the pipe. A thorough study of the pipe will follow in the next tutorials but for now it is enough to understand that before reaching the rasterizer (that actually draws points, lines and triangles using screen coordinates) the visible vertices have their X, Y and Z coordinates in the range [-1.0,1.0]. The rasterizer maps these coordinates to screen space (e.g, if the screen width is 1024 then the X coodinate -1.0 is mapped to 0 and 1.0 is mapped to 1023). Finally, the rasterizer draws the primitives according to the topology which is specified in the draw call (see below in the source walkthru). Since we didn't bind any shader to the pipeline our vertices undergo no transformation. This means that we just need to give them a value in the above range in order to make them visible. In fact, selecting zero for both X and Y places the vertex in the exact midpoint of both axis - in other words, the middle of the screen.

Installing GLEW: GLEW is available from its main website at http://glew.sourceforge.net/. Most Linux distributions provide prebuilt packages for it. On Ubuntu you can install it by running the following from the command line:

apt-get install libglew1.6 libglew1.6-dev

Source walkthru

#include <GL/glew.h>

Here we include the single GLEW header. If you include other OpenGL headers you must be careful to include this file before the others else GLEW will complain that about it. In order to link the program with GLEW you need to add '-lGLEW' to the makefile.

#include "math_3d.h"

In this tutorial we start using helper structures such as vector. We will expand this header as we go along.

GLenum res = glewInit();
if (res != GLEW_OK)
{
    fprintf(stderr, "Error: '%s'\n", glewGetErrorString(res));
    return 1;
}

Here we initialize GLEW and check for any errors. This must be done after GLUT has been initialized.

Vector3f Vertices[1];
Vertices[0] = Vector3f(0.0f, 0.0f, 0.0f);

We create an array of one Vector3f structures (this type is defined in math_3d.h) and initialize XYZ to be zero. This will make the dot appear at the middle of the screen.

GLuint VBO;

We allocate a GLuint in the global part of the program to store the handle of the vertex buffer object. You will see later that most (if not all) OpenGL objects are accessed via a variable of GLuint type.

glGenBuffers(1, &VBO);

OpenGL defines several glGen* functions for generating objects of various types. They often take two parameters - the first one specifies the number of objects you want to create and the second is the address of an array of GLuints to store the handles that the driver allocates for you (make sure the array is large enough to handle your request!). Future calls to this function will not generate the same object handles unless you delete them first with glDeleteBuffers. Note that at this point you don't specify what you intend to do with the buffers so they can be regarded as "generic". This is the job of the next function.

glBindBuffer(GL_ARRAY_BUFFER, VBO);

OpenGL has a rather unique way of using handles. In many APIs the handle is simply passed to any relevant function and the action is taken on that handle. In OpenGL we bind the handle to a target name and then execute commands on that target. These commmands affect the bounded handle until another one is bound in its stead or the call above takes zero as the handle. The target GL_ARRAY_BUFFER means that the buffer will contain an array of vertices. Another useful target is GL_ELEMENT_ARRAY_BUFFER which means that the buffer contains the indices of the vertices in another buffer. Other targets are also available and we will see them in future tutorials.

glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);

After binding our object we fill it with data. The call above takes the target name (same as what we used for binding), the size of the data in bytes, address of the array of vertices and a flag that indicates the usage pattern for this data. Since we are not going to change the buffer contents we specify GL_STATIC_DRAW. The opposite will be GL_DYNAMIC_DRAW. While this is only a hint to OpenGL it is a good thing to give some thought as to the proper flag to use. The driver can rely on it for optimization heuristics (such as what is the best place in memory to store the buffer).

glEnableVertexAttribArray(0);

In the shaders tutorial you will see that vertex attributes used in the shader (position, normal, etc) have an index mapped to them that enable you to create the binding between the data in the C/C++ program and the attribute name inside the shader. In addition you must also enable each vertex attribute index. In this tutorial we are not yet using any shader but the vertex position we have loaded into the buffer is treated as vertex attribute index 0 in the fixed function pipeline (which becomes active when there is no shader bound). You must enable each vertex attribute or else the data will not be accessible by the pipeline.

glBindBuffer(GL_ARRAY_BUFFER, VBO);

Here we bind our buffer again as we prepare for making the draw call. In this small program we only have one vertex buffer so making this call every frame is redundent but in more complex programs there are multiple buffers to store your various models and you must update the pipeline state with the buffer you intend to use.

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);

This call tells the pipeline how to interpret the data inside the buffer. The first parameter specifies the index of the attribute. In our case we know that it is zero by default but when we start using shaders we will either need to explicitly set the index in the shader or query it. The second parameter is the number of components in the attribute (3 for X, Y and Z). The third parameter is the data type of each component. The next parameter indicates whether we want our attribute to be normalized before it is used in the pipeline. It our case we want the data to pass un-changed. The fifth parameter (called the 'stride') is the number of bytes between two instances of that attribute in the buffer. When there is only one attribute (e.g. the buffer contains only vertex positions) and the data is tightly packed we pass the value zero. If we have an array of structures that contain a position and normal (each one is a vector of 3 floats) we will pass the size of the structure in bytes (6 * 4 = 24). The last parameter is useful in the case of the previous example. We need to specify the offset inside the structure where the pipeline will find our attribute. In the case of the structure with the position and normal the offset of the position is zero while the offset of the normal is 12.

glDrawArrays(GL_POINTS, 0, 1);

Finally, we make the call to draw the geometry. All the commands that we've seen so far are important but they only set the stage for the draw command. This is where the GPU really starts to work. It will now combine the parameters of the draw call with the state that was built up to this point and render the results to the screen.

OpenGL provides several types of draw calls and each one is appropriate for a different case. In general you can divide them up to two categories - ordered draws and indexed draws. Ordered draws are simpler. The GPU traverses your vertex buffer, going through the vertices one by one, and interprets them according to the topology specified in the draw call. For example, if you specify GL_TRIANGLES then vertices 0-2 become the first triangle, 3-5 the second, etc. If you want the same vertex to appear in more than one triangle you will need to specify it twice in the vertex buffer, which is a waste of space.

Indexed draws are more complex and involve an additional buffer called the index buffer. The index buffer contains indices of the vertices in the vertex buffer. The GPU scan the index buffer and in a similar fashion to the description above indices 0-2 become the first triangle and so on. If you want the same vertex in two triangles simply specify its index twice in the index buffer. The vertex buffer needs only to contain one copy. Index draws are more common in games because most models are created from triangles that represent some surface (skin of a person, castle wall, etc) with a lot of vertex sharing between them.

In this tutorial we use the simplest draw call - glDrawArrays. This is an ordered draw so there is no index buffer. We specify the topology as points which means every vertex is one point. The next parameter is the index of the first vertex to draw. In our case we want to start at the beginning of the buffer so we specify zero but this enables us to store multiple models in the same buffer and then select the one to draw based on its offset in the buffer. The last parameter is the number of vertices to draw.

glDisableVertexAttribArray(0);

It is good practice to disable each vertex attribute when it is not immediately used. Leaving it enabled when a shader is not using it is a sure way of asking for trouble.

Next tutorial


Tutorial 03 - First Triangle

Tutorial 3:

First Triangle

Background

This tutorial is very short. We simply expand the previous tutorial to render a triangle.

In this tutorial we rely on the normalized box again. Visible vertices must be inside the box so that viewport transformation will map them to the visible coordinates of the window. When looking down the negative Z axis this box looks like that:

Point (-1.0, -1.0) is mapped to the bottom left hand of the window, (-1.0, 1.0) is the upper left and so on. If you extend the position of one of the vertices below outside this box the triangle will be clipped and you will only see a part of it.

Source walkthru

Vector3f Vertices[3];
Vertices[0] = Vector3f(-1.0f, -1.0f, 0.0f);
Vertices[1] = Vector3f(1.0f, -1.0f, 0.0f);
Vertices[2] = Vector3f(0.0f, 1.0f, 0.0f);

We extended the array to contain three vertices.

glDrawArrays(GL_TRIANGLES, 0, 3);

Two changes were made to the drawing function: we draw triangles instead of points and we draw 3 vertices instead of 1.

Next tutorial


Tutorial 04 - Shaders

Tutorial 4:

Shaders

Background

From this tutorial forward every effect and technique we will implement will be done using shaders. Shaders are the modern way of doing 3D graphics. In a way you could claim that this is a step back as most of the 3D functionality that was provided by the fixed function pipeline and required the developer to only specify configuration parameters (lighting attributes, rotation values, etc) must now be implemented by the developer (via shaders), however, this programmability enables great flexibility and innovation.

The OpenGL programmable pipeline can be visualized as follows:

The vertex processor is in charge of executing the vertex shader on each and every vertex that passes through the pipeline (the number of which is determined according to the parameters to the draw call). Vertex shaders have no knowledge about the topology of the rendered primitives. In addition, you cannot discard vertices in the vertex processor. Each vertex enters the vertex processor exactly once, undergoes transformations and continues down the pipe.

The next stage is the geometry processor. In this stage the knoweldge as to the complete primitive (i.e. all of its vertices) as well as neighboring vertices is provided to the shader. This enables techniques that must take into account additional information beside the vertex itself. The geometry shader also has the ability to switch the output topology to a different one than the topology selected in the draw call. For example, you may supply it with a list of points and genereate two triangles (i.e. a quad) from each point (a technique known as billboarding). In addition, you have the option to emit multiple vertices for each geometry shader invocation and thus generate multiple primitives according to the output topology you selected.

The next stage in the pipe is the clipper. This is a fixed function unit with a straightforward task - it clips the primitives to the normalized box we have seen in the previous tutorial. It also clips them to the near Z and the far Z planes. There is also the option to supply user clip planes and have the clipper clip against them. The position of vertices that have survived the clipper is now mapped to screen space coordinates and the rasterizer renders them to the screen according to their topology. For example, in the case of triangles this means finding out all the points that are inside the triangle. For each point the rasterizer invokes the fragment processor. Here you have the option to determine the color of the pixel by sampling it from a texture or using whatever technique you desire.

The three programmable stages (vertex, geometry and fragment processors) are optional. If you don't bind a shader to them some default functionality will be executed.

Shader management is very similar to C/C++ program creation. First you write the shader text and make it available to your program. This can done by simply including the text in an array of characters in the source code itself or by loading it from an external text file (again into an array of characters). Then you compile the shaders one by one into shader objects. After that you link the shaders into a single program and load it into the GPU. Linking the shaders gives the driver the opportunity to trim down the shaders and optimize them according to their relationships. For example, you may pair a vertex shader that emits a normal with a fragment shader that ignores it. In that case the GLSL compiler in the driver can remove the normal related functionality of the shader and enable faster execution of the vertex shader. If that shader is later paired with a fragment shader that uses the normal then linking the other program will generate a different vertex shader.

Source walkthru

GLuint ShaderProgram = glCreateProgram();

We start the process of setting up our shaders by creating a program object. We will link all the shaders together into this object.

GLuint ShaderObj = glCreateShader(ShaderType);

We create two shader objects using the above call. One of them with shader type GL_VERTEX_SHADER and the other GL_FRAGMENT_SHADER. The process of specifying the shader source and compiling the shader is the same for both.

const GLchar* p[1];
p[0] = pShaderText;
GLint Lengths[1];
Lengths[0]= strlen(pShaderText);
glShaderSource(ShaderObj, 1, p, Lengths);

Before compiling the shader object we must specify its source code. The function glShaderSource takes the shader object as a parameter and provides you with flexibility in terms of specifying the source. The source can be distributed across several character arrays and you will need to provide an array of pointers to these arrays as well as an array of integers where each slot contains the length of the corresponding character array. For simplicity we use a single array of chars for the entire shader source and we use just one slot for both the pointer to the source as well as its length. The second parameter to the call is the number of slots in the two arrays (just 1 in our case).

glCompileShader(ShaderObj);

Compiling the shader is very easy...

GLint success;
glGetShaderiv(ShaderObj, GL_COMPILE_STATUS, &success);
if (!success) {
    GLchar InfoLog[1024];
    glGetShaderInfoLog(ShaderObj, sizeof(InfoLog), NULL, InfoLog);
    fprintf(stderr, "Error compiling shader type %d: '%s'\n", ShaderType, InfoLog);
}

...however, you usually get a few compilation errors, as expected. The piece of code above gets the compilation status and display all the errors the compiler encountered.

glAttachShader(ShaderProgram, ShaderObj);

Finally, we attach the compiled shader object to the program object. This is very similar to specifying the list of objects for linking in a makefile. Since we don't have a makefile here we emulate this behavior programatically. Only the attached objects take part of the linking process.

glLinkProgram(ShaderProgram);

After compiling all shader objects and attaching them to the program we can finally link it. Note that after linking the program you can get rid of the intermediate shader objects by calling glDetachShader and glDeleteShader for each and every one of them. The OpenGL driver maintains a reference count on most of the objects it generates. If a shader object is created and then deleted the driver will get rid of it, but if it is attached to a program calling glDeleteShader will only mark it for deletion and you will also need to call glDetachShader so that its reference count will drop to zero and it will be removed.

glGetProgramiv(ShaderProgram, GL_LINK_STATUS, &Success);
if (Success == 0) {
    glGetProgramInfoLog(ShaderProgram, sizeof(ErrorLog), NULL, ErrorLog);
    fprintf(stderr, "Error linking shader program: '%s'\n", ErrorLog);
}

Note that we check for program related errors (such as link errors) a bit differently than shader related errors. Instead of glGetShaderiv we use glGetProgramiv and instead of glGetShaderInfoLog we use glGetProgramInfoLog.

glValidateProgram(ShaderProgram);

You may ask yourself why do we need to validate a program after it has been successfully linked. The difference is that linking checks for errors based on the combination of shaders while the call above check whether the program can execute given the current pipeline state. In a complex application with multiple shaders and lots of state changes it is better to validate before every draw call. In our simple app we check it just once. Also, you may want to do this check only during development and avoid this overhead in the final product.

glUseProgram(ShaderProgram);

Finally, to use the linked shader program you set it into the pipeline state using the call above. This program will stay in effect for all draw calls until you replace it with another or explicitly disable its use (and enable the fixed function pipeline) by calling glUseProgram with NULL. If you created a shader program that contains only one type of shader then the other stages operate using their default fixed functionality.

We have completed the walkthru of the OpenGL calls related to shader management. The rest of this tutorial relates to the contents of the vertex and fragment shaders (contained in the 'pVS' and 'pFS' variables).

#version 330

This tells the compiler that we are targeting version 3.3 of GLSL. If the compiler does not support it it will emit an error.

layout (location = 0) in vec3 Position;

This statement appears in the vertex shader. It declares that a vertex specific attribute which is a vector of 3 floats will be known as 'Position' in the shader. 'Vertex specific' means that for every invocation of the shader in the GPU the value of a new vertex from the buffer will be supplied. The first section of the statement, layout (location = 0), creates the binding between the attribute name and attribute in the buffer. This is required for cases where our vertex contains several attributes (position, normal, texture coordinates, etc). We have to let the compiler know which attribute in the vertex in the buffer must be mapped to the declared attribute in the shader. There are two ways to do this. We can either set it explicitly as we do here (to zero). In that case we can use a hard coded value in our application (which we did with the first parameter to the call to glVertexAttributePointer). Or we can leave it out (and simply declare 'in vec3 Position' in the shader) and then query the location from the application at runtime using glGetAttribLocation. In that case we will need to supply the returned value to glVertexAttributePointer instead of using the hard coded value. We choose the simply way here but for more complex applications it better to let the compiler determine the attribute indices and query them during runtime. This makes it easier integrating shaders from multiple sources without adapting them to your buffer layout.

void main()

You can create your shader by linking together multiple shader objects. However, there can only be one main function for each shader stage (VS, GS, FS) which is used as the entry point to the shader. For example, you can create a lighting library with several functions and link it with your shader provided that none of the functions there is named 'main'.

gl_Position = vec4(0.5 * Position.x, 0.5 * Position.y, Position.z, 1.0);

Here we do hard coded transformation to the incoming vertex position. We cut the X and Y values by half and leave the Z unchanged. 'gl_Position' is a special built in variable that is supposed to contain the homogeneous (containing X, Y, Z and W components) vertex position. The rasterizer will look for that variable and will use it as the position in screen space (following a few more transformations). Cutting the X and Y values by half means that we will see a triangle which is one quarter the size of the triangle in the previous tutorial. Note that we are setting W to 1.0. This is extremely important to getting the triangle displayed correctly. Getting the projection from 3D to 2D is actually accomplished in two seperate stages. First you need to multiply all your vertices by the projection matrix (which we will develop in a few tutorials) and then the GPU automatically performs what is known as "perspective divide" to the position attribute before it reaches the rasterizer. This means that it divides all the components of the gl_Position by the W component. In this tutorial we are not yet doing any projection in the vertex shader but the perspective divide stage is something that we cannot disable. Whatever gl_Position value we output from the vertex shader will be divided by the HW using its W component. We need to remember that else we will not get the results we expect. In order to circumvent the effect of perspective divide we set W to 1.0. Division by 1.0 will not affect the other components of the position vector which will stay inside our normalized box.

If everything worked correctly, three vertices with the values (-0.5, -0.5), (0.5, -0.5) and (0.0, 0.5) reach the rasterizer. The clipper doesn't need to do anything because all vertices are well inside the normalized box. These values are mapped to screen space coordinates and the rasterizer starts running over all the points that are inside the triangle. For each point the fragment shader is executed. The following shader code is taken from the fragment shader.

out vec4 FragColor;

Usually the job of the fragment shader is to determine the color of the fragment (pixel). In addition, the fragment shader can discard the pixel altogether or change its Z value (which will affect the result of subsequent Z test). Outputing the color is done by declaring the above variable. The four components represent R, G, B and A (for alpha). The value that you set into this variable will be received by the rasterizer and evantually written to the framebuffer.

FragColor = vec4(1.0, 0.0, 0.0, 1.0);

In the previous couple of tutorials there wasn't a fragment shader so the everything was drawn in the default color of white. Here we set FragColor to red.

Next tutorial


Tutorial 05 - Uniform Variables

Tutorial 5:

Uniform Variables

Background

In this tutorial we meet a new type of shader variables - uniform variables. The difference between attribute and uniform variable is that attribute variables contain data which is vertex specific so they are reloaded with a new value from the vertex buffer for each shader invocation while the value of uniform variables remains constant accross the entire draw call. This means that you load the value before making the draw call and then you can access the same value in each invocation of the vertex shader. Uniform variables are useful for storing data such as lighting parameters (light position and direction, etc), transformation matrices, texture objects handles and so on.

In this tutorial we finally get something moving on the screen. We do it using a combination of a uniform variable whose value we change every frame and the idle callback function supplied by GLUT. The point is that GLUT doesn't call our render callback function repeatedly - unless it has to. GLUT has to call the render callback following events such as minimizing and maximizing the window or uncovering it by another window. If we don't change anything in the windows layout after launching the application the render callback is called just once. You can see for yourself by adding a printf call in the render function. You will see the output only once and you will see it again if you minimize and then maximize the window. Registering only the render callback in GLUT was fine for the previous tutorials but here we want to repeatedly change the value of a variable. We do this by registering an idle function callback. The idle function is called by GLUT when no events are received from the windowing system. You can have a dedicated function for this callback where you will do any bookkeeping such as time update or simply register the render callback function as an idle callback as well. In this tutorial we do the later and update the variable inside the render function.

Source walkthru

glutIdleFunc(RenderSceneCB);

Here we register the render callback as an idle function. Note that if you decide to use a dedicated idle callback function then you will need to add a call to glutPostRedisplay() at the end of it. Else the idle function will be called again and again but the render function will not. glutPostRedisplay() marks the current window as needed to be redisplayed and during the next round through GLUT's main loop the render callback will be called.

gScaleLocation = glGetUniformLocation(ShaderProgram, "gScale");
assert(gScaleLocation != 0xFFFFFFFF);

After linking the program we query the program object for the location of the uniform variable. This is another example of a case where the application C/C++ execution environment needs to be mapped to the shader execution environment. You don't have any direct access to shader content and you cannot directly update its variables. When you compile the shader the GLSL compiler assigns an index to each uniform variable. In the internal representation of the shader inside the compiler access to the variable is resolved using its index. That index is also available to the application via the glGetUniformLocation. You call this function with the program object handle and the name of the variable. The function returns the index or -1 if there was an error. It is very important to check for errors (as we do above with the assertion) or else future updates to the variables will not be delivered to the shader. There are mainly two reasons why this function can fail. You either misspelled the name of the variable or it was optimized away by the compiler. If the GLSL compiler finds out that the variable is not actually used in the shader it can simply drop it. In that case glGetUniformLocation will fail.

static float Scale = 0.0f;
Scale += 0.001f;
glUniform1f(gScaleLocation, sinf(Scale));

We maintain a static floating point variable that we increment a bit in every call to the render function (you may want to play with 0.001 if it runs too slowly or too quickly on your machine). The actual value which is passed to the shader is the sinus of the 'Scale' variable. This is to create a nice loop between -1.0 and 1.0. Note that sinf() takes radians and not degrees as a parameter but at this point we simply don't care. We just want the wave that sinus generates. The result of sinf() is passed to the shader using glUniform1f. OpenGL provides multiple instances of this function with the general form of glUniform{1234}{if}. You can use it to load values into a 1D, 2D, 3D or 4D (based on the number that follows the 'glUniform') vector of floating point or integer (this is the 'i' or 'f' suffix). There are also versions that take a vector address as a parameter as well as special version for matrices. The first parameter to the function is the index location that we have extracted using glGetUniformLocation().

We will now take a look at changes that were made in the VS (the FS remains unchanged).

uniform float gScale;

Here we declare the uniform value in the shader.

gl_Position = vec4(gScale * Position.x, gScale * Position.y, Position.z, 1.0);

We multiply the X and Y values of the position vector with the value that is changed from the application every frame. Can you explain why the triangle is upside down half of the loop?

Next tutorial


Tutorial 06 - Translation Transformation

Tutorial 6:

Translation Transformation

Background

In this tutorial we start looking at the various transformations that take an object in 3D and enable it to be displayed on screen while keeping the illusion of depth in the scene. The common way to do this is to represent each transformation using a matrix, multiply them one by one and then multiply the vertex position by the final product. Each tutorial will be dedicated to examining one transformation.

Here we take a look at the translation transformation which is responsible to moving an object along a vector of any length and direction. Let's say you want to move the triangle in the left picture to the location on the right:

One way to do it is to provide the offset vector (in this case - 1,1) as a uniform variable to the shader and simply add it to the position of each processed vertex. However, this breaks the method of multiplying a group of matrices into one to get a single comprehensive transformation. In addition, you will see later that translation is usually not the first one so you will to multiply the position by the matrix that represent the transformations before translation, then add the position and finally multiple by the matrix that represent the transformation that follow translation. This is too awkward. A better way will be to find a matrix that represents the translation and take part in the multiplication of all matrices. But can you find a matrix that when multiplied by the point (0,0), the bottom left vertex of the triangle on the left, gives the result (1,1)? The truth is that you can't do it using a 2D matrix (and you cannot do it with a 3D matrix for (0,0,0) ). In general we can say that what we need is a matrix M that given a point P(x,y,z) and a vector V(v1,v2,v3) provides M * P=P1(x + v1, y + v2, z + v3). In simple words this means that matrix M translates P to location P+V. In P1 we can see that each component is a sum of a component from P and the corresponding component of V. The left side of each sum equation is provided by the identity matrix:
I * P = P(x,y,z). So it looks like we should start with the identity matrix and find out the changes that will complete the right hand side of the sum equation in each component (...+V1, ...+V2, ...+V3). Let's see how the identity matrix looks like:

We want to modify the identity matrix such that the result will be:

There really isn't an easy way to do this if we stick to 3x3 matrix, but if we change to a 4x4 matrix we can do the following:

Representing a 3-vector using a 4-vector like that is called homogenous coordinates and is very popular and useful for 3D graphics. The fourth component is called 'w'. In fact, the internal shader symbol gl_Position that we have seen in the previous tutorial is a 4-vector and the w component has a very important role for making the projection from 3D to 2D. The common notation is to use w=1 for points and w=0 for vectors. The reason is that points can be translated but vectors cannot. You can change the length of a vector or its direction but all vectors with the same length/direction are considered equal, regardless their "starting position". So you can simply use the origin for all vectors. Setting w=0 and multiplying the translation matrix by the vector will result in the same vector.

Source walkthru

struct Matrix4f {
    float m[4][4];
};

We added a 4x4 matrix definition to math_3d.h. This will be used for most of our transformation matrices from now on.

GLuint gWorldLocation;

We use this handle to access the world matrix uniform variable in the shader. We name it 'world' because what we are doing to the object is moving (translating) its location to where we want it in the coordinate system of our virtual "world".

Matrix4f World;
World.m[0][0] = 1.0f; World.m[0][1] = 0.0f; World.m[0][2] = 0.0f; World.m[0][3] = sinf(Scale);
World.m[1][0] = 0.0f; World.m[1][1] = 1.0f; World.m[1][2] = 0.0f; World.m[1][3] = 0.0f;
World.m[2][0] = 0.0f; World.m[2][1] = 0.0f; World.m[2][2] = 1.0f; World.m[2][3] = 0.0f;
World.m[3][0] = 0.0f; World.m[3][1] = 0.0f; World.m[3][2] = 0.0f; World.m[3][3] = 1.0f;

In the render function we prepare a 4x4 matrix and poplulate it according to the explanation above. We set v2 and v3 to zero so we expect no change in the Y and Z coordinate of the object and we set v1 to the result of the sinus function. This will translate the X coordinate by a value that swings nicely between -1 and 1. Now we need to load the matrix into the shader.

glUniformMatrix4fv(gWorldLocation, 1, GL_TRUE, &World.m[0][0]);

This is another example of a glUniform* function to load data into uniform shader variables. This specific function loads 4x4 matrices and there are also versions for 2x2, 3x3, 3x2, 2x4, 4x2, 3x4 and 4x3. The first parameter is the location of the uniform variable (retrieved after shader compilation using glGetUniformLocation()). The second parameter indicates the number of matrices we are updating. We use 1 for one matrix but we can also use this function to update multiply matrices in one call. The third parameter often confuses newcomers. It indicates whether the matrix is supplied in row-major or column-major order. Row-major means the matrix is supplied row after row, starting from the top. Column-major is the same but in columns. The point is that C/C++ are row-major languages by default. This means that when you populate a two dimentional array with values they are laid out in memory row after row with the "top" row at the lower address. For example, see the following array:

int a[2][3];
a[0][0] = 1;
a[0][1] = 2;
a[0][2] = 3;
a[1][0] = 4;
a[1][1] = 5;
a[1][2] = 6;

Visually, the array looks like the following matrix:

1 2 3
4 5 6

And the memory layout is like that: 1 2 3 4 5 6 (with 1 at the lower address).

So our third parameter to glUniformMatrix4fv() is GL_TRUE because we supply the matrix in row-major order. We can also make the third parameter GL_FALSE but then we will need to transpose the matrix values (the C/C++ memory layout will remain the same but OpenGL will "think" that the first 4 values we supply are actually a matrix column and so on and will behave accordingly). The fourth parameter is simply the starting address of the matrix in memory.

The remaining source is shader code.

uniform mat4 gWorld;

This is a uniform variable of a 4x4 matrix. mat2 and mat3 are also available.

gl_Position = gWorld * vec4(Position, 1.0);

The position of the triangle vertices in the vertex buffer are vectors of 3 components, but we agreed that we need a fourth component with the value of 1. There are two options: place vertices with 4 components in the vertex buffer or add the fourth component in the vertex shader. There is no advantage to the first option. Each vertex position will consume an additional 4 bytes for a component which is known to be always 1. It is more efficient to stay with a 3 component vector and concatenate the w component in the shader. In GLSL this is done using 'vec4(Position, 1.0)'. We multiply the matrix by that vector and the result goes into gl_Position. To summarize, in every frame we generate a translation matrix that translates the X coordinate by a value that goes back and fourth between -1 and 1. The shader multiplies the position of every vertex by that matrix which results in the combined object moving left and right. In most cases the one of the triangles sides will go out of the normalized box after the vertex shader and the clipper will clip out that side. We will only be able to see the region which is inside the normalized box.

Next tutorial


Tutorial 07 - Rotation Transformation

Tutorial 7:

Rotation Transformation

Background

Next on our transforation list is the rotation, that is, given an angle and a point we want to rotate the point around one of the axis. We will always change two out of the trio X, Y and Z and leave the third component unchanged. This means that the path will lie on one of the three major planes: XY (when turning around Z), YZ (when turning around X) and XZ (when turning around Y). There are more complex rotation transformations that allow you to rotate around an arbitrary vector but we don't need them at this stage.

Let's define the problem in general terms. Consider the following diagram:

We want to move along the circle from (x1,y1) to (x2,y2). In other words we want to rotate (x1,y1) by the angle a2. Let's assume that the radius of the circle is 1. This means the following:

We will use the following trigonometric identities to develop x2 and y2:

Using the above we can write:

In the above diagram we are looking at the XY plane and Z is pointing into the page. If X&Y are part of a 4-vector then the above equation can be written in matrix form (without affecting Z&W):

If we want to create rotations for the Y and Z axis then the equations are basically the same but the matrix is arranged a bit differently. Here's the matrix for the rotation around the Y axis:

And the rotation matrix around the X axis:

Source walkthru

The code changes in this tutorial are very minor. We only change the contents of the single transformation matrix in the code.

World.m[0][0]=cosf(Scale); World.m[0][1]=-sinf(Scale); World.m[0][2]=0.0f; World.m[0][3]=0.0f;
World.m[1][0]=sinf(Scale); World.m[1][1]=cosf(Scale);  World.m[1][2]=0.0f; World.m[1][3]=0.0f;
World.m[2][0]=0.0f;        World.m[2][1]=0.0f;         World.m[2][2]=1.0f; World.m[2][3]=0.0f;
World.m[3][0]=0.0f;        World.m[3][1]=0.0f;         World.m[3][2]=0.0f; World.m[3][3]=1.0f;

As you can see we rotate around the Z axis. You can try the other rotations as well but I think that at this point without true projection from 3D to 2D the other rotations look a bit odd. We will complete them in a full transformation pipeline class in the coming tutorials.

Next tutorial


Tutorial 08 - Scaling Transformation

Tutorial 8:

Scaling Transformation

Background

The scaling transformation is very simple. Its purpose is to either increase or decrease the size of the object. You may want to do that, for example, when you want to create some differentiation using the same model (large and small trees that are actually the same) or when you want to match the size of the object to its role in the world. For the above examples you would probably want to scale the vertices position in the same amount on all three axis. However, sometimes you may want to scale just one or two axis, causing the model to become "thicker" or "leaner".

Developing the transformation matrix is very busy. We start with the identity matrix and remember that the reason that multiplying it by a vector leave the vector unchanged is that each of the '1's in the diagonal is multiplied by one of the components in turn. None of the components can affect the other. Therefore, replacing any one of that '1's with another value will cause the object to increase on that axis if the other value is larger than 1 or decrease on that axis if the other value is smaller then one.

Source walkthru

World.m[0][0]=sinf(Scale); World.m[0][1]=0.0f;        World.m[0][2]=0.0f;        World.m[0][3]=0.0f;
World.m[1][0]=0.0f;        World.m[1][1]=sinf(Scale); World.m[1][2]=0.0f;        World.m[1][3]=0.0f;
World.m[2][0]=0.0f;        World.m[2][1]=0.0f;        World.m[2][2]=sinf(Scale); World.m[2][3]=0.0f;
World.m[3][0]=0.0f;        World.m[3][1]=0.0f;        World.m[3][2]=0.0f;        World.m[3][3]=1.0f;

The only change from the previous tutorial is that we replace the world transformation matrix according to the above description. As you can see, we scale each of the three axis by a number that swings between -1 and 1. In the range (0,1] the triangle is anywhere between being very tiny and its original size and when the diagonal is zero it disappears completely. In the range [-1,0) looks the same only reversed because the scaling value in the diagonal actually changed the sign of the position.

Next tutorial


Tutorial 09 - Interpolation

Tutorial 9:

Interpolation

Background

This tutorial demonstrates a very important part of the 3D pipeline - the interpolation that the rasterizer performs on variables that come out of the vertex shader. As you have already seen, in order to get something meaningful on the screen you need to designate one of the VS output variables as 'gl_Position'. This is a 4-vector that contains the homogenuous coordinates of the vertex. The XYZ components of that vector are divided by the W component (a process known as perspective divide and is dealt with in the tutorial dedicated to that subject) and any component which goes outside the normalized box ([-1,1]) gets clipped. The result is transformed to screen space coordinates and then the triangle (or any other supported primitive type) is rendered to screen by the rasterizer.

The rasterizer performs interpolation between the three triangle vertices (either going line by line or any other technique) and "visits" each pixel inside the triangle by executing the fragment shader. The fragment shader is expected to return a pixel color which the rasterizer places in the color buffer for display (after passing a few additional tests like depth test, etc). Any other variable which comes out of the vertex shader does not go through the steps above. If the fragment shader does not explicitly requests that variable (and you can mix and match multiple fragment shaders with the same vertex shader) then a common driver optimization will be to drop any instructions in the VS that only affect this variable (for that particular shader program that combines this VS and FS pair). However, if the FS does use that variable the rasterizer interpolates it during rasterization and each FS invocation is provided a the interpolated value that matches that specific location. This usually means that the values for pixels that are right next to each other will be a bit different (though as the triangle becomes further and further away from the camera that becomes less likely).

Two very common variables that often rely on this interpolation are the triangle normal and texture coordinates. The vertex normal is usually calculated as the average between the triangle normals of all triangles that include that vertex. If that object is not completely flat this usually means that the three vertex normals of each triangle will be different from each other. In that case we rely on interpolation to calculate the specific normal at each pixel. That normal is used in lighting calculations in order to generate a more believable representation of lighting effects. The case for texture coordinates is similar. These coordinates are part of the model and are specified per vertex. In order to "cover" the triangle with a texture you need to perform the sample operation for each pixel and specify the correct texture coordinates for that pixel. These coordinates are the result of the interpolation.

In this tutorial we will see the effects of interpolation by interpolating different colors across the triangle face. Since I'm lazy we will generate the color in the VS. A more tedious approach is to supply it from the vertex buffer. Usually you don't supply colors from the vertex buffer. You supply texture coordinates and sample a color from a texture. That color is later processed by the lighting calculations.

Source walkthru

out vec4 Color;

Parameters passed between pipeline stages must be declared using the 'out' reserved word and in the global scope of the shader. The color is a 4-vector since the XYZ components carry the RGB values (respectively) and W is the alpha value (pixel transparency).

Color = vec4(clamp(Position, 0.0, 1.0), 1.0);

Color in the graphics pipeline is usually represented using a floating point value in the range [0.0, 1.0]. That value is later mapped to the integer 0 to 255 for each color channel (totaling in 16M colors). We set the vertex color value as a function of the vertex position. First we use the built-in function clamp() to make sure the values do not go outside of the 0.0-1.0 range. The reason is that the lower left vertex of the triangle is located at -1,-1. If we take that value as-is it will be interpolated by the rasterizer and until both X and Y pass zero we will not see anything because every value which is less than or equal to zero will be rendered as black. This means that half of the edge on each direction will be black before the color pass zero and become something meaningful. By clamping we make only the far bottom left black but as we get further away the color quickly becomes more bright. Try playing with the clamp function - remove it all together or change its parameters to see the effect.

The result of the clamp function does not go directly to the output variable since it is a 4-vector while the position is a 3-vector (clamp does not change the number of components, only their values). From the point of view of GLSL there is no default conversion here and we have to make this explicit. We do this using the notation 'vec4(vec3, W)' which creates a 4-vector by concatenating a 3-vector with the supplied W value. In our case we use 1.0 because this goes into the alpha part of the color and we want the pixel to be completely opaque.

in vec4 Color;

The opposite side of the output color in the VS is the input color in the FS. This variable undergoes interpolation by the rasterizer so every FS is (probably) executed with a different color.

FragColor = Color;

We use the interpolated color as the fragment color with no further changes and this completes this tutorial.

Next tutorial


Tutorial 10 - Indexed Draws

Tutorial 10:

Indexed Draws

Background

OpenGL provides several draw functions. glDrawArrays() that we have been using until now falls under the category of "ordered draws". This means that the vertex buffer is scanned from the specified offset and every X (1 for points, 2 for lines, etc) vertices a primitive is emitted. This is very simple to use but the downside is if a vertex is part of several primitives then it must be present several times in the vertex buffer. That is, there is no concept of sharing. Sharing is provided by the draw functions that belong to the "indexed draws" category. Here in addition to the vertex buffer there is also an index buffer that contains indices into the vertex buffer. Scanning the index buffer is similar to scanning the vertex buffer - every X indices a primitive is emitted. To exercise sharing you simply repeat the index of the shared vertex several times. Sharing is very important for memory efficiency because most objects are represented by some closed mesh of triangles and most vertices participate in more than one triangle.

Here is an example of an ordered draw:

If we are rendering triangles the GPU will generate the following set: V0/1/2, V3/4/5, V6/7/8, etc.

Here is an example of an indexed draw:

In this case the GPU will generate the following triangles: V2/0/1, V5/2/4, V6/5/7, etc.

Using index draws in OpenGL requires generating and populating an index buffer. That buffer must be bound in addition to the vertex buffer before the draw call and a different API must be used.

Source walkthru

GLuint IBO;

We added another buffer object handle for the index buffer.

Vertices[0] = Vector3f(-1.0f, -1.0f, 0.0f);
Vertices[1] = Vector3f(0.0f, -1.0f, 1.0f);
Vertices[2] = Vector3f(1.0f, -1.0f, 0.0f);
Vertices[3] = Vector3f(0.0f, 1.0f, 0.0f);

To demonstrate vertex sharing we need a mesh which is a bit more complex. Many tutorials use the famous spinning cube for that. This requires 8 vertices and 12 triangles. Since I'm lazy I use the spinning pyramid instead. This requires only 4 vertices and 4 triangles and is much easier to generate manually...

When looking at these vertices from the top (along the Y axis) we get the following layout:

unsigned int Indices[] = { 0, 3, 1,
                           1, 3, 2,
                           2, 3, 0,
                           0, 1, 2 };

The index buffer is populated using an array of indices. The indices match the location of the vertices in the vertex buffer. When looking at the array and the diagram above you can see that the last triangle is the pyramid base while the other three make up its faces. The pyramid is not symmetric but is very easy to specify.

glGenBuffers(1, &IBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);

We create and then populate the index buffer using the array of indices. You can see that the only difference in creating vertex and index buffers is that vertex buffers take GL_ARRAY_BUFFER as the buffer type while index buffers take GL_ELEMENT_ARRAY_BUFFER.

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);

In addition to binding the vertex buffer we must also bind the index buffer prior to drawing. Again, we use the GL_ELEMENT_ARRAY_BUFFER as the buffer type.

glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_INT, 0);

We use glDrawElements instead of glDrawArrays. The first parameter is the primitive type to render (same as glDrawArrays). The second parameter is the number of indices in the index buffer to use for primitive generation. The next parameter is the type of each index. The GPU must be told the size of each individual index else it will not know how to parse the buffer. Possible values here are GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, GL_UNSIGNED_INT. If the index range is small you want the smaller types that are more space efficient and if the index range is large you want the larger types. The final parameter tells the GPU the offset in bytes from the start of the index buffer to the location of the first index to scan. This is useful when the same index buffer contains the indices of multiple objects. By specifying the offset and count you can tell the GPU which object to render. In our case we want to start at the beginning so we specify zero. Note that the type of the last parameter is GLvoid* so if you specify anything other than zero you need to cast it to that type.

Next tutorial


Tutorial 11 - Concatenating Transformations

Tutorial 11:

Concatenating Transformations

Background

In the last few tutorials we have developed several transformations that give us the flexibility of moving an object anywhere in the 3D world. We still have a couple more to learn (camera control and perspective projection) but as you probably already guessed, a combination of the transformation is required. In most cases you will want to scale the object to fit your 3D world, rotate it into the required orientation, move it somewhere, etc. Up till now we have been exercising a single transformation at a time. In order to perform the above series of transformations we need to multiply the first transformation matrix by the vertex position and then multiple the next transformation by the result of the previous multiplication. This goes on until all the transformation matrices have been applied on the vertex. One trivial way to do that is to supply each and every transformation matrix to the shader and let it do all the multiplications. This, however, is very inefficient since the matrices are the same for all vertices and only vertex position changes. Luckily, linear algebra provides a set of rules that make our life easier. It tells us that given a set of matrices M0...Mn and a vector V the following holds true:

Mn * Mn-1 * ... * M0 * V = (Mn* Mn-1 * ... * M0) * V

So if you calculate:

N = Mn * Mn-1 * ... * M0

Then:

Mn * Mn-1 * ... * M0 * V = N * V

This means that we can calculate N once and then send it to the shader as a uniform variable where it will be multiplied in each vertex. This will require the GPU one matrix/vector multiplication per vertex.

How do you order the matrices when generating N? The first thing you need to remember is that the vector is initially multiplied by the matrix on the far right of the series (in our case - M0). Then the vector is transformed by each matrix as we travel from the right hand side to the left hand side. In 3D graphics you usually want to scale the object first, then rotate it, then translate it, then apply camera transformation and finally project it to 2D. Let's see what happens when you rotate first and then translate:

Now see what happens when you translate first and then rotate:

As you can see, it is very diffcult to set the object position in the world when you translate it first because if you move it away from the origin and then rotate it goes around the origin which actually means that you translate it again. This second translation is something you want to avoid. By rotating first and then translate you disconnect the dependency between the two operations. This is why it is always best to model around the origin as symmetrically as possible. That way when you later scale or rotate there is no side effect and the rotated or scaled object remains symmetrical as before.

Now that we are starting to handle more than one transformation in the demos we have to drop the habit of updating the matrix directly in the render function. This method doesn't scale well and is prone to errors. Instead, the pipeline class is introduced. This class hides the fine details of matrix manipulation under a simple API to change the translation, rotation, etc. After setting all the parameters inside it you simply extract the final matrix that combines all the transformation. This matrix can be fed directly into the shader.

Source walkthru

#define ToRadian(x) ((x) * M_PI / 180.0f)
#define ToDegree(x) ((x) * 180.0f / M_PI)

We are starting to use the actual values of angles in this tutorials. As it happens, the trigonometric functions of the standard C library take radian as a parameter. The above macros take the angle either in radians or degrees and convert to the other notation.

inline Matrix4f operator*(const Matrix4f& Right) const
{
    Matrix4f Ret;
    for (unsigned int i = 0 ; i < 4 ; i++) {
       for (unsigned int j = 0 ; j < 4 ; j++) {
           Ret.m[i][j] = m[i][0] * Right.m[0][j] +
                         m[i][1] * Right.m[1][j] +
                         m[i][2] * Right.m[2][j] +
                         m[i][3] * Right.m[3][j];
       }
    }

    return Ret;
}

This handy operator of the matrix class handles matrix multiplication. As you can see, each entry in the resulting matrix is defined as the dot product of its line in the left matrix with the column in the right matrix. This operator is key in the implementation of the pipeline class.

class Pipeline
{
    public:
       Pipeline() { ... }
       void Scale(float ScaleX, float ScaleY, float ScaleZ) { ... }
       void WorldPos(float x, float y, float z) { ... }
       void Rotate(float RotateX, float RotateY, float RotateZ) { ... }
       const Matrix4f* GetTrans();
    private:
       Vector3f m_scale;
       Vector3f m_worldPos;
       Vector3f m_rotateInfo;
       Matrix4f m_transformation;
};

The pipeline class abstracts the details of getting all the transformation required for one single object combined. There are currently 3 private member vectors that store the scaling, position in world space and rotation for each axis. In addition there are APIs to set them and a function to get the matrix that represent the sum of all these transformations.

const Matrix4f* Pipeline::GetTrans()
{
    Matrix4f ScaleTrans, RotateTrans, TranslationTrans;
    InitScaleTransform(ScaleTrans);
    InitRotateTransform(RotateTrans);
    InitTranslationTransform(TranslationTrans);
    m_transformation = TranslationTrans * RotateTrans * ScaleTrans;
    return &m_transformation;
}

This function initializes three seperate matrices as the transformations that match the current configuration. It multiplies them one by one and returns the final product. Note that the order is hard coded and follows the description above. If you need some flexibility there you can use a bitmask that specifies the order. Also note that it always stores the final transformation as a member. You can try optimizing this function by checking a dirty flag and returning the stored martix in the case that there was no change in configuration since the last time this function was called.

This function uses private methods to generate the different transformations according to what we've learned in the last few tutorials. In the next tutorials this class will be extended to handle camera control and perspective projection.

Pipeline p;
p.Scale(sinf(Scale * 0.1f), sinf(Scale * 0.1f), sinf(Scale * 0.1f));
p.WorldPos(sinf(Scale), 0.0f, 0.0f);
p.Rotate(sinf(Scale) * 90.0f, sinf(Scale) * 90.0f, sinf(Scale) * 90.0f);
glUniformMatrix4fv(gWorldLocation, 1, GL_TRUE, (const GLfloat*)p.GetTrans());

These are the changes to the render function. We allocate a pipeline object, configure it and send the resulting transformation down to the shader. Play with the parameters and see their effect on the final image.

Next tutorial


Tutorial 12 - Perspective Projection

Tutorial 12:

Perspective Projection

Background

We have finally reached the item that represents 3D graphics best - the projection from the 3D world on a 2D plane while maintaining the appearance of depth. A good example is a picture of a road or railway-tracks that seem to converge down to a single point far away in the horizon.

We are going to generate the transformation that satisfies the above requirement and we have an additional requirement we want to "piggyback" on it which is to make life easier for the clipper by representing the projected coordinates in a normalized space of -1 to +1. This means the clipper can do its work without having knowledge of the screen dimension and the location of the near and far planes.

The perspective projection tranformation will require us to supply 4 parameters:

  1. The aspect ratio - the ratio between the width and the height of the rectangular area which will be the target of projection.
  2. The vertical field of view: the vertical angle of the camera through which we are looking at the world.
  3. The location of the near Z plane. This allows us to clip objects that are too close to the camera.
  4. The location of the far Z plane. This allows us to clip objects that are too distant from the camera.

The aspect ratio is required since we are going to represent all coordinates in a normalized space whose width is equal to its height. Since this is rarely the case with the screen where the width is usually larger than the height it will need to be represented in the transformation by somehow "condensing" the points on the horizontal line vs. the vertical line. This will enable us to squeeze in more coordinates in terms of the X component in the normalized space which will satisfy the requirement of "seeing" more on the width than on the height in the final image.

The vertical field of view allows us to zoom in and out on the world. Consider the following example. In the picture on the left hand side the angle is wider which makes objects smaller while in the picture on the right hand side the angle is smaller which makes the same object appear larger. Note that this has an effect on the location of the camera which is a bit counter intuitive. On the left (where we zoom in with a smaller field of view) the camera needs to be placed further away and on the right it is closer to the projection plane. However, remember that this has no real effect since the projected coordinates are mapped to the screen and the location of the camera plays no part.

We start by determining the distance of the projection plane from the camera. The projection plane is a plane which is parallel to the XY plane. Obviously, not the entire plane is visible because this is too much. We can only see stuff in a rectangular area (called the projection window) which has the same proportions of our screen. The apsect ratio is calculated as follows:

ar = screen width / screen height

Let us conviniently determine the height of the projection window as 2 which means the width is exactly twice the aspect ratio (see the above equation). If we place the camera in the origin and look at the area from behind the camera's back we will see the following:

Anything outside this rectangle is going to be clipped away and we already see that coordinates inside it will have their Y component in the required range. The X component is currently a bit bigger but we will provide a fix later on.

Now let's take a look at this "from the side" (looking down at the YZ plane):

We find the distance from the camera to the projection plane using the vertical field of view (denoted by the angle alpha):

The next step is to calculate the projected coordinates of X and Y. Consider the next image (again looking down at the YZ plane).

We have a point in the 3D world with the coordinates (x,y,z). We want to find (xp,yp) that represent the projected coordinates on the projection plane. Since the X component is out of scope in this diagram (it is pointing in and out of the page) we'll start with Y. According to the rule of similar triangles we can determine the following:

In the same manner for the X component:

Since our projection window is 2*ar (width) by 2 (height) in size we know that a point in the 3D world is inside the window if it is projected to a point whose projected X component is between -ar and +ar and the projected Y component is between -1 and +1. So on the Y component we are normalized but on the X component we are not. We can get Xp normalized as well by further dividing it by the aspect ratio. This means that a point whose projected X component was +ar is now +1 which places it on the right hand side of the normalized box. If its projected X component was +0.5 and the aspect ratio was 1.333 (which is what we get on an 1024x768 screen) the new projected X component is 0.375. To summarize, the division by the aspect ratio has the effect of condensing the points on the X axis.

We have reached the following projection equations for the X and Y components:

Before completing the full process let's try to see how the projection matrix would look like at this point. This means representing the above using a matrix. Now we run into a problem. In both equations we need to divide X and Y by Z which is part of the vector that represents position. However, the value of Z changes from one vertex to the next so you cannot place it into one matrix that projects all vertices. To understand this better think about the top row vector of the matrix (a, b, c, d). We need to select the values of the vector such that the following will hold true:

This is the dot product operation between the top row vector of the matrix with the vertex position which yields the final X component. We can select 'b' and 'd' to be zero but we cannot find an 'a' and 'c' that can be plugged into the left hand side and provide the results on the right hand side. The solution adopted by OpenGL is to seperate the transformation into two parts: a multiplication by a projection matrix followed by a division by the Z value as an independant step. The matrix is provided by the application and the shader must include the multiplication of the position by it. The division by the Z is hard wired into the GPU and takes place in the rasterizer (somewhere between the vertex shader and the fragment shader). How does the GPU knows which vertex shader output to divide by its Z value? simple - the built-in variable gl_Position is designated for that job. Now we only need to find a matrix that represents the projection equations of X & Y above.

After multiplying by that matrix the GPU can divide by Z automatically for us and we get the result we want. But here's another complexity: if we multiply the matrix by the vertex position and then divide it by Z we literally loose the Z value because it becomes 1 for all vertices. The original Z value must be saved in order to perform the depth test later on. So the trick is to copy the original Z value into the W component of the resulting vector and divide only XYZ by W instead of Z. W maintains the original Z which can be used for depth test. The automatic step of dividing gl_Position by its W is called 'perspective divide'.

We can now generate an intermediate matrix that represents the above two equations as well as the copying of the Z into the W component:

As I said earlier, we want to include the normalization of the Z value as well to make it easier for the clipper to work without knowing the near and far Z values. However, the matrix above turns Z into zero. Knowing that after transforming the vector the system will automatically do perspective divide we need to select the values of the third row of the matrix such that following the division any Z value within viewing range (i.e. NearZ <= Z <= FarZ) will be mapped to the [-1,1] range. Such a mapping operation is composed of two parts. First we scale down the range [NearZ, FarZ] down to any range with a width of 2. Then we move (or translate) the range such that it will start at -1. Scaling the Z value and then translating it is represented by the general function:

But following perspective divide the right hand side of the function becomes:

Now we need to find the values of A and B that will perform the maping to [-1,1]. We know that when Z equals NearZ the result must be -1 and that when Z equals FarZ the result must be 1. Therefore we can write:

Now we need to select the third row of the matrix as the vector (a b c d) that will satisfy:

We can immediately set 'a' and 'b' to be zero because we don't want X and Y to have any effect on the transformation of Z. Then our A value can become 'c' and the B value can become 'd' (since W is known to be 1).

Therefore, the final transformation matrix is:

After multiplying the vertex position by the projection matrix the coordinates are said to be in Clip Space and after performing the perspective divide the coordinates are in NDC Space (Normalized Device Coordinates).

The path that we have taken in this series of tutorials should now become clear. Without doing any projection we can simply output vertices from the VS whose XYZ components (of the position vector) are within the range of [-1,+1]. This will make sure they end up somewhere in the screen. By making sure that W is always 1 we basically prevent perspective divide from having any effect. After that the coordinates are transformed to screen space and we are done. When using the projection matrix the perspective divide step becomes an integral part of the 3D to 2D projection.

Source walkthru

void Pipeline::InitPerspectiveProj(Matrix4f& m) const>
{
    const float ar = m_persProj.Width / m_persProj.Height;
    const float zNear = m_persProj.zNear;
    const float zFar = m_persProj.zFar;
    const float zRange = zNear - zFar;
    const float tanHalfFOV = tanf(ToRadian(m_persProj.FOV / 2.0));

    m.m[0][0] = 1.0f / (tanHalfFOV * ar);
    m.m[0][1] = 0.0f;
    m.m[0][2] = 0.0f;
    m.m[0][3] = 0.0f;

    m.m[1][0] = 0.0f;
    m.m[1][1] = 1.0f / tanHalfFOV;
    m.m[1][2] = 0.0f;
    m.m[1][3] = 0.0f;

    m.m[2][0] = 0.0f;
    m.m[2][1] = 0.0f;
    m.m[2][2] = (-zNear - zFar) / zRange;
    m.m[2][3] = 2.0f * zFar * zNear / zRange;

    m.m[3][0] = 0.0f;
    m.m[3][1] = 0.0f;
    m.m[3][2] = 1.0f;
    m.m[3][3] = 0.0f;
}

A structure called m_persProj was added to the Pipeline class that holds the perspective projection configurations. The method above generates the matrix that we have developed in the background section.

m_transformation = PersProjTrans * TranslationTrans * RotateTrans * ScaleTrans;

We add the perspective projection matrix as the first element in the multiplication that generates the complete transformation. Remember that since the position vector is multiplied on the right hand side that matrix is actually the last. First we scale, then rotate, translate and finally project.

p.SetPerspectiveProj(30.0f, WINDOW_WIDTH, WINDOW_HEIGHT, 1.0f, 1000.0f);

In the render function we set the projection parameters. Play with these and see their effect.

Next tutorial


Tutorial 13 - Camera Space

Tutorial 13:

Camera Space

Background

In the last several tutorials we saw two types of transformations. The first type were transformations that change the position (translation), orientation (rotation) or size (scaling) of an object. These transformations allow us to place an object anywhere within the 3D world. The second type was the perpsective projection transformation that takes the position of a vertex in the 3D world and projects it into a 2D world (i.e. a plane). Once the coordinates are in 2D it is very easy to map them to screen space coordinates. These coordinates are used to actually rasterize the primitives from which the object is composed (be it points, lines or triangles).

The missing piece of the puzzle is the location of the camera. In all the previous tutorials we implicitly assumed that the camera is convenietly located at the origin of the 3D space. In reality, we want to have the freedom to place the camera anywhere in the world and project the vertices into some 2D plane infront of it. This will reflect the correct relation between the camera and the object on screen.

In the following picture we see the camera positioned somewhere with its back to us. There is a virtual 2D plane before it and the ball is projected into the plane. The camera is tilted somewhat so the plane is tilted accordingly. Since the view from the camera is limited by its field of view angle the visible part of the (endless) 2D plane is the rectangle. Anything outside it is clipped out. Getting the rectangle onto the screen is our target.

Theoretically, it is possible to generate the transformations that would take an object in the 3D world and project it onto a 2D plane lying infront of a camera positioned in an arbitrary location in the world. However, that math is much more complex than what we have previously seen. It is much more simple to do it when the camera is stationed at the origin of the 3D world and looking down the Z axe. For example, an object is positioned at (0,0,5) and the camera is at (0,0,1) and looking down the Z axe (i.e. directly at the object). If we move both the camera and the object by one unit towards the origin then the relative distance and orientation (in terms of the direction of the camera) remains the same only now the camera is positioned at the origin. Moving all the objects in the scene in the same way will allow us to render the scene correctly using the methods that we have already learned.

The example above was simple because the camera was already looking down the Z axe and was in general aligned to the axes of the coordinate system. But what happens if the camera is looking somewhere else? Take a look at the following picture. For simplicity, this is a 2D coordinate system and we are looking at the camera from the top.

The camera was originally looking down the Z axe but then turned 45 degrees clockwise. As you can see, the camera defines its own coordinate system which may be identical to the world (upper picture) and may be different (lower picture). So there are actually two coordinate systems simulatenously. There is the 'world coordinate system' in which the objects are specified and there is a camera coordinate system which is aligned with the "axes" of the camera (target, up and right). These two coordinate systems are known as 'world space' and 'camera/view space'.

The green ball is located on (0,y,z) in world space. In camera space it is located somewhere in the upper left quadrant of the coordinate system (i.e. it has a negative X and a positive Z). We need to find out the location of the ball in camera space. Then we can simply forget all about the world space and use only the camera space. In camera space the camera is located at the origin and looking down the Z axe. Objects are specified relative to the camera and can be rendered using the tools we have learned.

Saying that the camera turned 45 degrees clockwise is the same as saying that the green ball turned 45 degrees counter-clockwise. The movement of the objects is always opposite to the movement of the camera. So in general, we need to add two new transformations and plug them into the transformation pipeline that we already have. We need to move the objects in a way that will keep their distance from the camera the same while getting the camera to the origin and we need to turn the objects in the opposite direction from the direction the camera is turning to.

Moving the camera is very simple. If the camera is located at (x,y,z), then the translation transformation is (-x, -y, -z). The reason is straightforward - the camera was placed in the world using a translation transformation based on the vector (x,y,z) so to move it back to the origin we need a translation transformation based on the opposite of that vector. This is how the transformation matrix looks like:

The next step is to turn the camera toward some target specified in world space coordinates. We want to find out the location of the vertices in the new coordinate system that the camera defines. So the actual question is: how do we transform from one coordinate system to another?

Take another look at the picture above. We can say that the world coordinate system is defined by the three linearly independent unit vectors (1,0,0), (0,1,0) and (0,0,1). Linearly independent means that we cannot find x,y and z that are not all zeros such that x*(1,0,0) + y(0,1,0) + z*(0,0,1) = (0,0,0). In more geometrical terms this means that any pair of vectors out of these three defines a plane which is perpendicular to the third vector (plane XY is perpedicular to Z axe, etc). It is easy to see that the camera coordinate system is defined by the vectors (1,0,-1), (0,1,0), (1,0,1). After normalizing these vectors we get (0.7071,0,-0.7071), (0,1,0) and (0.7071,0,0.7071).

The following image shows how the location of a vector is specified in two different coordinate systems:

We know how to get the unit vectors that represent the camera axes in world space and we know the location of the vector in world space (x,y,z). What we are looking for is the vector (x',y',z'). We now take advantage of an attribute of the dot product operation known as 'scalar projection'. Scalar projection is the result of a dot product between an arbitrary vector A and a unit vector B and results in the magnitude of A in the direction of B. In other words, the projection of vector A on vector B. In the example above if we do a dot product between (x,y,z) and the unit vector that represents the camera X axe we get x'. In the same manner we can get y' and z'. (x',y',z') is the location of (x,y,z) in camera space.

Let's see how to turn this knowledge into a complete solution for orienting the camera. The solution is called 'UVN camera' and is just one of many systems to specify the orientation of a camera. The idea is that the camera is defined by the following vectors:

  1. N - The vector from the camera to its target. Also known as the 'look at' vector in some 3D literature. This vector corresponds to the Z axe.
  2. V - When standing upright this is the vector from your head to the sky. If you are writing a flight simulator and the plane is reversed that vector may very well point to the ground. This vector corresponds to the Y axe.
  3. U - This vector points from the camera to its "right" side". It corresponds to the X axe.

In order to transform a position in world space to the camera space defined by the UVN vectors we need to perform a dot product operation between the position and the UVN vectors. A matrix represents this best:

In the code that accompanies this tutorial you will notice that the shader global variable 'gWorld' has been renamed 'gWVP'. This change reflects the way the series of transformations is known in many textbooks. WVP stands for - World-View-Projection.

Source walkthru

In this tutorial I decided to make a small design change and moved the low level matrix manipulation code from the Pipeline class to the Matrix4f class. The Pipeline class now tells Matrix4f to initialize itself in different ways and concatenates several matrices to create the final transformation.

(pipeline.h:85)

struct {
    Vector3f Pos;
    Vector3f Target;
    Vector3f Up;
} m_camera;

The Pipeline class has a few new members to store the parameters of the camera. Note that the axe that points from the camera to it's "right" is missing (the 'U' axe). It is calculated on the fly using a cross product between the target and up axes. In addition there is a new function called SetCamera to pass these values.

(math3d.h:21)

Vector3f Vector3f::Cross(const Vector3f& v) const
{
    const float _x = y * v.z - z * v.y;
    const float _y = z * v.x - x * v.z;
    const float _z = x * v.y - y * v.x;

    return Vector3f(_x, _y, _z);
}

The Vector3f has a new method to calculate the cross product between two Vector3f objects. A cross product between two vectors produces a vector which is perpendicular to the plane defined by the vectors. This becomes more intuitive when you remember that vectors have a direction and magnitude but no position. All vectors with the same direction and magnitude are considered equal, regardless where they "start". So you might as well make both vectors start at the origin. This means that you can create a triangle that has one vertex at the origin and two vertices at the tip of the vectors. The triangle defines a plane and the cross product is a vector which is perpendicular to that plane. Read more on the cross product in Wikipedia.

(math3d.h:30)

Vector3f& Vector3f::Normalize()
{
    const float Length = sqrtf(x * x + y * y + z * z);

    x /= Length;
    y /= Length;
    z /= Length;

    return *this;
}

To generate the UVN matrix we will need to make the vectors unit length. This operation is formally known as 'vector normalization' is executed by dividing each vector component by the vector length. More on this in Mathworld.

(math3d.cpp:84)

void Matrix4f::InitCameraTransform(const Vector3f& Target, const Vector3f& Up)
{
    Vector3f N = Target;
    N.Normalize();
    Vector3f U = Up;
    U.Normalize();
    U = U.Cross(Target);
    Vector3f V = N.Cross(U);

    m[0][0] = U.x; m[0][1] = U.y; m[0][2] = U.z; m[0][3] = 0.0f;
    m[1][0] = V.x; m[1][1] = V.y; m[1][2] = V.z; m[1][3] = 0.0f;
    m[2][0] = N.x; m[2][1] = N.y; m[2][2] = N.z; m[2][3] = 0.0f;
    m[3][0] = 0.0f; m[3][1] = 0.0f; m[3][2] = 0.0f; m[3][3] = 1.0f;
}

This function generates the camera transformation matrix that will be used later by the pipeline class. The U,V and N vectors are calculated and set into the matrix in rows. Since the vertex position is going to be multiplied on the right side (as a column vector) this means a dot product between U,V and N and the position. This generates the 3 scalar projections magnitude values that become the XYZ values of the position in screen space.

The function is supplied with the target and up vectors. The "right" vector is calculated as the cross product between them. Note that we do not trust the caller to pass unit length vectors so we normalize the vectors anyway. After generating the U vector we recalculate the up vector as a cross product between the target and the right vector. The reason will become clearer in the future when we will start moving the camera. It is simpler to update only the target vector and leave the up vector untouched. However, this means that the angle between the target and the up vectors will not be 90 degrees which makes this an invalid coordinate system. By calculating the right vector as a cross product of the target and the up vectors and then recalculating the up vector as a cross product between the target and the right we get a coordinate system with 90 degrees between each pair of axes.

(pipeline.cpp:22)

const Matrix4f* Pipeline::GetTrans()
{
    Matrix4f ScaleTrans, RotateTrans, TranslationTrans, CameraTranslationTrans, CameraRotateTrans, PersProjTrans;

    ScaleTrans.InitScaleTransform(m_scale.x, m_scale.y, m_scale.z);
    RotateTrans.InitRotateTransform(m_rotateInfo.x, m_rotateInfo.y, m_rotateInfo.z);
    TranslationTrans.InitTranslationTransform(m_worldPos.x, m_worldPos.y, m_worldPos.z);
    CameraTranslationTrans.InitTranslationTransform(-m_camera.Pos.x, -m_camera.Pos.y, -m_camera.Pos.z);
    CameraRotateTrans.InitCameraTransform(m_camera.Target, m_camera.Up);
    PersProjTrans.InitPersProjTransform(m_persProj.FOV, m_persProj.Width, m_persProj.Height, m_persProj.zNear, m_persProj.zFar);

    m_transformation = PersProjTrans * CameraRotateTrans * CameraTranslationTrans * TranslationTrans * RotateTrans * ScaleTrans;
    return &m_transformation;
}

Let's update the function that generates the complete transformation matrix of an object. It is now becoming quite complex with two new matrices that provide the camera part. After completing the world transformation (the combined scaling, rotation and translation of the object) we start the camera transformation by "moving" the camera to the origin. This is done by a translation using the negative vector of the camera position. So if the camera is positioned at (1,2,3) we need to move the object by (-1,-2,-3) in order to get the camera back to the origin. After that we generate the camera rotation matrix based on the camera target and up vectors. This completes the camera part. Finally, we project the coordinates.

(tutorial13.cpp:76)

Vector3f CameraPos(1.0f, 1.0f, -3.0f);
Vector3f CameraTarget(0.45f, 0.0f, 1.0f);
Vector3f CameraUp(0.0f, 1.0f, 0.0f);
p.SetCamera(CameraPos, CameraTarget, CameraUp);

We use the new capability in the main render loop. To place the camera we step back from the origin along the negative Z axe, then move to the right and straight up. The camera is looking along the positive Z axe and a bit to the right from the origin. The up vector is simply the positive Y axe. We set all this into the Pipeline object the Pipeline class takes care of the rest.

Next tutorial


Tutorial 14 - Camera Control - Part 1

Tutorial 14:

Camera Control - Part 1

Background

In the previous tutorial we learned how to position the camera anywhere in the 3D world. The next logical step is to allow the user to control it. Movement will be unrestricted - the user will be able to move in all directions. Controlling the camera will be done using two input devices - the keyboard will control our position and the mouse will change our view target. This is very similar to what most first person shooters are doing. This tutorial will focus on the keyboard and the next one on the mouse.

We are going to support the four directional keys in the conventional manner. Remember that our camera transformation is defined by position, target vector and up vector. When we move using the keyboard we only change our position. We cannot tilt the camera or turn it so the target and up vectors are uneffected.

To control the keyboard we will use another GLUT API: glutSpecialFunc(). This function registers a callback that is triggered when a "special" key is clicked. The group of special keys include the function, directional and PAGE-UP/PAGE-DOWN/HOME/END/INSERT keys. If you want to trap a regular key (characters and digits) use glutKeyboardFunc().

Source walkthru

The camera functionality is encapsulated in the Camera class. This class stores the attributes of the camera and can change them based on movement events that it receives. The attributes are fetched by the pipeline class that generates the transformation matrix from them.

(Camera.h)

class Camera
{
public:
    Camera();
    Camera(const Vector3f& Pos, const Vector3f& Target, const Vector3f& Up);
    bool OnKeyboard(int Key);
    const Vector3f& GetPos() const
    const Vector3f& GetTarget() const
    const Vector3f& GetUp() const

private:
    Vector3f m_pos;
    Vector3f m_target;
    Vector3f m_up;
};

This is the declaration of the Camera class. It stores the three attributes that define the camera - position, target vector and up vector. Two constructors are available. The default one simply places the camera at the origin looking down the positive Z axe with an up vector that points to the "sky" (0,1,0). There is also an option to create a camera with specific attribute values. The OnKeyboard() function supplies keyboard events to the Camera class. It returns a boolean value which indicates whether the event was consumed by the class. If the key is relevant (one of the directional keys) the return value is true. If not - false. This way you can build a chain of clients that receive a keyboard event and stop after reaching the first client that actually does something with the specific event.

(Camera.cpp:42)

bool Camera::OnKeyboard(int Key)
{
    bool Ret = false;

    switch (Key) {

    case GLUT_KEY_UP:
    {
        m_pos += (m_target * StepSize);
        Ret = true;
    }
    break;

    case GLUT_KEY_DOWN:
    {
        m_pos -= (m_target * StepSize);
        Ret = true;
    }
    break;

    case GLUT_KEY_LEFT:
    {
        Vector3f Left = m_target.Cross(m_up);
        Left.Normalize();
        Left *= StepSize;
        m_pos += Left;
        Ret = true;
    }
    break;

    case GLUT_KEY_RIGHT:
    {
        Vector3f Right = m_up.Cross(m_target);
        Right.Normalize();
        Right *= StepSize;
        m_pos += Right;
        Ret = true;
    }
    break;
    }

    return Ret;
}

This function move the camera according to keyboard events. GLUT defines macros that correspond to the directional keys and this is what the switch statement is based on. Unfortunately, the type of these macros is a simple 'int' rather than an enum.

Forward and backward movements are the simplest. Since movement is always along the target vector we only need to add or substract the target vector from the position. The target vector itself remains unchanged. Note that before adding or substracting the target vector we scale it by a constant value called 'StepSize'. We do it for all directional keys. StepSize provides a central point to change the speed (in the future we may change this into a class attribute). To make StepSize consistent we make sure that we always multiply it by unit length vectors (i.e. we must make sure the target and up vectors are unit length).

Sideways movement is a bit more complex. It is defined as a movement along a vector which is perpendicular to the plane created by the target and up vectors. This plane divides the three-dimensional space into two parts and there are two vectors that are perpendicular to it and are opposite to one another. We can call one of them "left" and the other "right". They are generated using a cross product of the target and up vectors in the two possible combinations: target cross up and up cross target (cross product is a non commutative operation - changing the order of parameters can generate different result). After getting the left/right vector we normalize it, scale it by the StepSize and add it to the position (which moves it in the left/right direction). Again, the target and up vectors are uneffected.

Note that the operations in this function make use of a few new operators such as '+=' and '-=' that have been added to the Vector3f class.

(tutorial14.cpp:73)

static void SpecialKeyboardCB(int Key, int x, int y)
{
    GameCamera.OnKeyboard(Key);
}

static void InitializeGlutCallbacks()
{
    glutDisplayFunc(RenderSceneCB);
    glutIdleFunc(RenderSceneCB);
    glutSpecialFunc(SpecialKeyboardCB);
}

Here we register a new callback to handle the special keyboard events. The callback receives the key and the location of the mouse at the time of the key press. We ignore the mouse position and pass the event on to an instance of the camera class which was already allocated on the global section of the file.

(tutorial14.cpp:55)

p.SetCamera(GameCamera.GetPos(), GameCamera.GetTarget(), GameCamera.GetUp());

Previously we initialized the camera parameters in the Pipeline class using a hard coded vectors. Now these vectors are dropped and the camera attributes are fetched directly from the Camera class.

Next tutorial


Tutorial 15 - Camera Control - Part 2

Tutorial 15:

Camera Control - Part 2

Background

In this tutorial we complete the implementation of the camera by enabling direction control using the mouse. There are various levels of freedom which are associated with the design of the camera. We are going to enable the level of control you come to expect in a first person game (shooter or otherwise). This means we will be able to turn the camera 360 degrees (around the positive Y axis), which corresponds to turning your head left or right and completing a full circle with your body. In addition, we will be able to tilt the camera up and down to get a better view above or below. We won't be able to turn the camera up until we complete a full circle or tilt it in a way a plane fuselage tilts during a turn. These levels of freedom are in the domain of flight simulators which is out of scope for this tutorial. At any rate, we will have a camera that will allow us to conveniently explore the 3D world will are going to develop in the coming tutorials.

The following world war 2 anti aircraft gun demonstrates the kind of camera we are going to build:

The gun has two control axis:

  1. It can turn 360 degrees around the vector (0,1,0). This angle is called the 'horizontal angle' and the vector is the 'vertical axis'.
  2. It can tilt up and down around a vector which is parallel to the ground. This movement is somewhat limited and the gun cannot complete a full circle. This angle is called the 'vertical angle' and the vector is the 'horizontal axis'. Note that while the vertical axis is constant (0,1,0) the horizontal axis turns around with the gun and is always perpendicular to the target of the gun. This is a key point to understand in order to get the math correctly.

The plan is to follow the movement of the mouse and change the horizontal angle when the mouse moves left and right and the vertical angle when the mouse moves up and down. Given these two angles we want to calculate the target and up vectors.

Turning the target vector by the horizontal angle is pretty straightforward. Using basic trigonometry we can see that the Z component of the target vector is the sine of the horizontal vector and the X component is the cosine of the horizontal angle (at this stage the camera looks straight ahead so the Y is zero). Revisit tutorial 7 to see a diagram of that.

Turning the target vector by the vertical angle is more complex since the horizontal axis turns along with the camera. The horizontal axis can be calculated using a cross product between the vertical axis and the target vector after is was turned by the horizontal angle, but turning around an arbitrary vector (lifting the gun up and down) can be tricky.

Luckily, we have an extremely useful mathematical tool for that problem - the quaternion. Quaternions were discovered in 1843 by Sir Willilam Rowan Hamilton, an irish mathematician, and are based on the complex number system. The quaternion 'Q' is defined as:

Where i, j and k are complex numbers and the following equation holds true:

In practice, we specify a quaternion as the 4-vector (x, y, z, w). The conjugate of quaternion 'Q' is defined as:

Normalizing a quaternion is the same as normalizing a vector. I'm going to describe the steps required to rotate a vector around an arbitrary vector using a quaternion. More details about the mathematical proof behind the steps can be found on the web.

The general function to calculate a quaternion 'W' that represents the rotated vector 'V' by the angle 'a' is:

Where Q is the rotation quaternion which is defined as:

After calculating 'W' the rotated vector is simply (W.x,W.y,W.z). An important point to note in the calculation of 'W' is that first we need to multiple 'Q' by 'V' which is a quaternion-by-vector multiplication that results in a quaternion, and then we need to do a quaternion-by-quaternion multiplication (the result of Q*V multiplied by the conjugate of 'Q'). The two types of multiplications are not the same. The file math_3d.cpp includes the implementations of these multiplication types.

We will need to keep the horizontal and vertical angles updated all the time as the user moves the mouse around the screen and we need to decide how to initialize them. The logical choice is to initialize them according to the target vector that is supplied to the constructor of the camera. Let's start with the horizontal angle. Take a look at the following diagram which looks down the XZ plane from above:

The target vector is (x,z) and we want to find the horizontal angle which is represented by the letter alpha (the Y component is relevant only for the vertical angle). Since the length of the radius of the circle is 1 it is very easy to see that the sine function of alpha is exactly z. Therefore, calculating the asine of z will provide alpha. Are we done? - not yet. Since z can be in the range [-1,1], the result of asine is -90 degrees to +90 degrees. But the range of the horizontal angle is 360 degrees. In addition, our quaternion does rotation clockwise. This means that when we rotate 90 degrees with the quaternion we end up with -1 on the Z axis which is opposite to the actual sine of 90 degrees (which is 1). IMHO, the easiest way to get this right is to always calculate the asine function using the positive value of Z and combine the result with the specific quarter of the circle where the vector is located. For example, when our target vector is (0,1) we calculate the asine of 1 which is 90 and substract it from 360. The result is 270. The asine range of 0 to 1 is 0 to 90 degrees. Combine that with the specific quarter of the circle and you get the final horizontal angle.

Calculating the vertical angle is a bit simpler. We are going to limit the range of movement to -90 degrees (equal to 270 degrees - looking straight up) to +90 degrees (looking straight down). This means we only need the negative value of the asine function of the Y component in the target vector. When Y equals 1 (looking straight up) the asine is 90 so we just need to reverse the sign. When Y equals -1 (looking straight down) the asine is -90 and reversing the sign gets us to 90. If you are confused check the diagram again and simply replace Z with Y and X with Z.

Source walkthru

(camera.cpp:38)

Camera::Camera(int WindowWidth, int WindowHeight, const Vector3f& Pos, const Vector3f& Target, const Vector3f& Up)
{
    m_windowWidth = WindowWidth;
    m_windowHeight = WindowHeight;
    m_pos = Pos;

    m_target = Target;
    m_target.Normalize();

    m_up = Up;
    m_up.Normalize();

    Init();
}

The constructor of the camera now gets the dimensions of the window. We need it in order to move the mouse to the center of the screen. In addition, note the call to Init() which sets up the internal camera attributes.

(camera.cpp:54)

void Camera::Init()
{
    Vector3f HTarget(m_target.x, 0.0, m_target.z);
    HTarget.Normalize();

    if (HTarget.z >= 0.0f)
    {
        if (HTarget.x >= 0.0f)
        {
            m_AngleH = 360.0f - ToDegree(asin(HTarget.z));
        }
        else
        {
            m_AngleH = 180.0f + ToDegree(asin(HTarget.z));
        }
    }
    else
    {
        if (HTarget.x >= 0.0f)
        {
            m_AngleH = ToDegree(asin(-HTarget.z));
        }
        else
        {
            m_AngleH = 90.0f + ToDegree(asin(-HTarget.z));
        }
    }

    m_AngleV = -ToDegree(asin(m_target.y));

    m_OnUpperEdge = false;
    m_OnLowerEdge = false;
    m_OnLeftEdge = false;
    m_OnRightEdge = false;
    m_mousePos.x = m_windowWidth / 2;
    m_mousePos.y = m_windowHeight / 2;

    glutWarpPointer(m_mousePos.x, m_mousePos.y);
}

In the Init() function we start by calculating the horizontal angle. We create a new target vector called HTarget (horizontal target) which is a projection of the original target vector on the XZ plane. Next we normalize it (since the math that was described earlier assumes a unit vector on the XZ plane). Then we check which quarter the target vector belongs to and calculate the final angle based on the positive value of the Z component. Next we calculate the vertical angle which is much simpler.

The camera has 4 new flags to indicate whether the mouse is positioned on one of the edges of the screen. We are going to implement an automatic turn in the corresponding direction when that happens. This will allow us to turn 360 degrees. We initialize the flags to FALSE since the mouse starts at the center of the screen. The next two lines of code calculate where the center of the screen is (based on the window dimension) and the new function glutWarpPointer actually moves the mouse. Starting with the mouse at the center of the screen makes life much simpler.

(camera.cpp:140)

void Camera::OnMouse(int x, int y)
{
    const int DeltaX = x - m_mousePos.x;
    const int DeltaY = y - m_mousePos.y;

    m_mousePos.x = x;
    m_mousePos.y = y;

    m_AngleH += (float)DeltaX / 20.0f;
    m_AngleV += (float)DeltaY / 20.0f;

    if (DeltaX == 0) {
        if (x <= MARGIN) {
            m_OnLeftEdge = true;
        }
        else if (x >= (m_windowWidth - MARGIN)) {
            m_OnRightEdge = true;
        }
    }
    else {
        m_OnLeftEdge = false;
        m_OnRightEdge = false;
    }

    if (DeltaY == 0) {
        if (y <= MARGIN) {
            m_OnUpperEdge = true;
        }
        else if (y >= (m_windowHeight - MARGIN)) {
            m_OnLowerEdge = true;
        }
    }
    else {
        m_OnUpperEdge = false;
        m_OnLowerEdge = false;
    }

    Update();
}

This function is used to notify the camera that the mouse moved. The parameters are the new screen position of the mouse. We start by calculating the delta from the previous location on both the X and Y axis. Next we store the new values for the next call to the function. We update the current horizontal and vertical angles by scaling down the deltas. I'm using a scaling value which works fine for me but on different computers you may want different scaling values. We are going to improve this in a future tutorial when we add the frame rate of the application as a factor.

The next set of tests update the 'm_On*Edge' flags according to the location of the mouse. There is a margin which is by default 10 pixels that triggers the "edge" behavior when the mouse gets close to one of the edges of the screen. Finally, we call Update() to recalculate the target and up vectors based on the new horizontal and vertical angles.

(camera.cpp:183)

void Camera::OnRender()
{
    bool ShouldUpdate = false;

    if (m_OnLeftEdge) {
        m_AngleH -= 0.1f;
        ShouldUpdate = true;
    }
    else if (m_OnRightEdge) {
        m_AngleH += 0.1f;
        ShouldUpdate = true;
    }

    if (m_OnUpperEdge) {
        if (m_AngleV > -90.0f) {
            m_AngleV -= 0.1f;
            ShouldUpdate = true;
        }
    }
    else if (m_OnLowerEdge) {
        if (m_AngleV < 90.0f) {
            m_AngleV += 0.1f;
            ShouldUpdate = true;
        }
    }

    if (ShouldUpdate) {
        Update();
    }
}

This function is called from the main render loop. We need it for cases where the mouse is located on one of the edges of the screen and is not moving. In this case there are no mouse events but we still want the camera to continuously move (until the mouse moves away from the edge). We check if one of the flags is set and update the corresponding angle accordingly. If there was a change in one of the angles we call Update() to update the target and up vectors. When the mouse moves aways from the screen we detect it in the mouse event handler and clear the flag. Note the way the vertical angle is limited between the ranges -90 degrees and +90 degrees. This is to prevent a full circle when looking up or down.

(camera.cpp:214)

void Camera::Update()
{
    const Vector3f Vaxis(0.0f, 1.0f, 0.0f);

    // Rotate the view vector by the horizontal angle around the vertical axis
    Vector3f View(1.0f, 0.0f, 0.0f);
    View.Rotate(m_AngleH, Vaxis);
    View.Normalize();

    // Rotate the view vector by the vertical angle around the horizontal axis
    Vector3f Haxis = Vaxis.Cross(View);
    Haxis.Normalize();
    View.Rotate(m_AngleV, Haxis);
    View.Normalize();

    m_target = View;
    m_target.Normalize();

    m_up = m_target.Cross(Haxis);
    m_up.Normalize();
}

This function updates the target and up vector according to the horizontal and vertical angles. We start with the view vector in a "reset" state. This means it is parallel to the gound (vertical angle is zero) and looking directly to the right (horizontal angle is zero - see the diagram above). We set the vertical axis to point straight up and then rotate the view vector around it by the horizontal angle. The result is a vector which points in the general direction of the intended target but not necessarily in the correct height (i.e. it is on the XZ plane). By doing a cross product of this vector with the vertical axis we get another vector on the XZ plane which is perpendicular to the plane created by the view vector and the vertical axis. This is our new horizontal axis and now it is time to rotate the vector up or down around it according to the vertical angle. The result is the final target vector and we set it into the corresponding member attribute. Now we must fix the up vector. For example, if the camera is looking up, the up vector must tilt back to compensate (it must be 90 degrees in respect to the target vector). This is similar to the way the back of your head tilts back when you look up to the sky. The new up vector is calculated by simply doing another cross product between the final target vector and the horizontal axis. If the vertical angle is still zero then the target vector remains on the XZ plane and the up vector remains (0,1,0). If the target vector is tilted up or down the up vector will tilt backward or forward, respectively.

(tutorial15.cpp:209)

glutGameModeString("1920x1200@32");
glutEnterGameMode();

These glut functions enable our application to run full screen in the so called high performance 'game mode'. It makes turning the camera 360 degrees simpler because all you need to do is pull the mouse towards one of the edges of the screen. Note the resolution and the bits per pixel that are configured via the game mode string. 32 bits per pixel provides the maximum number of colors for rendering.

(tutorial15.cpp:214)

pGameCamera = new Camera(WINDOW_WIDTH, WINDOW_HEIGHT);

The camera is now allocated dynamically at this location because it performs a glut call (glutWarpPointer). This call will fail if glut has not yet been initialized.

(tutorial15.cpp:99)

glutPassiveMotionFunc(PassiveMouseCB);
glutKeyboardFunc(KeyboardCB);

We register two new glut callback functions here. One is for the mouse and the other for regular keyboard clicks (the special keyboard callback traps directional and function keys). Passive motion means that the mouse moves without any of its buttons pressed.

(tutorial15.cpp:81)

static void KeyboardCB(unsigned char Key, int x, int y)
{
    switch (Key) {
        case 'q':
            exit(0);
    }
}

static void PassiveMouseCB(int x, int y)
{
    pGameCamera->OnMouse(x, y);
}

Now that we are using a full screen mode it is more difficult to exit the application. The keyboard callback traps the 'q' key and exits. The mouse callback simply transfers the location of the mouse to the camera.

(tutorial15.cpp:44)

static void RenderSceneCB()
{
    pGameCamera->OnRender();

Whenever we are in the main render loop we must notify the camera. This gives the camera a chance to turn when the mouse is not moving and is on one of the screen edges.

Next tutorial


Tutorial 16 - Basic Texture Mapping

Tutorial 16:

Basic Texture Mapping

Background

Texture mapping means applying any type of picture on one or more faces of a 3D model. The picture (a.k.a 'texture') can be anything but is often a pattern such as bricks, foliage, barren land, etc, that adds realism to the scene. For example, compare the following images:

To get texture mapping working you need to do three things: load a texture into OpenGL, supply texture coordinates with the vertices (to map the texture to them) and perform a sampling operation from the texture using the texture coordinates in order to get the pixel color. Since a triangle is scaled, rotated, translated and finally projected it can land on the screen in numerous ways and look very different depending on its orientation to the camera. What the GPU needs to do is make the texture follow the movement of the vertices of the triangle so that it will look real (if the texture appear to "swim" across the triangle it will not). To do this the developer supplies a set of coordinates known as 'texture coordinates' to each vertex. As the GPU rasterizes the triangle it interpolates the texture coordinates across the triangle face and in the fragment shader the developer maps these coordindates to the texture. This action is known as 'sampling' and the result of sampling is a texel (a pixel in a texture). The texel often contains a color which is used to paint the corresponding pixel on the screen. In the coming tutorials we will see that the texel can contain different types of data that can be used for multiple effects.

OpenGL supports several types of textures such as 1D, 2D, 3D, cube, etc that can be used for different techniques. Let's stick with 2D textures for now. A 2D texture has a width and height that can be any number within the limitations of the spec. Multiplying the width by height tells you the number of texels in the texture. How do you specify the texture coordinates of a vertex? - No, it is not the coordinate of a texel inside the texture. That would be too limiting because replacing a texture with one that has different width/height means that we will need to update the texture coordinates of all the vertices to match the new texture. The ideal scenario is to be able to change textures without changing texture coordinates. Therefore, texture coordinates are specified in 'texture space' which is simply the normalized range [0,1]. This means that the texture coordinate is usually a fraction and by multiplying that fraction with the corresponding width/height of a texture we get the coordinate of the texel in the texture. For example, if the texture coordinate is [0.5,0.1] and the texture has a width of 320 and a height of 200 the texel location will be (160,20) (0.5 * 320 = 160 and 0.1 * 200 = 20).

The usual convention is to use U and V as the axis of the texture space where U corresponds to X in the 2D cartesian coordinate system and V corresponds to Y. OpenGL treats the values of the UV axes as going from left to right on the U axis and down to up on the V axis. Take a look at the following image:

This image presents the texture space and you can see the origin of that space in the bottom left corner. U grows towards the right and V grows up. Now consider a triangle whose texture coordinates are specified in the following picture:

Let's say that we apply a texture such that when using these texture coordinates we get the picture of the small house in the location above. Now the triangle goes through various transformations and when the time comes to rasterize it it looks like this:

As you can see, the texture coordinates "stick" to the vertices as they are a core attributes and they don't change under the transformations. When interpolating the texture coordinates most pixels get the same texture coordinates as in the original picture (because they remained in the same place relative to the vertices) and since the triangle was flipped so is the texture which is applied to it. This means that as the original triangle is rotated, stretched or squeezed the texture diligently follows it. Note that there are also techniques that change the texture coordinates in order to move texture across the triangle face in some controlled way but for now our coordinates will stay the same.

Another important concept associated with texture mapping is 'filtering'. We've discussed how to map a texture coordinate to a texel. The texel location in the texture is always specified in whole numbers, but what happens if our texture coordinates (remember - this is a fraction between 0 and 1) maps us to a texel at (152.34,745.14)? The trivial answer is to round this down to (152,745). Well, this works and provides adequate results but in some cases won't look very good. A better approach is to get the 2 by 2 quad of texels ( (152,745), (153,745), (152,744) and (153,744) ) and do some linear interpolation between their colors. This linear interpolation must reflect the relative distance between (152.34,745.14) and each one of the texels. The closest the coordinate is to a texel the greater influence it gets on the end result and the further it is the less influence it gets. This looks much better than the original approach.

The method by which the final texel value is selected is known as 'filtering'. The simple approach of rounding the texture location is known as 'nearest filtering' and the more complex approach that we saw is called 'linear filtering'. Another name for nearest filtering you may come across is 'point filtering'. OpenGL supports several types of filters and you have the option to choose. Usually the filters that provide better results demand greater compute power from the GPU and may have an effect on the frame rate. Choosing the filter type is a matter of balancing between the desired result and the capability of the target platform.

Now that we understand the concept of texture coordinates it is time to take a look at the way texture mapping is done in OpenGL. Texturing in OpenGL means manipulating the intricate connections between four concepts: the texture object, the texture unit, the sampler object and the sampler uniform in the shader.

The texture object contains the data of the texture image itself, i.e., the texels. The texture can be of different types (1D, 2D, etc) with different dimensions and the underlying data type can have multiple formats (RGB, RGBA, etc). OpenGL provides a way to specify the starting point of the source data in memory and all the above attributes and load the data into the GPU. There are also multiple parameters that you can control such as the filter type, etc. In a very similar way to vertex buffer objects the texture object is associated with a handle. After creating the handle and loading the texture data and parameters you can simply switch textures on the fly by binding different handles into the OpenGL state. You no longer need to load the data again. From now on it is the job of the OpenGL driver to make sure the data is loaded in time to the GPU before rendering starts.

The texture object is not bound directly into the shader (where the actual sampling takes place). Instead, it is bound to a 'texture unit' whose index is passed to the shader. So the shader reaches the texture object by going through the texture unit. There are usually multiple texture units available and the exact number depends on the capability of your graphis card. In order to bind a texture object A to a texture unit 0 you first need to make texture unit 0 active and then bind texture object A. You can now make texture unit 1 active and bind a different (or even the same) texture object to it. Texture unit 0 remains bound to texture object A.

There is a bit of complexity in the fact that each texture unit actually has a place for several texture objects simultaneously, as long as the textures are of different types. This is called the 'target' of the texture object. When you bind a texture object to a texture unit you specify the target (1D, 2D, etc). So you can have texture object A bound to the 1D target while object B is bound to the 2D target of the same texture unit.

The sampling operation (usually) takes place inside the fragment shader and there is a special function that does it. The sampling function needs to know the texture unit to access because you can sample from multiple texture unit in the fragment shader. There is a group of special uniform variables for that, according to the texture target: 'sampler1D', 'sampler2D', 'sampler3D', 'samplerCube', etc. You can create as many sampler uniform variables as you want and assign the value of a texture unit to each one from the application. Whenever you call a sampling function on a sampler uniform variable the corresponding texture unit (and texture object) will be used.

The final concept is the sampler object. Don't confuse it with the sampler uniform variable! These are seperate entities. The thing is that the texture object contains both the texture data as well as parameters that configure the sampling operation. These parameters are part of the sampling state. However, you can also create a sampler object, configure it with a sampling state and bind it to the texture unit. When you do that the sampler object will override any sampling state defined in the texture object. Don't worry - for now we won't be using sampler objects at all but it is good to know that they exist.

The following diagram summarizes the relationships between the texturing concepts that we just learned:

Source walkthru

OpenGL knows how to load texture data in different formats from a memory location but does not provide any means for loading the texture into memory from image files such as PNG and JPG. We are going to use an external library for that. There are many options in that department and we're going to use ImageMagick, a free software library that supports many image types and is portable across several operating systems. See the instructions for more info on installing it.

Most of the handling of the texture is encapsulated in the following class:

(ogldev_texture.h:27)

class Texture
{
public:
   Texture(GLenum TextureTarget, const std::string& FileName);

   bool Load();

   void Bind(GLenum TextureUnit);
};

When creating a Texture object you will need to specify a target (we use GL_TEXTURE_2D) and file name. After that you call the Load() function. This can fail, for example, if the file does not exist or if ImageMagick encountered any other error. When you want to use a specific Texture instance you need to bind it to one of the texture units.

(ogldev_texture.cpp:31)

try {
   m_pImage = new Magick::Image(m_fileName);
   m_pImage->write(&m_blob, "RGBA");
}
catch (Magick::Error& Error) {
   std::cout << "Error loading texture '" << m_fileName << "': " << Error.what() << std::endl;
   return false;
}

This is how we use ImageMagick to load the texture from file and prepare it in memory to be loaded into OpenGL. We start by instantiating a class member with the type Magic::Image using the texture file name. This call loads the texture into a memory representation which is private to ImageMagick and cannot be used directly by OpenGL. Next we write the image into a Magick::Blob object using the RGBA (red, green, blue and alpha channels) format. The BLOB (Binary Large Object) is a useful mechanism for storing an encoded image in memory in a way that it can be used by external programs. If there is any error an exception will be thrown so we need to be prepared for it.

(ogldev_texture.cpp:40)

glGenTextures(1, &m_textureObj);

This OpenGL function is very similar to glGenBuffers() that we are already familiar with. It generates the specified number of texture objects and places their handles in the GLuint array pointer (the second parameter). In our case we only need one object.

(ogldev_texture.cpp:41)

glBindTexture(m_textureTarget, m_textureObj);

We are going to make several texture related calls and in a similar fashion to what with did with vertex buffers, OpenGL needs to know on what texture object to operate. This is the purpose of the glBindTexture() function. It tells OpenGL the texture object we refer to in all the following texture related calls, until a new texture object is bound. In addition to the handle (the second object) we also specify the texture target that can be GL_TEXTURE_1D, GL_TEXTURE_2D, etc. There can be a different texture object bound to each one of the targets simultaneously. In our implementation the target is passed as part of the constructor (and for now we are using GL_TEXTURE_2D).

(ogldev_texture.cpp:42)

glTexImage2D(m_textureTarget, 0, GL_RGBA, m_pImage->columns(), m_pImage->rows(), 0, GL_RGBA, GL_UNSIGNED_BYTE, m_blob.data());

This rather complex function is used to load the main part of the texture object, that is, the texture data itself. There are several glTexImage* function available and each one covers a few texture targets. The texture target is always the first parameter. The second parameter is the LOD, or Level-Of-Detail. A texture object can contain the same texture in different resolutions, a concept known as mip-mapping. Each mip-map has a different LOD index, starting from 0 for the highest resolution and growing as resolution decreases. For now, we have only a single mip-map so we pass zero.

The next parameter is the internal format in which OpenGL stores the texture. For example, you can pass a texture with the full four color channel (red, green, blue and alpha) but if you specify GL_RED you will get a texture with only the red channel, which looks kinda, well..., red (try it!). We use GL_RGBA to get the full texture color correctly. The next two parameters are the width and height of the texture in texels. ImageMagick conveniently stores this information for us when it loads the image and we get it using the Image::columns()/rows() functions. The fifth parameter is the border, which we leave as zero for now.

The last three parameters specify the source of the incoming texture data. The parameters are format, type and memory address. The format tells us the number of channels and needs to match the BLOB that we have in memory. The type describes the core data type that we have per channel. OpenGL supports many data types but in the ImageMagick BLOB we have one byte per channel so we use GL_UNSIGNED_BYTE. Finally comes the memory address of the actual data which we extract from the BLOB using the Blob::data() function.

(ogldev_texture.cpp:43)

glTexParameterf(m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

The general function glTexParameterf control many aspects of the texture sampling operation. These aspects are part of the texture sampling state. Here we specify the filter to be used for magnification and minification. Every texture has a given width and height dimensions but rarely it is applied to a triangle of the same proportion. In most case the triangle is either smaller or larger than the texture. In this case the filter type determines how to handle the case of magnifying or minifying the texture to match the proportion of the triangle. When the rasterized triangle is larger than the texture (e.g. it is very close to the camera) we may have several pixels covered by the same texel (magnification). When it is smaller (e.g. very far from the camera) several texels are covered by the same pixel (minification). Here we select the linear interpolation filter type for both cases. As we've seen earlier, linear interpolation provides good looking results by mixing the colors of a 2x2 texel quad based on the proximity of the actual texel location (calculated by scaling the texture coordinates by the texture dimensions).

(ogldev_texture.cpp:49)

void Texture::Bind(GLenum TextureUnit)
{
   glActiveTexture(TextureUnit);
   glBindTexture(m_textureTarget, m_textureObj);
}

As our 3D applications grow more complex, we may be using many different textures in many draw calls in the render loop. Before each draw call is made we need to bind the texture object we want to one of the texture units so that it will be sampled from in the fragment shader. This function takes the texture unit enum (GL_TEXTURE0, GL_TEXTURE1, etc) as a parameter. It makes it active using glActiveTexture() and then binds the texture object to it. This object will remain bound to this texture unit until the next call to Texture::Bind() is made with the same texture unit.

(shader.vs)

#version 330

layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;

uniform mat4 gWVP;

out vec2 TexCoord0;

void main()
{
   gl_Position = gWVP * vec4(Position, 1.0);
   TexCoord0 = TexCoord;
};

This is the updated vertex shader. There is an additional input parameter called TexCoord which is a 2D vector. Instead of outputing the color this shader passes the texture coordinates from the vertex buffer down to the fragment shader untouched. The rasterizer will interpolate the texture coordinates across the triangle face and each fragment shader will be invoked with its own specific texture coordinates.

(shader.fs)

in vec2 TexCoord0;

out vec4 FragColor;

uniform sampler2D gSampler;

void main()
{
    FragColor = texture2D(gSampler, TexCoord0.st);
};

And this is the updated fragment shader. It has an input variable called TexCoord0 which contains the interpolated texture coordinates we got from the vertex shader. There is a new uniform variable called gSampler that has the sampler2D type. This is an example of a sampler uniform variable. The application must set the value of the texture unit into this variable so that the fragment shader will be able to access the texture. The main function does one thing - it uses the internal texture2D function to sample the texture. The first parameter is the sampler uniform variable and the second is the texture coordinates. The returned value is the sampled texel (which in our case contains color) after having gone through filtering. This is the final color of the pixel in this tutorial. In the coming tutorials we will see that lighting simply scales that color based on the lighting parameters.

(tutorial16.cpp:128)

Vertex Vertices[4] = {
    Vertex(Vector3f(-1.0f, -1.0f, 0.5773f), Vector2f(0.0f, 0.0f)),
    Vertex(Vector3f(0.0f, -1.0f, -1.15475), Vector2f(0.5f, 0.0f)),
    Vertex(Vector3f(1.0f, -1.0f, 0.5773f), Vector2f(1.0f, 0.0f)),
    Vertex(Vector3f(0.0f, 1.0f, 0.0f), Vector2f(0.5f, 1.0f)) };

Up until this tutorial our vertex buffer was simply a consecutive list of Vector3f structures that contained the position. Now we have the 'Vertex' structure that contains the position as well as the texture coordinates as a Vector2f.

(tutorial16.cpp:80)

...
glEnableVertexAttribArray(1);
...
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)12);
...
pTexture->Bind(GL_TEXTURE0);
...
glDisableVertexAttribArray(1);

These are the changes and additions to the render loop. We start by enabling vertex attribute 1 for the texture coordinates, in addition to attribute 0 which is already enabled for the position. This corresponds to the layout statement in the vertex shader. Next we call glVertexAttribPointer to specify the location of the texture coordinates in the vertex buffer. The texture coordinate is composed of 2 floating point values which corresponds to the second and third paramaters. Note the fifth parameter. This is the size of the vertex structure and is specified for both the position and the texture coordinates. This parameter is known as the 'vertex stride' and tells OpenGL the number of bytes between the start of the attribute in a vertex and the start of the same attribute in the next vertex. In our case the buffer contains: pos0, texture coords0, pos1, texture coords1, etc. In the previous tutorials we only had position so it was ok to set it to either zero or sizeof(Vector3f). Now that we have more than one attribute the stride can only be the number of bytes in the Vertex structure. The last parameter is the offset in bytes from the start of the Vertex structure to the texture attributes. We have to do the casting to GLvoid* because that's how the function expects to get the offset.

Before the draw call we have to bind the texture we want to use to the texture unit. We only have one texture here so any texture unit would do fine. We only need to make sure the same texture unit is set into the shader (see below). After the draw call we disable the attribute.

(tutorial16.cpp:253)

glFrontFace(GL_CW);
glCullFace(GL_BACK);
glEnable(GL_CULL_FACE);

These OpenGL calls are not really related to texturing, I just added it to make it look better (try disabling...). They enable back face culling, a common optimization used to drop triangles before the heavy process of rasterization. The motivation here is that often 50% of the surface of an object is hidden from us (the back of a person, house, car, etc). The glFrontFace() function tells OpenGL that vertices in a triangle are specifed in clockwise order. That is, when you look at the front face of the triangle, you will find the vertices in the vertex buffer in clockwise order. The glCullFace() tells the GPU to cull the back of a triangle. This means that the "inside" of an object doesn't need to be rendered, only the external part. Finally, back face culling itself is enabled (by default it is disabled). Note that in this tutorial I reversed the order of the indices of the bottom triangle. The way that it was before made the triangle appear as if it was facing the inside of the pyramid (see line 170 in tutorial16.cpp).

(tutorial16.cpp:262)

glUniform1i(gSampler, 0);

Here we set the index of the texture unit we are going to use into the sampler uniform variable in the shader. 'gSampler' is a uniform whose value was acquired earlier using glGetUniformLocation(). The important thing to note here is that the actual index of the texture unit is used here, and not the OpenGL enum GL_TEXTURE0 (which has a different value).

(tutorial16.cpp:264)

pTexture = new Texture(GL_TEXTURE_2D, "test.png");

if (!pTexture->Load()) {
    return 1;
}

Here we create the Texture object and load it. 'test.png' is included with the sources of this tutorial but ImageMagick should be able to handle almost any file you throw at it.

Exercise: if you run the sample code of this tutorial you will notice that the faces of the pyramid are not identical. Try to understand why that happens and what needs to be changed to make them identical

Next tutorial


Tutorial 17 - Ambient Lighting

Tutorial 17:

Ambient Lighting

Background

Lighting is one of the most important subjects in the field of 3D graphics. Modeling it correctly adds a lot to the visual appeal of the rendered scene. The reason the word 'modeling' is used is because you cannot simulate exactly what nature does. The real light is made of huge amounts of particles called 'photons' and behaves as waves and particles simultaneously (the 'wave-particle duality' of light). If you try to calculate the effect of each photon in your program you'll run out of compute power really quick.

Therefore, several light models have been developed over the years that capture the core effect that light has when it falls on objects and makes them visible. These light models have become more and more complex as the 3D graphics field advanced and more compute power became available. In the course of the next few tutorials we will go through the basic lighting models that are simpler to implement but contribute immensely to the overall atmosphere of the scene.

The basic light model is called 'Ambient/Diffuse/Specular'. Ambient light is the type of light you see when you go outside in a usual sunny day. Even though the sun is traveling across the sky and its light rays hit the world at different angles in different parts of the day, most of the stuff will be visible, even if it is in shadow. Since light bounces off everything it eventually hits everything so objects that are not in the direct path of the sun are also lit. Even a light bulb in a room behaves like the sun in that sense and spreads ambient light because if the room is not too big everything is lit equally. The ambient light is modeled as light that has no origin, no direction and has an equal effect on all objects in the scene.

Diffuse lighting emphasizes the fact that the angle by which the light hits the surface effects the brightness by which the object is lit. When light hits an object on one side that side is brighter than the other side (the side not directly infront of the light source). We just saw that the sun spreads ambient light which has no specific direction. However, the sun also has diffuse properties in its light. When it hits a tall building you can usually see that one side of the building is lighter than the other side. The most important property of diffuse light is its direction.

Specular lighting is more a property of the object, rather than the light itself. This is what makes parts of things shine when light hits them at a very specific angle and the viewer is positioned at a specific point. Metalic objects often have some kind of specular property. For example, a car in a bright sunny day can sometimes shine off its edges. Calculating specular lighting must take into consideration both the direction the light hits (and bounces off) as well as the position of the viewer.

In 3D applications you usually don't create ambient, diffuse or specular lights directly. Instead, you use light sources such as the sun (when outdoor), a light bulb (indoors) or a flashlight (in a cave). These light source types can have different combinations of ambient, diffuse and specualr intensities as well as specialized properties. For example, a flashlight has a cone of light and things that are far off it are not illuminated by it at all.

In the following tutorials we will develop several useful light source types and study the basic light model as we go along.

We will start with a light source called 'directional light'. A directional light has a direction but no specific origin. This means that all light rays are parallel to each other. The direction of light is specified by a vector and that vector is used to calculate the light on all objects in the scene, regardless of their position. The sun fits very nicely into the category of a directional light. If you try to calculate the precise angle by which the sun hits two adjacent buildings you will end up with two values that are almost identical (i.e. the difference between them will be an extremely tiny fraction). This is because the sun is located some 150 million killometers away. Therefore, we simply disregard its position and take only the direction into account.

Another important property of a directional light is that its brightness remains the same regardless of the distance from the lit object. This is in contrast to another light source which we will study in the coming tutorials, the point light, whose brightness becomes weaker and weaker as it gets further away (e.g. the light bulb).

The following picture illustrates a directional light:

We already saw that the sun has both ambient as well as diffuse properties in its light. We are going to develop the ambient part here and the diffuse part in the next tutorial.

In the previous tutorial we learned how to sample the color of a pixel from a texture. The color has three channels (red, green and blue) and each channel is a single byte. This means that the value of the color can range from 0 to 255. Different combination of channels create different colors. When all channels are zero the color is black. When they are all 255 the color is white. Everything else is in between. By scaling all channels by the same fraction you can have the same basic color but make it brighter or darker (depending on the scaling factor).

When white light hits a surface the reflected color is simply the color of the surface. It can be a bit lighter or darker, depending on the power of the light source, but it is still the same basic color. If the light source is pure red (255,0,0) the reflected color can only be some type of red. This is because the light has no red and blue channels that can reflect back from the surface. If the surface is pure blue the end result will be utter black. The bottom line is that light can only expose the actual color of an object, it cannot "paint" it.

We will specify the color of light sources as a trio of floating point values in the range [0-1]. By multiplying the color of light by the color of the object we get the reflected color. However, we also want to take the ambient intensity of light into account. Therefore, the ambient intensity will be specified as a single floating point value in the range [0-1] which will also be multiplied by all channels of the reflected color that we've just calculated. This will be the final color. The following equation summarizes the calculation of ambient light:

In this tutorial code sample you will be able to play with the 'a' and 's' keys to increase or decrease the intensity of the ambient light and see the effect it has on the textured pyramid from the previous tutorial. This is only the ambient part of a directional light so the direction itself is not yet involved. This will change in the next tutorial when we study diffuse lighting. For now you will see that the pyramid is lit the same regardless of where you look at it.

The ambient light is considered by many as something to be avoided as much as possible. This is because it looks somewhat artificial and the simplicity of implementation doesn't contribute much to the realism of the scene. By using advanced methods such as global illumination one can eliminate the need for ambient light because the light that reflects off objects and hits other objects can also be taken into account. Since we are not there yet you will usually need some small amount of ambient light to avoid cases where one side of an object is lit and the other is in complete darkness. At the end of the day getting light to look good invloves a lot of playing with the parameters and tune-work.

Source walkthru

Our code samples are growing more complex over time and this trend will continue. In this tutorial, in addition to implementing ambient lighting, we also do a major restructure of the code. This will put the code in a better position for the tutorials ahead. The major changes are:

  1. Encapsulating shader manangement in the Technique class. This includes activities such as compilation and linkage. From now on we will be implementing our visual effects in classes that are derived from the Technique class.
  2. Moving of GLUT initialization and callback management into the GLUTBackend component. This component registers itself to receive callback calls from GLUT and forwards them to the application using a C++ interface called ICallbacks.
  3. Moving the global functions and variables in the main cpp file into a class that can be considered "the application". In the future we will extend this into a base class for all applications which will provide common functionality for all. This approach is very popular in many game engines and frameworks.

Most of the code in this tutorial (except the lighting specific code) is not new and has simply been rearranged according to the above design principles. Therefore, only the new headers files are reviewed.

(glut_backend.h:24)

void GLUTBackendInit(int argc, char** argv);

bool GLUTBackendCreateWindow(unsigned int Width, unsigned int Height, unsigned int bpp, bool isFullScreen, const char* pTitle);

A lot of the GLUT specific code has been moved to a "GLUT backend" component which makes it easier to initialize GLUT and create a window using the above simple functions.

(glut_backend.h:28)

void GLUTBackendRun(ICallbacks* pCallbacks);

After GLUT is initialized and a window is created the next step is to execute GLUT main loop using the above wrapper function. The new addition here is the ICallbacks interface which helps in registering GLUT callback functions. Instead of having each application register the callbacks on its own the GLUT backend component registers its own private functions and delivers the event to the object specified in the call to the function above. The main application class will often implement this interface on its own and simply pass itself as a paramemter in the call to GLUTBackendRun. This approach was selected for this tutorial too.

(technique.h:25)

class Technique
{
public:

   Technique();

   ~Technique();

   virtual bool Init();

   void Enable();

protected:

   bool AddShader(GLenum ShaderType, const char* pShaderText);

   bool Finalize();

   GLint GetUniformLocation(const char* pUniformName);

private:

   GLuint m_shaderProg;

   typedef std::list<GLuint> ShaderObjList;
   ShaderObjList m_shaderObjList;
};

In the previous tutorials all the grind work of compiling and linking the shaders was part of the application responsibility. The Technique class helps by wrapping the common functionality into itself and allowing derived class to focus on the core of the effect (a.k.a the 'Technique').

Each technique must first be initialized by calling the Init() function. The derived technique must call Init() of the base class (which creates the OpenGL program object) and can add its own private initialization here.

After a Technique object is created and initialized the usual sequence is for the derived technique class to call the protected function AddShader() on as many GLSL shaders (provided in a character array) as needed. Lastly, Finalize() is called to link the objects. The function Enable() is actually a wrapper for glUseProgram() so it must be called whenever switching a technique and calling the draw function.

This class tracks the intermediate compiled objects and after linking deletes them using glDeleteShader(). This helps in reducing the amount of resources your application is consuming. For better performance OpenGL applications often compile all shaders during load time and not during run time. By removing the objects immediately after linking you help keep the OpenGL resources consumed by your app low. The program object itself is deleted in the destructor using glDeleteProgram().

(tutorial17.cpp:49)

class Tutorial17 : public ICallbacks
{
public:

    Tutorial17()
    {
        ...
    }

    ~Tutorial17()
    {
        ...
    }

    bool Init()
    {
        ...
    }

    void Run()
    {
       GLUTBackendRun(this);
    }

    virtual void RenderSceneCB()
    {
        ...
    }

    virtual void IdleCB()
    {
        ...
    }

    virtual void SpecialKeyboardCB(int Key, int x, int y)
    {
        ...
    }

    virtual void KeyboardCB(unsigned char Key, int x, int y)
    {
        ...
    }

    virtual void PassiveMouseCB(int x, int y)
    {
        ...
    }

private:

    void CreateVertexBuffer()
    {
        ...
    }
    void CreateIndexBuffer()
    {
        ...
    }

    GLuint m_VBO;
    GLuint m_IBO;
    LightingTechnique* m_pEffect;
    Texture* m_pTexture;
    Camera* m_pGameCamera;
    float m_scale;
    DirectionalLight m_directionalLight;
};

This is a skeleton of the main application class which encapsulates the remaining code we are already familiar with. Init() takes care of creating the effect, loading the texture and creating the vertex/index buffers. Run() calls GLUTBackendRun() and passes the object itself as a parameter. Since the class implements the ICallbacks interface all the GLUT events end up in the proper methods of the class. In addition, all the variables that were previously part of the global section of the file are now private attributes in the class.

(lighting_technique.h:25)

struct DirectionalLight
{
    Vector3f Color;
    float AmbientIntensity;
};

This is the beginning of the defintion of the directional light. Right now, only the ambient part exists and the direction itself is still absent. We will add the direction in the next tutorial when we review diffuse lighting. The structure contains two fields - a color and an ambient intensity. The color determines what color channels of the objects can be reflected back and in what intensity. For example, if the color is (1.0, 0.5, 0.0) then the red channel of the object will be reflected fully, the green channel will be scaled down by half and the blue channel will be dropped completely. This is because an object can only reflect the incoming light (light sources are different - they emit light and need to be handled separately). In the case of the sun the usual color would be pure white (1.0, 1.0, 1.0).

The AmbientIntensity specifies how dim or bright is the light. You can have a pure white light with intensity of 1.0 so that the object is full lit or an intensity of 0.1 which means the object will be visible but appear very dim.

(lighting_technique.h:31)

class LightingTechnique : public Technique
{
public:

    LightingTechnique();

    virtual bool Init();

    void SetWVP(const Matrix4f& WVP);
    void SetTextureUnit(unsigned int TextureUnit);
    void SetDirectionalLight(const DirectionalLight& Light);

private:

    GLuint m_WVPLocation;
    GLuint m_samplerLocation;
    GLuint m_dirLightColorLocation;
    GLuint m_dirLightAmbientIntensityLocation;
};

Here is the first example of the usage of the Technique class. LightingTechnique is a derived class that implements lighting using the common functionality of compiling and linking provided by the base class. The Init() function must be called after the object is created. It simply calls Technique::AddShader() and Techique::Finalize() to generate the GLSL program.

(lighting.fs)

#version 330

in vec2 TexCoord0;

out vec4 FragColor;

struct DirectionalLight
{
    vec3 Color;
    float AmbientIntensity;
};

uniform DirectionalLight gDirectionalLight;
uniform sampler2D gSampler;

void main()
{
    FragColor = texture2D(gSampler, TexCoord0.xy) *
            vec4(gDirectionalLight.Color, 1.0f) *
            gDirectionalLight.AmbientIntensity;
}

The vertex shader remains unchanged in this tutorial. It keeps passing the position (after having multiplied it by the WVP matrix) and the texture coordinates. All the new logic goes into the fragment shader. The new addition here is the use of the 'struct' keyword to define the directional light. As you can see, this keyword is used in practically the same way as in C/C++. The structure is identical to the one we have in the application code and we must keep it that way so that the application and the shader can communicate.

There is now a new uniform variable of the DirectionalLight type that the application needs to update. This variable is used in the calculation of the final pixel color. As before, we sample the texture to get the base color. We then multiply it by the color and ambient intensity, per the formula above. This concludes the calculation of the ambient light.

(lighting_technique.cpp:44)

m_WVPLocation = GetUniformLocation("gWVP");
m_samplerLocation = GetUniformLocation("gSampler");
m_dirLightColorLocation = GetUniformLocation("gDirectionalLight.Color");
m_dirLightAmbientIntensityLocation = GetUniformLocation("gDirectionalLight.AmbientIntensity");

In order to access the DirectionalLight uniform variable from the application you must get the location of both of its fields independently. The LightingTechnique class has four GLuint location variables in order to access the uniforms in the vertex and the fragment shader. The WVP and sampler locations are fetched in the familiar way. The color and ambient intensity are fetched in the way that we see above - by specifying the name of the uniform variable in the shader followed by a dot and then the name of the field in the structure itself. Setting the value of these variables is done in the same way as any other variable. The LightingTechnique class provides two methods to set the directional light and the WVP matrix. The Tutorial17 class calls them prior to each draw to update the values.

This tutorial allows you to play with the ambient intensity using the 'a' and 's' keys that increase and decrease it, respectively. Follow the KeyboardCB() function in the Tutorial17 class to see how this is done.

Next tutorial


Tutorial 18 - Diffuse Lighting

Tutorial 18:

Diffuse Lighting

Background

The main difference between ambient light and diffuse light is the fact that diffuse light is dependent on the direction of the rays of light while ambient light ignores it completely. When only ambient light is present the entire scene is equally lit. Diffuse light makes the parts of objects that face the light brighter than the parts that are opposite from it.

Diffuse light also adds a twist where the angle by which the light strikes the surface determines the brightness of that surface. This concept is demonstrated by the following picture:

Let's assume that the strength of both light rays is the same and the only difference is their direction. The model of diffuse light says that the surface on the left will be brighter than the surface on the right because the surface on the right is hit at a sharper angle than the surface on the left. In fact, the surface on the left will be the brightest possible because the light there hits at an angle of 90 degrees.

The model of diffuse light is actually based on Lambert's cosine law that says that the intensity of light reflected from a surface is directly proportional to the cosine of the angle between the observer's line of sight and the surface normal. Note that we changed this a bit by using the direction of light instead of the observer's line of sight (which we will use in specular light).

To calculate the intensity of light in the diffuse model we are going to simply use the cosine of the angle between the light and the surface normal (whereas Lambert's law refers to the more general concept of 'directionaly proportional'). Consider the following picture:

We see four light rays hitting the surface at different angles. The surface normal is the green arrow pointing out from the surface. Light ray A has the greatest strength. The angle between A and the normal is zero and the cosine of zero is 1. This means that after we multiply the intensity of light (three channels of 0 to 1) by the color of the surface we will multiply by 1. We can't get any better than this with diffuse light. Light ray B hits the surface at an angle between 0 and 90. This means that the angle between B and the normal is also between 0 and 90 and the cosine of that angle is between 0 and 1. We will scale the result of the multiplication above by the cosine of that angle which means the intensity of light will definitely be less than light ray A.

Things become different with light rays C and D. C hits the surface directly from the side, at an angle of 0. The angle between C and the normal is exactly 90 degrees and the cosine is 0. This results in C having no effect on lighting the surface at all! The angle between D and the normal is obtuse which means the cosine is some negative number which is smaller than 0 and larger or equal to -1. The end result is the same as C - no effect on the surface brightness.

From this discussion we draw an important conclusion - in order to have any effect on the brightness of a surface the light must hit the surface such that the angle between it and the surface normal will be greater or equal to zero and up to (but not including!) 90 degrees.

We see that the surface normal plays an important part in the calculation of diffuse light. The examples above were very simple - the surface was a single line and there was only one normal to consider. In the real world we have objects that are composed of multiple polygon and the normal of each polygon is a bit different than the one next to it. Here's an example:

Since the normal is the same across the face of a polygon, it is enough to calculate the diffuse light in the vertex shader. All the three vertices in a triangle would have the same color and this will be the color of the entire triangle. However, this won't look too good. We will have a bunch of polygons where each one has a particular color which is slightly different than the one next to it and we will see how color breaks at the edges. This can definitely be improved.

The trick is to use a concept known as a 'vertex normal'. A vertex normal is the average of the normals of all the triangles that share the vertex. Instead of having the vertex shader calculate the diffuse light we only pass through the vertex normal as an attribute to the fragment shader and nothing more. The rasterizer will get three different normals and will need to interpolate between them. The fragement shader will be invoked for each pixel with the specific normal for this pixel. We can then calculate the diffuse light at the pixel level using that specific normal. The result will be a lighting effect which nicely changes across the triangle face and between neighboring triangles. This technique is known as Phong Shading. Here's how the vertex normals look like after interpolation:

You may find the pyramid model that we have been using in the last few tutorials a bit strange looking with those vertex normals and decide to stick with the original normals. This is OK. However, as models become more complex (and we will see that in the future) and their surfaces become smoother I think you will find the vertex normals more appropriate.

The only thing left to worry about is the coordinate space in which diffuse lighting calculations are going to take place. The vertices and their normals are specified in a local coordinate space and are transformed in the vertex shader all the way to clip space by the WVP matrix that we supply to the shader. However, specifying the direction of light in world space is the most logical course of action. After all, the direction of light is the result of some light source which is positioned in the world somewhere (even the sun is located in the "world", albeit many miles away) and sheds its light in a particular direction. Therefore, we will need to transform the normals to world space before the calculation.

Source walkthru

(lighting_technique.h:25)

struct DirectionalLight
{
    Vector3f Color;
    float AmbientIntensity;
    Vector3f Direction;
    float DiffuseIntensity;
};

This is the new DirectionalLight structure. There are two new members here: the direction is a 3 dimensional vector specified in world space and the intensity is a floating point number (will be used in the same way as the ambient intensity).

(lighting.vs)

#version 330

layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
layout (location = 2) in vec3 Normal;

uniform mat4 gWVP;
uniform mat4 gWorld;

out vec2 TexCoord0;
out vec3 Normal0;

void main()
{
    gl_Position = gWVP * vec4(Position, 1.0);
    TexCoord0 = TexCoord;
    Normal0 = (gWorld * vec4(Normal, 0.0)).xyz;
}

This is the updated vertex shader. We have a new vertex attribute, the normal, that the application will need to supply. In addition, the world transformation has its own uniform variable and we will need to supply it in addition to the WVP matrix. The vertex shader transforms the normal to world space using the world matrix and passes it to the fragment shader. Note how the 3 dimensional normal is extended to a 4 dimensional vector, multiplied by the 4 dimensional world matrix and then reduced back to 3 dimensions using the notation (...).xyz. This capability of the GLSL language is called 'swizzling' and allows great flexibility in vector manipulations. For example, if you have a 3 dimensional vector v(1,2,3) you can write: vec4 n = v.zzyy and then vector n will contain (3,3,2,2). Remember that when we extend the normal from 3 to 4 dimensions we must place zero at the fourth component. This nullifies the effect of translation in the world matrix (the fourth column). The reason is that vectors cannot be moved like points. They can only be scaled or rotated.

(lighting.fs:1)

#version 330

in vec2 TexCoord0;
in vec3 Normal0;

out vec4 FragColor;

struct DirectionalLight
{
    vec3 Color;
    float AmbientIntensity;
    float DiffuseIntensity;
    vec3 Direction;
};

Here is the beginning of the fragment shader. It now receives the interpolated vertex normal that was transformed by the vertex shader to world space. The DirectionalLight structure was extended to match the one in the C++ code and contains the new light attributes.

(lighting.fs:19)

void main()
{
    vec4 AmbientColor = vec4(gDirectionalLight.Color * gDirectionalLight.AmbientIntensity, 1.0f);

There is no change in the calculation of the ambient color factor. We calculate and store it here and use it in the final formula below.

    float DiffuseFactor = dot(normalize(Normal0), -gDirectionalLight.Direction);

This is the core of the diffuse light calculation. We calculate the cosine of the angle between the light vector and the normal by doing a dot product between them. There are three things to note here:

  1. The normal passed from the vertex shader is normalized before it is used. This is because the interpolation the vector went through may have changed its length and it is no longer a unit vector.
  2. The light direction is reversed. If you think about this for a moment you will see that light that hits a surface at a right angle is actualy 180 degrees away from the surface normal (which simply points back at the light source). By reversing the direction of light in this case we get a vector which equals the normal. Therefore, the angle between them is zero, which is what we want.
  3. The light vector is not normalized. It will be a waste of GPU resources to normalize the same vector over and over again for all pixels. Instead, we make sure we normalize the vector the application passes is normalized before the draw call is made.

    vec4 DiffuseColor;

    if (DiffuseFactor > 0) {
        DiffuseColor = vec4(gDirectionalLight.Color * gDirectionalLight.DiffuseIntensity * DiffuseFactor, 1.0f);
    }
    else {
        DiffuseColor = vec4(0, 0, 0, 0);
    }

Here we calculate the diffuse term which depends on the color of light, the diffuse intensity and the direction of light. If the diffuse factor is negative or equals to zero it means that light strikes the surface at an obtuse angle (either "from the side" or "from behind"). In that case the diffuse light has no effect and the DiffuseColor vector is initialized to zero. If the angle is greater than zero we calculate the diffuse color by multiplying the basic light color by the constant diffuse intensity and then scaling the result by the diffuse factor. If the angle between the light and the normal is 0 the diffuse factor will be 1 which will provide the maximum light strength.

    FragColor = texture2D(gSampler, TexCoord0.xy) * (AmbientColor + DiffuseColor);
}

This is the final lighting calculation. We add the ambient and diffuse terms and multiply the result by the color which is sampled from the texture. Now you can see that even if diffuse light has no effect on the surface (due to direction), the ambient light can still light it up, if it exists.

(lighting_technique.cpp:144)

void LightingTechnique::SetDirectionalLight(const DirectionalLight& Light)
{
    glUniform3f(m_dirLightLocation.Color, Light.Color.x, Light.Color.y, Light.Color.z);
    glUniform1f(m_dirLightLocation.AmbientIntensity, Light.AmbientIntensity);
    Vector3f Direction = Light.Direction;
    Direction.Normalize();
    glUniform3f(m_dirLightLocation.Direction, Direction.x, Direction.y, Direction.z);
    glUniform1f(m_dirLightLocation.DiffuseIntensity, Light.DiffuseIntensity);
}

This function sets the parameters of the directional light into the shader. It was extended to cover the direction vector and the diffuse intensity. Note that the direction vector is normalized before it is set. The LightingTechnique class also fetches the direction and diffuse intensity uniform locations from the shader as well as the world matrix uniform location. There is also a function to set the world transformation matrix. All this stuff is pretty routine by now and the code is not quoted here. Check the source for more details.

(tutorial18.cpp:35)

struct Vertex
{
    Vector3f m_pos;
    Vector2f m_tex;
    Vector3f m_normal;

    Vertex() {}

    Vertex(Vector3f pos, Vector2f tex)
    {
        m_pos = pos;
        m_tex = tex;
        m_normal = Vector3f(0.0f, 0.0f, 0.0f);
    }
};

The updated Vertex structure now includes the normal. It is initialized automatically to zero by the constructor and we have a dedicated function that scans all the vertices and calculates the normals.

(tutorial18.cpp:197)

void CalcNormals(const unsigned int* pIndices, unsigned int IndexCount, Vertex* pVertices, unsigned int VertexCount)
{
    for (unsigned int i = 0 ; i < IndexCount ; i += 3) {
        unsigned int Index0 = pIndices[i];
        unsigned int Index1 = pIndices[i + 1];
        unsigned int Index2 = pIndices[i + 2];
        Vector3f v1 = pVertices[Index1].m_pos - pVertices[Index0].m_pos;
        Vector3f v2 = pVertices[Index2].m_pos - pVertices[Index0].m_pos;
        Vector3f Normal = v1.Cross(v2);
        Normal.Normalize();

        pVertices[Index0].m_normal += Normal;
        pVertices[Index1].m_normal += Normal;
        pVertices[Index2].m_normal += Normal;
    }

    for (unsigned int i = 0 ; i < VertexCount ; i++) {
        pVertices[i].m_normal.Normalize();
    }
}

This function takes an array of vertices and indices, fetches the vertices of each triangle according to the indices and calculates its normal. In the first loop we only accumulate the normals into each of the three triangle vertices. For each triangle the normal is calculated as a cross product between the two edges that are coming out of the first vertex. Before accumulating the normal in the vertex we make sure we normalize it. The reaons is that the result of the cross product is not guaranteed to be of unit length. In the second loop we scan the array of vertices directly (since we don't care about the indices any more) and normalize the normal of each vertex. This operation is equivalent to averaging out the accumulated sum of normals and leaves us with a vertex normal that is of a unit length. This function is called before the vertex buffer is created in order to get the calculated vertex normals into the buffer along with the other vertex attributes.

(tutorial18.cpp:131)

    const Matrix4f& WorldTransformation = p.GetWorldTrans();
    m_pEffect->SetWorldMatrix(WorldTransformation);
    ...
    glEnableVertexAttribArray(2);
    ...
    glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)20);
    ...
    glDisableVertexAttribArray(2);

These are the main changes to the render loop. The pipeline class has a new function that provides the world transformation matrix (in addition to the WVP matrix). The world matrix is calculated as the multiplication of the scaling matrix by the rotation matrix and finally by the translation matrix. We enable and disable the third vertex attribute array and specify the offset of the normal within each vertex in the vertex buffer. The offset is 20 because the normal is preceded by the position (12 bytes) and the texture coordinates (8 bytes).

To complete the demo that we see in this tutorial's picture we must also specify the diffuse intensity and the light direction. This is done in the constructor of the Tutorial18 class. The diffuse intensity is set to 0.8 and the direction of light is from left to right. The ambient intensity was decreased all the way down to zero to amplify the effect of diffuse light. You can play with the keys 'z' and 'x' to control the diffuse intensity (as well as 'a' and 's' from the previous tutorial that governs ambient intensity).

Mathematical note
There are many sources online that tell you that you need the transpose of the inverse of the world matrix in order to transform the normal vector. This is correct, however, we usually don't need to go that far. Our world matrices are always orthogonal (their vectors are always orthogonal). Since the inverse of an orthogonal matrix is equal to its transpose, the transpose of the inverse is actually the transpose of the transpose, so we end up with the original matrix. As long as we avoid doing distortions (scaling one axis differently than the rest) we are fine with the approach I presented above.

Next tutorial


Tutorial 19 - Specular Lighting

Tutorial 19:

Specular Lighting

Background

When we calculated ambient lighting the only factor was the strength of light. Then we progressed to diffuse lighting which added the direction of light into the equation. Specular lighting includes these factors and adds a new element into the mix - the position of the viewer. The idea is that when light strikes a surface at some angle it is also reflected away at the same angle (on the other side of the normal). If the viewer is located exactly somewhere along the way of the reflected light ray it receives a larger amount of light than a viewer who is located further away.

The end result of specular lighting is that objects will look brighter from certain angles and this brightness will diminish as you move away. The perfect real world example of specular lighting is metallic objects. These kinds of objects can sometimes be so bright that instead of seeing the object in its natural color you see a patch of shining white light which is reflected directly back at you. However, this type of quality which is very natural for metals is absent in many other materials (e.g. wood). Many objects simply don't shine, regardless of the where the light is coming from and where the viewer is standing. The conclusion is that the specular factor depends more on the object, rather than the light itself.

Let's see how we can bring in the viewer location into the calculation of specular light. Take a look at the following picture:

There are five things we need to pay attention to:

  • 'I' is the incident light that hits the surface (and generates the diffuse light).
  • 'N' is the surface normal.
  • 'R' is the ray of light which is reflected back from the surface. It is symmetric across the normal from 'I' but its general direction is reversed (it points "up" and not "down").
  • 'V' is the vector from the point on the surface where the light hits to the 'eye' (which represents the viewer).
  • 'α' is the angle which is created by the vectors 'R' and 'V'.

We are going to model the phenomenon of specular light using the angle 'α'. The idea behind specular light is that the strength of the reflected light is going to be at its maximum along the vector 'R'. In that case 'V' is identical to 'R' and the angle is zero. As the viewer starts moving away from 'R' the angle grows larger. We want the effect of light to gradually decrease as the angle grows. By now you can probably guess that we are going to use the dot product operation again in order to calculate the cosine of 'α'. This will serve as our specular factor in the lighting formula. When 'α' is zero the cosine is 1 which is the maximum factor that we can get. As 'α' is decreased the cosine becomes smaller until 'α' reaches 90 degrees where the cosine is zero and there is absolutely no specular effect. When 'α' is more than 90 degrees the cosine is negative and there is also no specular effect. This means that the viewer is absolutely not in the path of the reflected ray of light.

To calculate 'α' we will need both 'R' and 'V'. 'V' can be calculating by substracting the location of the point where the light hits in world space from the location of the viewer (also in world space). Since our camera is already maintained in world space we only need to pass its position to the shader. Since the image above is simplified, there is a single point there where the light hits. In reality, the entire triangle is lit (assuming it is facing the light). So we will calculate the specular effect for every pixel (same as we did with diffuse light) and for that we need the location of the pixel in world space. This is also simple - we can transform the vertices into world space and let the rasterizer interpolate the world space position of the pixel and provide us the result in the fragment shader. Actually, this is the same as the handling of the normal in the previous tutorial.

The only thing left is to calculate the reflected ray 'R' using the vector 'I' (which is provided by the application to the shader). Take a look at the following picture:

Remember that a vector doesn't really have a starting point and all vectors that have the same direction and magnitude are equal. Therefore, the vector 'I' was copied "below" the surface and the copy is identical to the original. The target is to find the vector 'R'. Based on the rules of vector addition 'R' is equal to 'I'+'V'. 'I' is already known so all we have to do is find out 'V'. Note that the opposite of the normal 'N' also appears as '-N' and using a dot product operation between 'I' and '-N' we can find the magnitude of the vector which is created when 'I' is projected on '-N'. This magnitude is exactly half the magnitude of 'V'. Since 'V' has the same direction as 'N' we can calculate 'V' by multiplying 'N' (whose length is 1.0) by twice that magnitude. To summarize:

Now that you understand the math it is time to let you in on a little secret - GLSL provides an internal function called 'reflect' that does exactly this calculation. See below how it is used in the shader.

Let's finalize the formula of specular light:

We start by multiplying the color of light by the color of the surface. This is the same as with ambient and diffuse light. The result is multiplied by the specular intensity of the material ('M'). A material which does not have any specular property (e.g. wood) would have a specular intensity of zero which will zero out the result of the equation. Shinier stuff such as metal can have increasingly higher levels of specular intensity. After that we multiply by the cosine of the angle between the reflected ray of light and the vector to the eye. Note that this last part is raised to the power of 'P'. 'P' is called the 'specular power' or the 'shininess factor'. Its job is to intensify and sharpen the edges if the area where the specular light is present. The following picture shows the effect of the specular power when it is set to 1:

While the following shows a specular exponent of 32:

The specular power is also considered as an attribute of the material so different objects will have different specular power values.

Source walkthru

(lighting_technique.h:32)

class LightingTechnique : public Technique
{
public:
...
    void SetEyeWorldPos(const Vector3f& EyeWorldPos);
    void SetMatSpecularIntensity(float Intensity);
    void SetMatSpecularPower(float Power);

private:
...
    GLuint m_eyeWorldPosLocation;
    GLuint m_matSpecularIntensityLocation;
    GLuint m_matSpecularPowerLocation;

There are three new attributes in the LightingTechnique - eye position, specular intensity and power of the material. All three are indepedent from the light itself. The reason is that when the same light falls on two different materials (e.g. metal and wood) each of them shines in a different way. The current usage model of the two material attributes is a bit limiting. All the triangles that are part of the same draw call get the same values for these attributes. This can be a bit annoying when the triangles represent different parts of the model with different material properties. When we get to the mesh loading tutorials we will see that we can generate different specular values in a modeler software and make them part of the vertex buffer (instead of a parameter to the shader). This will allow us to process triangles with different specular lighting in the same draw call. For now the simple approach will do (as an exercise you can try adding specular intensity and power to the vertex buffer and access it in the shader).

(lighting.vs:12)

out vec3 WorldPos0;

void main()
{
    gl_Position = gWVP * vec4(Position, 1.0);
    TexCoord0 = TexCoord;
    Normal0 = (gWorld * vec4(Normal, 0.0)).xyz;
    WorldPos0 = (gWorld * vec4(Position, 1.0)).xyz;
}

The vertex shader above includes just one new line (the last one). The world matrix (which we added in the previous tutorial in order to transform the normal) is now used to pass the world position of the vertex to the fragment shader. We see an interesting technique here of transforming the same vertex position (provided in local space) using two different matrices and passing the results indepedently to the fragment shader. The result of the full transformation (world-view-projection matrix) goes into the formal system variable 'gl_Position' and the GPU takes care of transforming it to a screen space coordinate and using it for the actual rasterization. The result of the "partial" transformation (only to world space) goes into a user defined attributes which is simply interpolated during rasterization so every pixel for which the fragment shader is invoked is provided its own world space position value. This technique is very common and useful.

(lighting.fs:5)

in vec3 WorldPos0;
.
.
.
uniform vec3 gEyeWorldPos;
uniform float gMatSpecularIntensity;
uniform float gSpecularPower;

void main()
{
    vec4 AmbientColor = vec4(gDirectionalLight.Color * gDirectionalLight.AmbientIntensity, 1.0f);
    vec3 LightDirection = -gDirectionalLight.Direction;
    vec3 Normal = normalize(Normal0);

    float DiffuseFactor = dot(Normal, LightDirection);

    vec4 DiffuseColor = vec4(0, 0, 0, 0);
    vec4 SpecularColor = vec4(0, 0, 0, 0);

    if (DiffuseFactor > 0) {
        DiffuseColor = vec4(gDirectionalLight.Color, 1.0f) *
            gDirectionalLight.DiffuseIntensity *
            DiffuseFactor;

        vec3 VertexToEye = normalize(gEyeWorldPos - WorldPos0);
        vec3 LightReflect = normalize(reflect(gDirectionalLight.Direction, Normal));
        float SpecularFactor = dot(VertexToEye, LightReflect);
        if (SpecularFactor > 0) {
            SpecularFactor = pow(SpecularFactor, gSpecularPower);
            SpecularColor = vec4(gDirectionalLight.Color * gMatSpecularIntensity * SpecularFactor, 1.0f);
        }
    }

    FragColor = texture2D(gSampler, TexCoord0.xy) * (AmbientColor + DiffuseColor + SpecularColor);
}

There are several changes in the fragment shader. There are now three new uniform variables that store the attributes required for calculating specular light (eye pos, specular intensity and power). The ambient color is calculated in the same way as the two previous tutorials. Then the diffuse and specular color vectors are created and initialize to zero. They both have a value different then zero only when the angle between the light and the surface is less than 90 degrees. This is checked using the diffuse factor (same as in the diffuse lighting tutorial).

The next step is to calculate the vector from the vertex in world space to the viewer location (also in world space). We do this by substracting the world position of the vertex from the eye position which is a uniform variable and identical for all pixels. This vector is normalized to make it ready for the dot product operation. After that the reflected light vector is calculated using the built-in function 'reflect' (you may also try to calculate it manually based on the description above). This function take two parameters - the light vector and the surface normal. The important thing here is to use the original light vector which goes towards the surface and not the reversed one which was used for the diffuse factor calculation. This is obvious from the diagram above. Next we calculate the specular factor as the cosine of the angle between the reflected ray of light and the vector from the vertex to the viewer (again using a dot product operation).

The specular effect is noticeable only if that angle is less than 90 degrees. Therefore, we check if the result of the last dot product operation is greater than zero. The final specular color is calculated by multiplying the color of light by the specular intensity of the material and the specular factor. We add the specular color to the ambient and diffuse color to create the total color of light. This is multiplied by the sampled color from the texture and provides the final color of the pixel.

(tutorial19.cpp:134)

m_pEffect->SetEyeWorldPos(m_pGameCamera->GetPos());
m_pEffect->SetMatSpecularIntensity(1.0f);
m_pEffect->SetMatSpecularPower(32);

Using the specular color is very simple. In the render loop we grab the camera position (which is already maintained in world space) and pass it to the lighting technique. We also set the specular intensity and power. All the rest is handled by the shader.

Play with different specular values and light direction to see their effect. You may need to circle around the object to get into a position where the specular light is visible.

Next tutorial


Tutorial 20 - Point Light

Tutorial 20:

Point Light

Background

We have studied the three basic light models (ambient, diffuse and specular) under the umbrella of directional light. Directional light is a light type which is characterized by a single direction vector and the lack of any origin. Therefore, it doesn't grow weaker with distance (in fact, you can't even define its distance from its target). We are now going to review the point light type which has both an origin as well as a fading effect which grows stronger as objects move away from it. The classic example for a point light is the light blub. You can't feel the fading effect when the light bulb is inside a standard room but take it outside and you will quickly see how limited its strength is. Notice that the direction of light which is constant across the scene for directional light becomes dynamic with point light. That's because a point light shines in all directions equally so the direction must be calculated per object by taking the vector from the object towards the point light origin. That is why we specify the origin rather than the direction for point lights.

The fading effect of point lights is usually called 'attenuation'. The attenuation of a real light is governed by the inverse-square law that says that the strength of light is inversely proportional to the square of the distance from the source of light. This is described in mathematical terms by the following formula:

This formula doesn't provide good looking results in 3D graphics. For example, as the distance becomes smaller the strength of light approaches infinity. In addition, the developer has no control over the results except for setting the initial strength of light. This is too limiting. Therefore, we add a few factors to the formula to make it more flexible:

We've added three light attenuation factors to the denominator. A constant factor, a linear factor and an exponential factor. The physically accurate formula is achieved when setting the constant and linear factors to zero and the exponential factor to 1. You may find it useful to set the constant factor to 1 and the other two factors to a much smaller fraction. When setting the constant factor to one you basically guarantee that the strength of light will reach maximum (actually, what you configure it to be in the program) at distance zero and will decrease as distance grows because the denominator will become greater than one. As you fine tune the linear and exponential factors you will reach the desired effect of light which rapidly or slowly fades with distance.

Let's summarize the steps required for the calculation of point light:

  1. Calculate the ambient term the same as in directional light.
  2. Calculate the light direction as the vector going from the pixel (in world space) to the point light origin. You can now calculate the diffuse and specular terms the same as in directional light but using this light direction.
  3. Calculate the distance from the pixel to the light origin and use it to reach the total attenuation value.
  4. Add the three light terms together and divide them by the attenuation to reach the final point light color.

Source walkthru

(lighting_technique.h:24)

struct BaseLight
{
    Vector3f Color;
    float AmbientIntensity;
    float DiffuseIntensity;
};
.
.
.
struct PointLight : public BaseLight
{
    Vector3f Position;

    struct
    {
        float Constant;
        float Linear;
        float Exp;
    } Attenuation;
}

Despite their differences, directional and point lights still have much in common. This common stuff has been moved to the BaseLight structure that both light types are now derived from. The directional light adds the direction in its concrete class while point light adds position (in world space) and the three attenuation factors.

(lighting_technique.h:81)

void SetPointLights(unsigned int NumLights, const PointLight* pLights);

In addition to demonstrating how to implement a point light, this tutorial also shows how to use multiple lights. The assumption is that there will usually be a single directional light (serving as the "sun") and/or possibly several point light sources (light bulbs in a rooms, torches in a dungeon, etc). This function takes an array of PointLight structures and the array size and updates the shader with their values.

(lighting_technique.h:103)

struct {
    GLuint Color;
    GLuint AmbientIntensity;
    GLuint DiffuseIntensity;
    GLuint Position;
    struct
    {
        GLuint Constant;
        GLuint Linear;
        GLuint Exp;
    } Atten;
} m_pointLightsLocation[MAX_POINT_LIGHTS];

In order to support multiple point lights the shader contains an array of structures identical to struct PointLight (only in GLSL). There are basically two methods to update an array of structures in shaders:

  1. You can get the location of each structure field in each of the array elements (e.g. array of 5 structures with 4 fields each leads to 20 uniform locations) and set the value of each field in each element seperately.
  2. You can get the location of the fields only in the first array element and use a GL function that sets an array of variables for each specific field attribute type. For example, if the first field is a float and the second is an integer you can set all the values of the first field by passing an array of floats in one call and set the second field by with an array of integers in the second call.

The first method is more wasteful in terms of the number of uniform locations you must maintain but is more flexible to use. It allows you to update any variable in the entire array by simply accessing its location and does not require you to transform your input data as the second method does.

The second method requires less uniform location management but if you want to update several array elements at once and your user passes an array of structures (as in SetPointLights()) you will need to transform it into a structure of arrays since each uniform location will need to be updated by an array of variables of the same type. When using an array of structures there is a gap in memory between the same field in two consecutive array elements which requires you to gather them into their own array. In this tutorial we will use the first method. You should play with both and decide what works best for you.

MAX_POINT_LIGHTS is a constant value that limits the maximum number of point lights that can be used and must be synchronized with the corresponding value in the shader. The default value is 2. As you increase the number of lights in your application you may end up with a performance problem that becomes worse as the number of lights grows. This problem can be mitigated using a technique called 'deferred shading' which will be explored in the future.

(lighting.fs:46)

vec4 CalcLightInternal(BaseLight Light, vec3 LightDirection, vec3 Normal)
{
    vec4 AmbientColor = vec4(Light.Color, 1.0f) * Light.AmbientIntensity;
    float DiffuseFactor = dot(Normal, -LightDirection);

    vec4 DiffuseColor = vec4(0, 0, 0, 0);
    vec4 SpecularColor = vec4(0, 0, 0, 0);

    if (DiffuseFactor > 0) {
        DiffuseColor = vec4(Light.Color * Light.DiffuseIntensity * DiffuseFactor, 1.0f);
        vec3 VertexToEye = normalize(gEyeWorldPos - WorldPos0);
        vec3 LightReflect = normalize(reflect(LightDirection, Normal));
        float SpecularFactor = dot(VertexToEye, LightReflect);
        if (SpecularFactor > 0) {
            SpecularFactor = pow(SpecularFactor, gSpecularPower);
            SpecularColor = vec4(Light.Color * gMatSpecularIntensity * SpecularFactor, 1.0f);
        }
    }

    return (AmbientColor + DiffuseColor + SpecularColor);
}

It should not come as a big surprise that we can share quite a lot of shader code between directional light and point light. Most of the algorithm is the same. The difference is that we need to factor in the attenuation only for the point light. In addition, the light direction is provided by the application in the case of directional light and must be calculated per pixel for point light.

The function above encapsulates the common stuff between the two light types. The BaseLight structure contains the intensities and the color. The LightDirection is provided seperately because of the reason above. The vertex normal is also provided because we normalize it once when entering the fragment shader and then use it in multiple calls to this function.

(lighting.fs:70)

vec4 CalcDirectionalLight(vec3 Normal)
{
    return CalcLightInternal(gDirectionalLight.Base, gDirectionalLight.Direction, Normal);
}

With the common function in place, the function to calculate the directional light simply becomes its wrapper, taking most of its arguments from the global variables.

(lighting.fs:75)

vec4 CalcPointLight(int Index, vec3 Normal)
{
    vec3 LightDirection = WorldPos0 - gPointLights[Index].Position;
    float Distance = length(LightDirection);
    LightDirection = normalize(LightDirection);

    vec4 Color = CalcLightInternal(gPointLights[Index].Base, LightDirection, Normal);
    float Attenuation = gPointLights[Index].Atten.Constant +
                        gPointLights[Index].Atten.Linear * Distance +
                        gPointLights[Index].Atten.Exp * Distance * Distance;

    return Color / Attenuation;
}

Calculating point light is just a bit more complex than directional light. This function will be called for every configured point light so it takes the light index as a parameter and uses it to index into the global array of point lights. It calculated the vector from the light source (provided in world space by the application) to the world space position passed by the vertex shader. The distance from the point light to the pixel is calculated using the built-in function length(). Once we have the distance we normalize the light direction vector. Remember that CalcLightInternal() expects it to be normalized and in the case of directional light the LightingTechnique class takes care of it. We get the color back from CalcInternalLight() and using the distance that we got earlier we calculate the attenuation. The final point light color is calculated by dividing the color that we have by the attenuation.

(lighting.fs:89)

void main()
{
    vec3 Normal = normalize(Normal0);
    vec4 TotalLight = CalcDirectionalLight(Normal);

    for (int i = 0 ; i < gNumPointLights ; i++) {
        TotalLight += CalcPointLight(i, Normal);
    }

    FragColor = texture2D(gSampler, TexCoord0.xy) * TotalLight;
}

Once we get all the infrastructure in place the fragment shader becomes very simple. It simply normalizes the vertex normal and then accumulates the results of all light types together. The result is multiplied by the sampled color and is used as the final pixel color.

(lighting_technique.cpp:279)

void LightingTechnique::SetPointLights(unsigned int NumLights, const PointLight* pLights)
{
    glUniform1i(m_numPointLightsLocation, NumLights);

    for (unsigned int i = 0 ; i < NumLights ; i++) {
        glUniform3f(m_pointLightsLocation[i].Color, pLights[i].Color.x, pLights[i].Color.y, pLights[i].Color.z);
        glUniform1f(m_pointLightsLocation[i].AmbientIntensity, pLights[i].AmbientIntensity);
        glUniform1f(m_pointLightsLocation[i].DiffuseIntensity, pLights[i].DiffuseIntensity);
        glUniform3f(m_pointLightsLocation[i].Position, pLights[i].Position.x, pLights[i].Position.y, pLights[i].Position.z);
        glUniform1f(m_pointLightsLocation[i].Atten.Constant, pLights[i].Attenuation.Constant);
        glUniform1f(m_pointLightsLocation[i].Atten.Linear, pLights[i].Attenuation.Linear);
        glUniform1f(m_pointLightsLocation[i].Atten.Exp, pLights[i].Attenuation.Exp);
    }
}

This function updates the shader with the point lights values by iterating over the array elements and passing each element's attribute values one by one. This is the so called "method 1" that was described earlier.

This tutorials demo shows two point lights chasing one another across a field. One light is based on the cosine function while the other on the sine function. The field is a very simple quad made of two triangles. The normal is a straight up vector.

Next tutorial


Tutorial 21 - Spot Light

Tutorial 21:

Spot Light

Background

The spot light is the third and final light type that we will review (at least for a little while...). It is more complex than directional light and point light and essentially borrows stuff from both. The spot light has an origin position and is under the effect of attenuation as distance from target grows (as point light) and its light is pointed at a specific direction (as directional light). The spot light adds the unique attribute of shedding light only within a limited cone that grows wider as light moves further away from its origin. A good example for a spot light is the flashlight. Spot lights are very useful when the character in the game you are developing is exploring an underground dungeon or escaping from prison.

We already know all the tools to develop the spot light. The missing piece is the cone effect of this light type. Take a look at the following picture:

The spot light direction is defined as the black arrow that points straight down. We want our light to have an effect only on the area limited within the two red lines. The dot product operation again comes to the rescue. We can define the cone of light as the angle between each of the red lines and the light direction (i.e. half the angle between the red lines). We can take the cosine 'C' of that angle and perform a dot product between the light direction 'L' and the vector 'V' from the light origin to the pixel. If the result of the dot product is larger than 'C' (remember that a cosine result grows larger as the angle grows smaller), then the angle between 'L' and 'V' is smaller than the angle between 'L' and the two red lines that define the spot light cone. In that case we want the pixel to receive light. If the angle is larger the pixel does not receive any light from the spot light. In the example above a dot product between 'L' and 'V' will yield a result which is smaller than the dot product between 'L' and either one of the red lines (it is quite obvious that the angle between 'L' and 'V' is larger than the angle between 'L' and the red lines). Therefore, the pixel is outside the cone of light and is not illuminated by the spot light.

If we go with this "receive/doesn't receive light" approach we will end up with a highly artificial spot light that has a very noticeable edge between its lit and dark areas. It will look like a perfect circle within total darkness (assuming no other light sources). A more realistic looking spot light is one whose light gradually decreases towards the edges of the circle. We can use the dot product that we calculated (in order to determine whether a pixel is lit or not) as a factor. We already know that the dot product will be 1 (i.e. maximum light) when the vectors 'L' and 'V' are equal. But now we run into some nasty behavior of the cosine function. The spot light angle should not be too large or else the light will be too widespread and we will loose the appearance of a spot light. For example, let's set the angle at 20 degrees. The cosine of 20 degrees is 0.939, but the range [0.939, 1.0] is too small to serve as a factor. There is not enough room there to interpolate values that the eye will be able to notice. The range [0, 1] will provide much better results.

The approach that we will use is to map the smaller range defined by the spot light angle into the larger range of [0, 1]. Here's how we do it:

The principle is very simple - calculate the ratio between the smaller range and the larger range and scale the specific range you want to map by that ratio.

Source walkthru

(lighting_technique.h:68)

struct SpotLight : public PointLight
{
    Vector3f Direction;
    float Cutoff;

    SpotLight()
    {
        Direction = Vector3f(0.0f, 0.0f, 0.0f);
        Cutoff = 0.0f;
    }
};

The structure that defines the spot light is derived from PointLight and adds the two attributes that differentiate it from the point light: a direction vector and cutoff value. The cutoff value represents the maximum angle between the light direction and the light to pixel vector for pixels that are under the influence of the spot light. The spot light has no effect beyond the cutoff value. We've also added to the LightingTechnique class an array of locations for the shader (not quoted here). This array allows us to access the spot light array in the shader.

(lighting.fs:39)

struct SpotLight
{
    struct PointLight Base;
    vec3 Direction;
    float Cutoff;
};
...
uniform int gNumSpotLights;
...
uniform SpotLight gSpotLights[MAX_SPOT_LIGHTS];

There is a similar structure for the spot light type in GLSL. Since we cannot use inheritance here as in the C++ code we use the PointLight structure as a member and add the new attributes next to it. The important difference here is that in the C++ code the cutoff value is the angle itself while in the shader it is the cosine of that angle. The shader only cares about the cosine so it is more efficient to calculate it once and not for every pixel. We also define an array of spot lights and use a counter called 'gNumSpotLights' to allow the application to define the number of spot lights that are actually used.

(lighting.fs:85)

vec4 CalcPointLight(struct PointLight l, vec3 Normal)
{
    vec3 LightDirection = WorldPos0 - l.Position;
    float Distance = length(LightDirection);
    LightDirection = normalize(LightDirection);

    vec4 Color = CalcLightInternal(l.Base, LightDirection, Normal);
    float Attenuation = l.Atten.Constant +
        l.Atten.Linear * Distance +
        l.Atten.Exp * Distance * Distance;

    return Color / Attenuation;
}

The point light function has gone through a minor modification - it now takes a PointLight structure as a parameter, rather than access the global array directly. This makes it simpler to share it with spot lights. Other than that, there is no change here.

(lighting.cpp:fs)

vec4 CalcSpotLight(struct SpotLight l, vec3 Normal)
{
    vec3 LightToPixel = normalize(WorldPos0 - l.Base.Position);
    float SpotFactor = dot(LightToPixel, l.Direction);

    if (SpotFactor > l.Cutoff) {
        vec4 Color = CalcPointLight(l.Base, Normal);
        return Color * (1.0 - (1.0 - SpotFactor) * 1.0/(1.0 - l.Cutoff));
    }
    else {
        return vec4(0,0,0,0);
    }
}

This is where we calculate the spot light effect. We start by taking the vector from the light origin to the pixel. As is often the case, we normalize it to get it ready for the dot product ahead. We do a dot product between this vector and the light direction (which has already been normalized by the application) and get the cosine of the angle between them. We then compare it to the light's cutoff value. This is the cosine of the angle between the light direction and the vector that defines its circle of influence. If the cosine is smaller it means the angle between the light direction and the light to pixel vector places the pixel outside the circle of influence. In this case the contribution of this spot light is zero. This will limit the spot light to a small or large circle, depending on the cutoff value. If it is the other way around we calculate the base color as if the light is a point light. Then we take the dot product result that we've just calculated ('SpotFactor') and plug it into the forumla described above. This provides the factor that will linearly interpolate 'SpotFactor' between 0 and 1. We multiply it by the point light color and receive the final spot light color.

(lighting.fs:122)

...
for (int i = 0 ; i < gNumSpotLights ; i++) {
    TotalLight += CalcSpotLight(gSpotLights[i], Normal);
}
...

In a similar fashion to point lights we have a loop in the main function that accumulates the contribution of all spot lights into the final pixel color.

(lighting_technique.cpp:367)

void LightingTechnique::SetSpotLights(unsigned int NumLights, const SpotLight* pLights)
{
    glUniform1i(m_numSpotLightsLocation, NumLights);

    for (unsigned int i = 0 ; i < NumLights ; i++) {
        glUniform3f(m_spotLightsLocation[i].Color, pLights[i].Color.x, pLights[i].Color.y, pLights[i].Color.z);
        glUniform1f(m_spotLightsLocation[i].AmbientIntensity, pLights[i].AmbientIntensity);
        glUniform1f(m_spotLightsLocation[i].DiffuseIntensity, pLights[i].DiffuseIntensity);
        glUniform3f(m_spotLightsLocation[i].Position, pLights[i].Position.x, pLights[i].Position.y, pLights[i].Position.z);
        Vector3f Direction = pLights[i].Direction;
        Direction.Normalize();
        glUniform3f(m_spotLightsLocation[i].Direction, Direction.x, Direction.y, Direction.z);
        glUniform1f(m_spotLightsLocation[i].Cutoff, cosf(ToRadian(pLights[i].Cutoff)));
        glUniform1f(m_spotLightsLocation[i].Atten.Constant, pLights[i].Attenuation.Constant);
        glUniform1f(m_spotLightsLocation[i].Atten.Linear, pLights[i].Attenuation.Linear);
        glUniform1f(m_spotLightsLocation[i].Atten.Exp, pLights[i].Attenuation.Exp);
    }
}

This function updates the shader program with an array of SpotLight structures. This is the same as the correspoding function for point lights, with two additions. The light direction vector is also applied to the shader, after it has been normalized. Also, the cutoff value is supplied as an angle by the caller but is passed to the shader as the cosine of that angle (allowing the shader to compare a dot product result directly to that value). Note that the library function cosf() takes the angle in radians so we use the handy macro ToRadian in order to translate it.

Next tutorial


Tutorial 22 - Loading models using the Open Asset Import Library

Tutorial 22:

Loading models using the Open Asset Import Library

Background

We have made it thus far using manually generated models. As you can imagine, the process of specifying the position and other attributes for each and every vertex in an object does not scale well. A box, pyramid and a simple tiled surface are OK, but what about something like a human face? In the real world of games and commercial applications the process of mesh creation is handled by artists that use modeling programs such as Blender, Maya and 3ds Max. These applications provide advanced tools that help the artist create extremely sophisticated models. When the model is complete it is saved to a file in one of the many available formats. The file contains the entire geometry definition of the model. It can now be loaded into a game engine (provided the engine supports the particular format) and its contents can be used to populate vertex and index buffers for rendering. Knowing how to parse the geometry definition file format and load professional models is crucial in order to take your 3D programming to the next level.

Developing the parser on your own can consume quite a lot of your time. If you want to be able to load models from different sources, you will need to study each format and develop a specific parser for it. Some of the formats are simple but some are very complex and you might end up spending too much time on something which is not exactly core 3D programming. Therefore, the approach persued by this tutorial is to use an external library to take care of parsing and loading the models from files.

The Open Asset Import Library, or Assimp, is an open source library that can handle many 3D formats, including the most popular ones. It is portable and available for both Linux and Windows. It is very easy to use and integrate into programs written in C/C++.

There is not much theory in this tutorial. Let's dive right in and see how we can integrate Assimp into our 3D programs.
(before you start, make sure you install Assimp from the link above).

Source walkthru

(mesh.h:50)

class Mesh
{
public:
    Mesh();

    ~Mesh();

    bool LoadMesh(const std::string& Filename);

    void Render();

private:
    bool InitFromScene(const aiScene* pScene, const std::string& Filename);
    void InitMesh(unsigned int Index, const aiMesh* paiMesh);
    bool InitMaterials(const aiScene* pScene, const std::string& Filename);
    void Clear();

#define INVALID_MATERIAL 0xFFFFFFFF

    struct MeshEntry {
        MeshEntry();

        ~MeshEntry();

        bool Init(const std::vector& Vertices,
        const std::vector& Indices);

        GLuint VB;
        GLuint IB;
        unsigned int NumIndices;
        unsigned int MaterialIndex;
    };

    std::vector m_Entries;
    std::vector m_Textures;
};

The Mesh class represents the interface between Assimp and our OpenGL program. An object of this class takes a file name as a parameter to the LoadMesh() function, uses Assimp to load the model and then creates vertex buffers, index bufferss and Texture objects that contain the data of the model in the form that our program understands. In order to render the mesh we use the function Render(). The internal structure of the Mesh class matches the way that Assimp loads models. Assimp uses an aiScene object to represent the loaded mesh. The aiScene object contains mesh structures that encapsulate parts of the model. There must be at least one mesh structure in the aiScene object. Complex models can contain multiple mesh structures. The m_Entries member of the Mesh class is a vector of the MeshEntry struct where each structure corresponds to one mesh structure in the aiScene object. That structure contains the vertex buffer, index buffer and the index of the material. For now, a material is simply a texture and since mesh entries can share materials we have a separate vector for them (m_Textures). MeshEntry::MaterialIndex points into one of the textures in m_Textures.

(mesh.cpp:77)

bool Mesh::LoadMesh(const std::string& Filename)
{
    // Release the previously loaded mesh (if it exists)
    Clear();

    bool Ret = false;
    Assimp::Importer Importer;

    const aiScene* pScene = Importer.ReadFile(Filename.c_str(), aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_FlipUVs);

    if (pScene) {
        Ret = InitFromScene(pScene, Filename);
    }
    else {
        printf("Error parsing '%s': '%s'\n", Filename.c_str(), Importer.GetErrorString());
    }

    return Ret;
}

This function is the starting point of loading the mesh. We create an instance of the Assimp::Importer class on the stack and call its ReadFile function. This function takes two parameters: the full path of the model file and a mask of post processing options. Assimp is capable of performing many useful processing actions on the loaded models. For example, it can generate normals for models that lack them, optimize the structure of the model to improve performance, etc. The full list of options is availabe here. In this tutorial we use three options, aiProcess_Triangulate, which translate models that are made from non triangle polygons into triangle based meshes. For example, a quad mesh can be translated into a triangle mesh by creating two triangles out of each quad. The second option, aiProcess_GenSmoothNormals, generates vertex normals in the case that the original model does not already contain them. Note that the post processing options are basically non overlapping bitmasks so you can combine multiple options by simply ORing their values. The final option, aiProcess_FlipUVsv, flips the texture coordinates along the Y axis. This was required in order to render the Quake model that was used for the demo correctly. You will need to tailor the options that you use according to the input data. If the mesh was loaded successfully, we get a pointer to an aiScene object. This object contains the entire model contents, divided into aiMesh structures. Next we call the InitFromScene() function to initialize the Mesh object.

(mesh.cpp:97)

bool Mesh::InitFromScene(const aiScene* pScene, const std::string& Filename)
{
    m_Entries.resize(pScene->mNumMeshes);
    m_Textures.resize(pScene->mNumMaterials);

    // Initialize the meshes in the scene one by one
    for (unsigned int i = 0 ; i < m_Entries.size() ; i++) {
        const aiMesh* paiMesh = pScene->mMeshes[i];
        InitMesh(i, paiMesh);
    }

    return InitMaterials(pScene, Filename);
}

We start the initialization of the Mesh object by setting up space in the mesh entries and texture vectors for all the meshes and materials we will need. The numbers are available in the aiScene object members mNumMeshes and mNumMaterials, respectively. Next we scan the mMeshes array in the aiScene object and initialize the mesh entries one by one. Finally, the materials are initialized.

(mesh.cpp:111)

void Mesh::InitMesh(unsigned int Index, const aiMesh* paiMesh)
{
    m_Entries[Index].MaterialIndex = paiMesh->mMaterialIndex;

    std::vector Vertices;
    std::vector Indices;
    ...

We start the initialization of the mesh by storing its material index. This will be used during rendering to bind the proper texture. Next we create two STL vectors to store the contents of the vertex and index buffers. A STL vector has a nice property of storing its contents in a continuous buffer. This makes it easy to load the data into the OpenGL buffer (using the glBufferData() function).

(mesh.cpp:118)

    const aiVector3D Zero3D(0.0f, 0.0f, 0.0f);

    for (unsigned int i = 0 ; i < paiMesh->mNumVertices ; i++) {
        const aiVector3D* pPos = &(paiMesh->mVertices[i]);
        const aiVector3D* pNormal = &(paiMesh->mNormals[i]) : &Zero3D;
        const aiVector3D* pTexCoord = paiMesh->HasTextureCoords(0) ? &(paiMesh->mTextureCoords[0][i]) : &Zero3D;

        Vertex v(Vector3f(pPos->x, pPos->y, pPos->z),
                Vector2f(pTexCoord->x, pTexCoord->y),
                Vector3f(pNormal->x, pNormal->y, pNormal->z));

        Vertices.push_back(v);
    }
    ...

Here we prepare the contents of the vertex buffer by populating the Vertices vector. We use the following attributes of the aiMesh class:

  1. mNumVertices - the number of vertices.
  2. mVertices - an array of mNumVertices vectors that contain the position.
  3. mNormals - an array of mNumVertices vectors that contain the vertex normals.
  4. mTextureCoords - an array of mNumVertices vectors that contain the texture coordinates. This is actualy a two dimensional array because each vertex can hold several texture coordinates.

So basically we have three separate arrays that contain everything we need for the vertices and we need to pick out each attribute from its corresponding array in order to build the final Vertex structure. This structure is pushed back to the vertex vector (maintaining the same index as in the three aiMesh arrays). Note that some models do not have texture coordinates so before accessing the mTextureCoords array (and possibly causing a segmentation fault) we check whether texture coordinates exist by calling HasTextureCoords(). In addition, a mesh can contain multiple texture coordinates per vertex. In this tutorial we take the simple way of using only the first texture coordinate. So the mTextureCoords array (which is 2 dimensional) is always accessed on its first row. Therefore, the HasTextureCoords() function is always called for the first row. If a texture coordinate does not exist the Vertex structure will be initialized with the zero vector.

(mesh.cpp:132)

    for (unsigned int i = 0 ; i < paiMesh->mNumFaces ; i++) {
        const aiFace& Face = paiMesh->mFaces[i];
        assert(Face.mNumIndices == 3);
        Indices.push_back(Face.mIndices[0]);
        Indices.push_back(Face.mIndices[1]);
        Indices.push_back(Face.mIndices[2]);
    }
    ...

Next we create the index buffer. The mNumFaces member in the aiMesh class tells us how many polygons exist and the array mFaces contains their data (which is indices of the vertices). First we verify that the number of indices in the polygon is indeed 3 (when loading the model we requested that it will get triangulated but it is always good to check this). Then we extract the indices from the mIndices array and push them into the Indices vector.

(mesh.cpp:140)

    m_Entries[Index].Init(Vertices, Indices);
}

Finally, the MeshEntry structure is initialized using the vertex and index vectors. There is nothing new in the MeshEntry::Init() function so it is not quoted here. It uses glGenBuffer(), glBindBuffer() and glBufferData() to create and populate the vertex and index buffers. See the source file for more details.

(mesh.cpp:143)

bool Mesh::InitMaterials(const aiScene* pScene, const std::string& Filename)
{
    for (unsigned int i = 0 ; i < pScene->mNumMaterials ; i++) {
        const aiMaterial* pMaterial = pScene->mMaterials[i];
       ...

This function loads all the textures that are used by the model. The mNumMaterials attribute in the aiScene object holds the number of materials and mMaterials is an array of pointers to aiMaterials structures (by that size). The aiMaterial structure is a complex beast, but it hides its complexity behind a small number of API calls. In general the material is organized as a stack of textures and between consecutive textures the configured blend and strength function must be applied. For example, the blend function can tell us to add the color from the two textures and the strength function can tell us to multiply the result by half. The blend and strength functions are part of the aiMaterial structure and can be retrieved. To make our life simpler and to match the way our lighting shader currently works we ignore the blend and strength function and simply use the texture as is.

(mesh.cpp:165)

        m_Textures[i] = NULL;
        if (pMaterial->GetTextureCount(aiTextureType_DIFFUSE) > 0) {
            aiString Path;

            if (pMaterial->GetTexture(aiTextureType_DIFFUSE, 0, &Path, NULL, NULL, NULL, NULL, NULL) == AI_SUCCESS) {
                std::string FullPath = Dir + "/" + Path.data;
                m_Textures[i] = new Texture(GL_TEXTURE_2D, FullPath.c_str());

                if (!m_Textures[i]->Load()) {
                    printf("Error loading texture '%s'\n", FullPath.c_str());
                    delete m_Textures[i];
                    m_Textures[i] = NULL;
                    Ret = false;
                }
            }
        }
        ...

A material can contain multiple textures, and not all of them have to contain colors. For example, a texture can be a height map, normal map, displacement map, etc. Since our lighting shader currently uses a single texture for all the light types we are interested only in the diffuse texture. Therefore, we check how many diffuse textures exist using the aiMaterial::GetTextureCount() function. This function takes the type of the texture as a parameter and returns the number of textures of that specific type. If at least one diffuse texture is available we fetch it using the aiMaterial::GetTexture() function. The first parameter to that function is the type. Next comes the index and we always use 0. After that we need to specify the address of a string where the texture file name will go. Finally, there are five address parameters that allow us to fetch various configurations of the texture such as the blend factor, map mode, texture operation, etc. These are optional and we ignore them for now so we just pass NULL. We are interested only in the texture file name and we concatenate it to the directory where the model is located. The directory was retrieved at the start of the function (not quoted here) and the assumption is that the model and the texture are in the same subdirectory. If the directory structure is more complex you may need to search for the texture elsewhere. We create our texture object as usual and load it.

(mesh.cpp:187)

       if (!m_Textures[i]) {
          m_Textures[i] = new Texture(GL_TEXTURE_2D, "../Content/white.png");
          Ret = m_Textures[i]->Load();
       }
    }

    return Ret;
}

The above piece of code is a small workaround to a problem you may encounter if you start loading models you find on the net. Sometimes a model does not include a texture and in cases like that you will not see anything because the color that will be sampled from a non existing texture is by default black. One way to deal with it is to detect this case and treat it with a special case in the shader or a dedicated shader. This tutorial takes a simpler approach of loading a texture that contains a single white texel (you will find this texture in the attached sources). This will make the basic color of all pixels white. It will probably not look great but at least you will see something. This texture takes very little space and allows us to use the same shader for both cases.

(mesh.cpp:197)

void Mesh::Render()
{
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    glEnableVertexAttribArray(2);

    for (unsigned int i = 0 ; i < m_Entries.size() ; i++) {
        glBindBuffer(GL_ARRAY_BUFFER, m_Entries[i].VB);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)12);
        glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)20);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_Entries[i].IB);

        const unsigned int MaterialIndex = m_Entries[i].MaterialIndex;

        if (MaterialIndex < m_Textures.size() && m_Textures[MaterialIndex]) {
            m_Textures[MaterialIndex]->Bind(GL_TEXTURE0);
        }

        glDrawElements(GL_TRIANGLES, m_Entries[i].NumIndices, GL_UNSIGNED_INT, 0);
    }

    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
    glDisableVertexAttribArray(2);
}

This function encapsulates the rendering of a mesh and separates it from the main application (in previous tutorials it was part of the application code itself). The m_Entries array is scanned and the vertex buffer and index buffer in each node are bound. The material index of the node is used to fetch the texture object from the m_Texture array and the texture is also bound. Finally, the draw command is executed. Now you can have multiple mesh objects that have been loaded from files and render them one by one by calling the Mesh::Render() function.

(glut_backend.cpp:112)

glEnable(GL_DEPTH_TEST);

The last thing we need to study is something that was left out in previous tutorials. If you go ahead and load models using the code above you will probably encounter visual anomalies with your scene. The reason is that triangles that are further from the camera are drawn on top of the closer ones. In order to fix this we need to enable the famous depth test (a.k.a Z-test). When the depth test is enabled the rasterizer compares the depth of each pixel prior to rendering with the existing pixel on the same location on the screen. The pixel whose color is eventually used is the one who "wins" the depth test (i.e. closer to the camera). The depth test is not enabled by default and the code above takes care of that (part of the OpenGL initialization code in the function GLUTBackendRun()). This is just one of three pieces of code that are required for the depth test (see below).

(glut_backend.cpp:73)

glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH);

The second piece is the initialization of the depth buffer. In order to compare depth between two pixels the depth of the "old" pixel must be stored somewhere (the depth of the "new" pixel is available because it was passed from the vertex shader). For this purpose we have a special buffer known as the depth buffer (or Z buffer). It has the same proporations as the screen so that each pixel in the color buffer has a corresponding slot in the depth buffer. That slot always stores the depth of the closest pixel and it is used in the depth test for the comparison.

(tutorial22.cpp:101)

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

The last thing we need to do is to clear the depth buffer at the start of a new frame. If we don't do that the buffer will contain old values from the previous frame and the depth of the pixels from the new frame will be compared against the depth of the pixels from the previous frame. As you can imagine, this will cause serious corruptions (try!). The glClear() function takes a bitmask of the buffers it needs to operate on. Up until now we've only cleared the color buffer. Now it's time to clear the depth buffer as well.

Next tutorial


Tutorial 23 - Shadow Mapping - Part 1

Tutorial 23:

Shadow Mapping - Part 1

Background

The concept of shadow is inseparable from the concept of light, as you need light in order to cast a shadow. There are many techniques that generate shadows and in this two part tutorial we are going to study one of the more basic and simple ones - shadow mapping.

When it comes to rasterization and shadows the question that you ask yourself is - is this pixel located in shadow or not? Let's ask this differently - does the path from the light source to the pixel goes through another object or not? If it does - the pixel is probably in shadow (assuming the other object is not transparent...), and if not - the pixel is not in shadow. In a way, this question is similar to the question we asked ourselves in the previous tutorial - how to make sure that when two objects overlap each other we will see the closer one. If we place the camera for a moment at the light origin the two questions become one. We want the pixels that fail the depth test (i.e. the ones that are further away and have pixels before them) to be in shadow. Only the pixels that win the depth test must be in light. They are the ones that are in direct contact with the light source and there is nothing in between that conceals them. In a nutshell, this is the idea behind shadow mapping.

So it looks like the depth test can help us detect whether a pixel is in shadow or not but there is a problem. The camera and the light are not always positioned in the same place. The depth test is normally used to solve the visibility problem from the camera point of view, so how can we harness it for shadow detection when the light is located further away? The solution is to render the scene twice. First from the light point of view. The results of this render pass don't reach the color buffer. Instead, the closest depth values are rendered into an application created depth buffer (instead of the one that is automatically generated by GLUT). In the second pass the scene is rendered as usual from the camera point of view. The depth buffer that we've created is bound to the fragment shader for reading. For each pixel we fetch the corresponding depth from that depth buffer. We also calculate the depth of this pixel from the light point of view. Sometimes the two depth values will be identical. This is the case where this pixel was closest to the light so its depth value ended up in the depth buffer. If that happen we consider the pixel as if it is in light and calculate its color as usual. If the depth values are different it means there is another pixel that covers this pixel when looking at it from the light position. In this case we add some shadow factor to the color calculation in order to simulate the shadow effect. Take a look at the following picture:

Our scene is made up of two objects - the surface and the cube. The light source is located at the top left corner and is pointing at the cube. In the first pass we render into the depth buffer from the point of view of the light source. Let's focus on the three points A, B and C. When B is rendered its depth value goes into the depth buffer. The reason is that there is nothing in between the point and the light. By default, it is the closest point to the light on that line. However, when A and C are rendered they "compete" on the exact same spot in the depth buffer. Both points are on the same straight line from the light source so after perspective projection takes place the rasterizer finds out both points need to go to the same pixel on the screen. This is the depth test and point C "wins" it.

In the second pass we render the surface and the cube from the camera point of view. In addition to everything we have done in our lighting shader per pixel we also calculate the distance from the light source to the pixel and compare it to the corresponding value in the depth buffer. When we rasterize point B the two values should roughly be same (some differences are expected due to differences in interpolation and floating point precision issues). Therefore, we decide that B is not in shadow and act accordingly. When we rasterize point A we find out that the stored depth value is clearly smaller than the depth of A. Therefore, we decide that A is in shadow and apply some shadow factor to it in order to get it darker than usual.

This, in a nutshell, is the shadow mapping algorithm (the depth buffer that we render to in the first pass is called the "shadow map"). We are going to study it in two stages. In the first stage (this tutorial) we will learn how to render into the shadow map. The process of rendering something (depth, color, etc) into an application created texture is known as 'render to texture'. We will display the shadow map on the screen using a simple texture mapping technique that we are already familiar with. This is a good debugging step as getting the shadow map correct is crucial in order to get the complete shadow effect working correctly. In the next tutorial we will see how to use the shadow map in order to do the "in shadow/not in shadow" decision.

The sources of this tutorial include a simple quad mesh that can be used to display the shadow map. The quad is made up of two triangles and the texture coordinates are set up such that they cover the entire texture space. When the quad is rendered the texture coordinates are interpolated by the rasterizer, allowing you to sample an entire texture and display it on screen.

Source walkthru

(shadow_map_fbo.h:50)

class ShadowMapFBO
{
    public:
        ShadowMapFBO();

        ~ShadowMapFBO();

        bool Init(unsigned int WindowWidth, unsigned int WindowHeight);

        void BindForWriting();

        void BindForReading(GLenum TextureUnit);

    private:
        GLuint m_fbo;
        GLuint m_shadowMap;
};

The results of the 3D pipeline in OpenGL end up in something which is called a 'framebuffer object' (a.k.a FBO). This concept wraps within it the color buffer (which is displayed on screen), the depth buffer as well as a few other buffers for additional usages. When glutInitDisplayMode() is called it creates the default framebuffer using the specified parameters. This framebuffer is managed by the windowing system and cannot be deleted by OpenGL. In addition to the default framebuffer, an application can create FBOs of its own. These objects can be manipulated and used for various techniques under the control of the application. The ShadowMapFBO class provides an easy to use interface to a FBO which will be used for the shadow mapping technique. Internally, this class contains two OpenGL handles. The handle 'm_fbo' represents the actual FBO. The FBO encapsulates within it the entire state of the framebuffer. Once this object is created and configured properly we can change framebuffers by simply binding a different object. Note that only the default framebuffer can be used to display something on the screen. The framebuffers created by the application can only be used for "offscreen rendering". This can be an intermediate rendering pass (e.g. our shadow mapping buffer) which can later be used for the "real" rendering pass that goes to the screen.

In itself, the framebuffer is just a placeholder. To make it usable we need to attach textures to one or more of the available attachment points. The textures contain the actual storage space of the framebuffer. OpenGL defines the following attachment points:

  1. COLOR_ATTACHMENTi - the texture that will be attached here will receive the color that comes out of the fragment shader. The 'i' suffix means that there can be multiple textures attached as color attachments simultaneously. There is a mechanism in the fragment shader that enables rendering into several color buffers at the same time.
  2. DEPTH_ATTACHMENT - the texture that will be attached here will receive the results of the depth test.
  3. STENCIL_ATTACHMENT - the texture that will be attached here will serve as the stencil buffer. The stencil buffer enables limiting the area of rasterization and can be used for various techniques.
  4. DEPTH_STENCIL_ATTACHMENT - this one is simply a combination of depth and stencil buffers as the two are often used together.

For the shadow mapping technique we will only need a depth buffer. The member attribute 'm_shadowMap' is the handle of the texture that will be attached to the DEPTH_ATTACHMENT attachment point. The ShadowMapFBO also provides a couple of methods that will be used in the main render function. We will call BindForWriting() before rendering into the shadow map and BindForReading() when starting the second rendering pass.

(shadow_map_fbo.cpp:43)

glGenFramebuffers(1, &m_fbo);

Here we create the FBO. Same as in textures and buffers, we specify the address of an array of GLuints and its size. The array is populated with the handles.

(shadow_map_fbo.cpp:46)

glGenTextures(1, &m_shadowMap);
glBindTexture(GL_TEXTURE_2D, m_shadowMap);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, WindowWidth, WindowHeight, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

Next we create the texture that will serve as the shadow map. In general, this is a standard 2D texture with some specific configuration to make it suitable for its purpose:

  1. The internal format is GL_DEPTH_COMPONENT. This is different from the previous use of this function where the internal format was usually one of the color types (e.g. GL_RGB). GL_DEPTH_COMPONENT means a single floating point number that represents the normalized depth.
  2. The last parameter of glTexImage2D is null. This means that we are not supplying any data by which to initialize the buffer. This makes sense knowing that we want the buffer to contain the depth values of each frame and each frame is a bit different. Whenever we start a new frame we will use glClear() to clear out the buffer. This is all the initialization that we need for the content.
  3. We tell OpenGL that in case a texture coordinate goes out of bound it needs to clamp it to the [0,1] range. This can happen when the projection window from the camera point of view contains more than the projection window from the light point of view. To avoid strange artifacts such as the shadow repeating itself elsewhere (due to wraparound) we clamp the texture coordinates.

(shadow_map_fbo.cpp:54)

glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);

We have generated the FBO, the texture object and also configured the texture object for shadow mapping. Now we need to attach the texture object to the FBO. The first thing we need to do is to bind the FBO. This will make it "current" and then all future FBO operations will apply to it. This function takes the FBO handle and the desired target. The target can be GL_FRAMEBUFFER, GL_DRAW_FRAMEBUFFER or GL_READ_FRAMEBUFFER. GL_READ_FRAMEBUFFER is used when we want to read from the FBO using glReadPixels (not in this tutorial). GL_DRAW_FRAMEBUFFER is used when we want to render into the FBO. When we use GL_FRAMEBUFFER both the reading and writing state is updated and this is the recommended way for initializing the FBO. We will use GL_DRAW_FRAMEBUFFER when we actually start to render.

(shadow_map_fbo.cpp:55)

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_shadowMap, 0);

Here we attach the shadow map texture to the depth attachment point of the FBO. The last parameter to this function indicates the mipmap layer to use. Mipmapping is a texture mapping feature where a texture is represented at different resolutions, starting from the highest resolution at mipmap 0 and decreasing resolutions in mipmaps 1-N. The combination of a mipmapped texture and trilinear filtering provides more pleasant results by combining texels from neighboring mipmap levels (when no single level is perfect). Here we have a single mipmap level so we use 0. We provide the shadow map handle as the fourth parameter. If we use 0 here it will detach the current texture from the specified attachment point (depth in the case above).

(shadow_map_fbo.cpp:58)

glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);

Since we are not going to render into the color buffer (only into the depth) we explicitly specify it using the above call. By default, the color buffer target is set to GL_COLOR_ATTACHMENT0, but our FBO isn't even going to contain a color buffer. Therefore, it is better to tell OpenGL our intentions explicitly. The valid parameters to this functions are GL_NONE and GL_COLOR_ATTACHMENT0 to GL_COLOR_ATTACHMENTm where 'm' is GL_MAX_COLOR_ATTACHMENTS - 1. These parameters are valid only for FBOs. If the default framebuffer is used the valid parameters are GL_NONE, GL_FRONT_LEFT, GL_FRONT_RIGHT, GL_BACK_LEFT and GL_BACK_RIGHT. These allow you to render directly into the front or back buffers (where each one has a left and right buffer). We also set the read buffer to GL_NONE (remember, we are not going to call one of the glReadPixel APIs). This is mainly to avoid problems with GPUs that support only OpenGL 3.x and not 4.x.

(shadow_map_fbo.cpp:61)

GLenum Status = glCheckFramebufferStatus(GL_FRAMEBUFFER);

if (Status != GL_FRAMEBUFFER_COMPLETE) {
    printf("FB error, status: 0x%x\n", Status);
    return false;
}

When the configuration of the FBO is finished it is very important to verify that its state is what the OpenGL spec defines as "complete". This means that no error was detected and that the framebuffer can now be used. The code above checks that.

(shadow_map_fbo.cpp:72)

void ShadowMapFBO::BindForWriting()
{
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo);
}

We will need to toggle between rendering into the shadow map and rendering into the default framebuffer. In the second pass we will also need to bind our shadow map for input. This function and the next one provide easy to use wrappers to do that. The above function simply binds the FBO for writing as we did earlier. We will call it before the first render pass...

(shadow_map_fbo.cpp:78)

void ShadowMapFBO::BindForReading(GLenum TextureUnit)
{
    glActiveTexture(TextureUnit);
    glBindTexture(GL_TEXTURE_2D, m_shadowMap);
}

...and this function will be used before the second render pass to bind the shadow map for reading. Note that we bind specifically the texture object, rather than the FBO itself. This function takes the texture unit to which the shadow map will be bound. The texture unit index must be synchronized with the shader (since the shader has a sampler2D uniform variable to access the texture). It is very important to note that while glActiveTexture takes the texture index as an enum (e.g. GL_TEXTURE0, GL_TEXTURE1, etc), the shader needs simply the index itself (0, 1, etc). This can be the source of many bugs (believe me, I know).

(shadow_map.vs)

#version 330

layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
layout (location = 2) in vec3 Normal;

uniform mat4 gWVP;

out vec2 TexCoordOut;

void main()
{
    gl_Position = gWVP * vec4(Position, 1.0);
    TexCoordOut = TexCoord;
}

We are going to use the same shader program for both render passes. The vertex shader will be used by both passes while the fragment shader will be used only by the second pass. Since we are disabling writing to the color buffer in the first pass the fragment shader will simply be left unused there. The vertex shader above is very simple. It generates the clip space coordinate by multiplying the local space position by the WVP matrix and passes through the texture coordinates. In the first pass the texture coordinates are redundant (no fragment shader). However, there is no real impact and it is simpler to share the vertex shader. As you can see, from the point of view of the shader it makes no difference whether this is a Z pass or a real render pass. What makes the difference is that the application passes a light point of view WVP matrix in the first pass and a camera point of view WVP matrix in the second pass. In the first pass the Z buffer will be populated by the closest Z values from the light point of view and on the second pass from the camera point of view. In the second pass we also need the texture coordinates in the fragment shader because we will sample from the shadow map (which is now input to the shader).

(shadow_map.fs)

#version 330

in vec2 TexCoordOut;
uniform sampler2D gShadowMap;

out vec4 FragColor;

void main()
{
    float Depth = texture(gShadowMap, TexCoordOut).x;
    Depth = 1.0 - (1.0 - Depth) * 25.0;
    FragColor = vec4(Depth);
}

This is the fragment shader that is used to display the shadow map in the render pass. The 2D texture coordinates are used to fetch the depth value from the shadow map. The shadow map texture was created with the type GL_DEPTH_COMPONENT as its internal format. This means that the basic texel is a single floating point value and not a color. This is why '.x' is used during sampling. The perspective projection matrix has a known behavior that when it normalizes the Z in the position vector it reserves more values in the [0,1] range to the closer locations rather than the locations that are further away from the camera. The rational is to allow greater Z precision as we get closer to the camera because errors here are more noticeable. When we display the contents of the depth buffer we may run into a case where the resulting image is not clear enough. Therefore, after we sample the depth from the shadow map we sharpen it by scaling the distance of the current point to the far edge (where Z is 1.0) and then substracting the result from 1.0 again. This amplifies the range and improves the final image. We use the new depth value to create a color by broadcasting it across all the color channels. This means we will get some variation of gray (white at the far clipping plane and black at the near clipping plane).

Now let's see how to combine the pieces of code above and create the application.

(tutorial23.cpp:106)

virtual void RenderSceneCB()
{
    m_pGameCamera->OnRender();
    m_scale += 0.05f;

    ShadowMapPass();
    RenderPass();

    glutSwapBuffers();
}

The main render function has become much simpler as most functionality moved to other functions. First we take care of the "global" stuff like updating the position of the camera and the class member which is used to rotate the object. Then we call a function to render into the shadow map texture followed by a function to display the results. Finally, glutSwapBuffer() is called to display it to the screen.

(tutorial23.cpp:117)

virtual void ShadowMapPass()
{
    m_shadowMapFBO.BindForWriting();

    glClear(GL_DEPTH_BUFFER_BIT);

    Pipeline p;
    p.Scale(0.1f, 0.1f, 0.1f);
    p.Rotate(0.0f, m_scale, 0.0f);
    p.WorldPos(0.0f, 0.0f, 5.0f);
    p.SetCamera(m_spotLight.Position, m_spotLight.Direction, Vector3f(0.0f, 1.0f, 0.0f));
    p.SetPerspectiveProj(20.0f, WINDOW_WIDTH, WINDOW_HEIGHT, 1.0f, 50.0f);
    m_pShadowMapTech->SetWVP(p.GetWVPTrans());

    m_pMesh->Render();

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

We start the shadow map pass by binding in the shadow map FBO. From now on all the depth values will go into our shadow map texture and color writes will be discarded. We clear the depth buffer (only) before we start doing anything. Then we set up the pipeline class in order to render the mesh (a tank from Quake2 is supplied with the tutorial source). The single point worth noticing here is that the camera is updated based on the position and direction of the spot light. We render the mesh and then switch back to the default framebuffer by binding FBO zero.

(tutorial23.cpp:135)

virtual void RenderPass()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    m_pShadowMapTech->SetTextureUnit(0);
    m_shadowMapFBO.BindForReading(GL_TEXTURE0);

    Pipeline p;
    p.Scale(5.0f, 5.0f, 5.0f);
    p.WorldPos(0.0f, 0.0f, 10.0f);
    p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());
    p.SetPerspectiveProj(30.0f, WINDOW_WIDTH, WINDOW_HEIGHT, 1.0f, 50.0f);
    m_pShadowMapTech->SetWVP(p.GetWVPTrans());
    m_pQuad->Render();
}

The render pass starts by clearing both color and depth buffers. These buffers belond to the default framebuffer. We tell the shader to use texture unit 0 and bind the shadow map texture for reading on texture unit 0. From here on everything is as usual. We scale the quad up, place it directly infront of the camera and render it. During rasterization the shadow map is sampled and displayed.

Note: in this tutorial's code we no longer automatically load a white texture when the mesh file does not specify one. The reason is to be able to bind the shadow map instead. If a mesh does not contain a texture we simply bind none and this allows the calling code to bind its own texture.

Next tutorial


Tutorial 24 - Shadow Mapping - Part 2

Tutorial 24:

Shadow Mapping - Part 2

Background

In the previous tutorial we learned the basic principle behind the shadow mapping technique and saw how to render the depth into a texture and later display it on the screen by sampling from the depth buffer. In this tutorial we will see how to use this capability and display the shadow itself.

We know that shadow mapping is a two-pass technique and that in the first pass the scene is rendered from the point of view of the light. Let's review what happens to the Z component of the position vector during that first pass:

  1. The position of the vertices that are fed into the vertex shader are generally specified in local space.
  2. The vertex shader transforms the position from local space to clip space and forwards it down the pipeline (see tutorial 12 if you need a refresher about clip space).
  3. The rasterizer performs perspective divide (a division of the position vector by its W component). This takes the position vector from clip space to NDC space. In NDC space everything which ends up on the screen has a X, Y and Z components in the range [-1,1]. Things outside these ranges are clipped away.
  4. The rasterizer maps the X and Y of the position vector to the dimensions of the framebuffer (e.g. 800x600, 1024x768, etc). The results are the screen space coordinates of the position vector.
  5. The rasterizer takes the screen space coordinates of the three triangle vertices and interpolates them to create the unique coordinates for each pixel that the triangle covers. The Z value (still in the [-1,1] range) is also interpolated so every pixel has its own depth.
  6. Since we disabled color writes in the first pass the fragment shader is disabled. The depth test, however, still executes. To compare the Z value of the current pixel with the one in the buffer the screen space coordinates of the pixel are used to fetch the depth from the buffer. If the depth of the new pixel is smaller than the stored one the buffer is updated (and if color writes were enabled the color buffer would have also been updated).

In the process above we saw how the depth value from the light point of view is calculated and stored. In the second pass we render from the camera point of view so naturally we get a different depth. But we need both depth values - one to get the triangles ordered correctly on the screen and the other to check what is inside the shadow and what is not. The trick in shadow mapping is to maintain two position vectors and two WVP matrices while traveling through the 3D pipeline. One WVP matrix is calculated from the light point of view and the other from the camera point of view. The vertex shader gets one position vector in local space as usual, but it outputs two vectors:

  1. The builtin gl_Position which is the result of transforming the position by the camera WVP matrix.
  2. A "plain" vector which is the result of transforming the position by the light WVP matrix.

The first vector will go through above process (--> NDC space...etc) and these will be used for the regular rasterization. The second vector will simply be interpolated by the rasterizer across the triangle face and each fragment shader invocation will be provided with its own value. So now for each physical pixel we also have a clip space coordinate of the same point in the original triangle when looking at it from the light point of view. It is very likely that the physical pixels from the two point of views are different but the general location in the triangle is the same. All that remains is to somehow use that clip space coordinate in order to fetch the depth value from the shadow map. After that we can compare the depth to the one in the clip space coordinate and if the stored depth is smaller then it means the pixel is in shadow (because another pixel had the same clip space coordinate but with a smaller depth).

So how can we fetch the depth in the fragment shader using the clip space coordinate that was calculated by trasforming the position by the light WVP matrix? When we start out we are basically in step 2 above.

  1. Since the fragment shader receives the clip space coordinate as a standard vertex attribute the rasterizer does not perform perspective divide on it (only what goes through gl_Position). But this is something that is very easy to do manually in the shader. We divide the coordinate by its W component and get a coordinate in NDC space.
  2. We know that in NDC the X and Y range from -1 to 1. In step 4 above the rasterizer maps the NDC coordinates to screen space and uses them to store the depth. We are going to sample the depth and for that we need a texture coordinate in the range [0,1]. If we linearly map the range [-1,1] to [0,1] we will get a texture coordinate that will map to the same location in the shadow map. Example: the X in NDC is zero and the width of the texture is 800. Zero in NDC needs to be mapped to 0.5 in the texture coordinate space (because it is half way between -1 and 1). The texture coordinate 0.5 is mapped to 400 in the texture which is the same location that is calculated by the rasterizer when it performs screen space transform.
  3. Transforming X and Y from NDC space to texture space is done as follows:
    • u = 0.5 * X + 0.5
    • v = 0.5 * Y + 0.5

Source walkthru

(lighting_technique.h:80)

class LightingTechnique : public Technique {
    public:
    ...
        void SetLightWVP(const Matrix4f& LightWVP);
        void SetShadowMapTextureUnit(unsigned int TextureUnit);
    ...
    private:
        GLuint m_LightWVPLocation;
        GLuint m_shadowMapLocation;
...

The lighting technique needs a couple of new attributes. A WVP matrix that is calculated from the light point of view and a texture unit for the shadow map. We will continue using texture unit 0 for the regular texture that is mapped on the object and will dedicate texture unit 1 for the shadow map.

(lighting.vs)

#version 330

layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
layout (location = 2) in vec3 Normal;

uniform mat4 gWVP;
uniform mat4 gLightWVP;
uniform mat4 gWorld;

out vec4 LightSpacePos;
out vec2 TexCoord0;
out vec3 Normal0;
out vec3 WorldPos0;

void main()
{
    gl_Position = gWVP * vec4(Position, 1.0);
    LightSpacePos = gLightWVP * vec4(Position, 1.0);
    TexCoord0 = TexCoord;
    Normal0 = (gWorld * vec4(Normal, 0.0)).xyz;
    WorldPos0 = (gWorld * vec4(Position, 1.0)).xyz;
}

This is the updated vertex shader of the LightingTechnique class with the additions marked in bold text. We have an additional WVP matrix uniform variable and a 4-vector as output which contains the clip space coordinates calculated by transforming the position by the light WVP matrix. As you can see, in the vertex shader of the first pass the variable gWVP contained the same matrix as gLightWVP here and gl_Position there got the same value as LightSpacePos here. But since LightSpacePos is just a standard vector it does not get an automatic perspective division as gl_Position. We will do this manually in the fragment shader below.

(lighting.fs:58)

float CalcShadowFactor(vec4 LightSpacePos)
{
    vec3 ProjCoords = LightSpacePos.xyz / LightSpacePos.w;
    vec2 UVCoords;
    UVCoords.x = 0.5 * ProjCoords.x + 0.5;
    UVCoords.y = 0.5 * ProjCoords.y + 0.5;
    float z = 0.5 * ProjCoords.z + 0.5;
    float Depth = texture(gShadowMap, UVCoords).x;
    if (Depth < (z + 0.00001))
        return 0.5;
    else
        return 1.0;
}

This function is used in the fragment shader to calculate the shadow factor of a pixel. The shadow factor is a new factor in the light equation. We simply multiply the result of our current light equation by that factor and this causes some attenuation of the light in pixels that are determined to be shadowed. The function takes the interpolated LightSpacePos vector that was passed from the vertex shader. The first step is to perform perspective division - we divide the XYZ components by the W component. This transfers the vector to NDC space. Next we prepare a 2D coordinate vector to be used as the texture coordinate and initialize it by transforming the LightSpacePos vector from NDC to texture space according to the equation in the background section. The texture coordinates are used to fetch the depth from the shadow map. This is the depth of the closest location from all the points in the scene that are projected to this pixel. We compare that depth to the depth of the current pixel and if it is smaller return a shadow factor of 0.5, else the shadow factor is 1.0 (no shadow). The Z from the NDC space also goes through transformation from the (-1,1) range to (0,1) range because we have to be in the same space when we compare. Notice that we add a small epsilon value to the current pixel's depth. This is to avoid precision errors that are inherent when dealing with floating point values.

(lighting.fs:72)

vec4 CalcLightInternal(BaseLight Light, vec3 LightDirection, vec3 Normal, float ShadowFactor)
{
    ...
    return (AmbientColor + ShadowFactor * (DiffuseColor + SpecularColor));
}

The changes to the core function that does the lighting calculations are minimal. The caller must pass the shadow factor and the diffuse and specular colors are modulated by that factor. Ambient light is not affected by the shadow because by definition, it is everywhere.

(lighting.fs:97)

vec4 CalcDirectionalLight(vec3 Normal)
{
    return CalcLightInternal(gDirectionalLight.Base, gDirectionalLight.Direction, Normal, 1.0);
}

Our shadow mapping implementation is currently limited to spot lights. In order to calculate the WVP matrix of the light it needs both a position and a direction which point light and directional light lack. We will add the missing features in the future but for now we simply use a shadow factor of 1 for the directional light.

(lighting.fs:102)

vec4 CalcPointLight(struct PointLight l, vec3 Normal, vec4 LightSpacePos)
{
    vec3 LightDirection = WorldPos0 - l.Position;
    float Distance = length(LightDirection);
    LightDirection = normalize(LightDirection);
    float ShadowFactor = CalcShadowFactor(LightSpacePos);

    vec4 Color = CalcLightInternal(l.Base, LightDirection, Normal, ShadowFactor);
    float Attenuation = l.Atten.Constant +
        l.Atten.Linear * Distance +
        l.Atten.Exp * Distance * Distance;

    return Color / Attenuation;
}

Since the spot light is actually calculated using a point light this function now takes the extra parameter of the light space position and calculates the shadow factor. It passes it on to CalcLightInternal() which uses it as described above.

(lighting.fs:117)

vec4 CalcSpotLight(struct SpotLight l, vec3 Normal, vec4 LightSpacePos)
{
    vec3 LightToPixel = normalize(WorldPos0 - l.Base.Position);
    float SpotFactor = dot(LightToPixel, l.Direction);

    if (SpotFactor > l.Cutoff) {
        vec4 Color = CalcPointLight(l.Base, Normal, LightSpacePos);
        return Color * (1.0 - (1.0 - SpotFactor) * 1.0/(1.0 - l.Cutoff));
    }
    else {
        return vec4(0,0,0,0);
    }
}

The spot light function simply passes through the light space position to the point light function.

(lighting.fs:131)

void main()
{
    vec3 Normal = normalize(Normal0);
    vec4 TotalLight = CalcDirectionalLight(Normal);

    for (int i = 0 ; i < gNumPointLights ; i++) {
        TotalLight += CalcPointLight(gPointLights[i], Normal, LightSpacePos);
    }

    for (int i = 0 ; i < gNumSpotLights ; i++) {
        TotalLight += CalcSpotLight(gSpotLights[i], Normal, LightSpacePos);
    }

    vec4 SampledColor = texture2D(gSampler, TexCoord0.xy);
    FragColor = SampledColor * TotalLight;
}

Finally, the main function of the fragment shader. We are using the same light space position vector for both spot and point lights even though only spot lights are supported. This limitation will be fixed in the future. We have finished reviewing the changes in the lighting technique and will now take a look at the application code.

(tutorial24.cpp:86)

m_pLightingEffect = new LightingTechnique();

if (!m_pLightingEffect->Init()) {
    printf("Error initializing the lighting technique\n");
    return false;
}

m_pLightingEffect->Enable();
m_pLightingEffect->SetSpotLights(1, &m_spotLight);
m_pLightingEffect->SetTextureUnit(0);
m_pLightingEffect->SetShadowMapTextureUnit(1);

This code which sets up the LightingTechnique is part of the Init() function so it is executed only once during startup. Here we set the uniform values that will not change from frame to frame. Our standard texture unit for the texture which belongs to the mesh is 0 and we dedicate texture unit 1 for the shadow map. Remember that the shader program must be enabled before its uniform variables are set up and they remain persistent as long as the program is not relinked. This is convenient because it allows you to switch between shader programs and only worry about the uniform variables that are dynamic. Uniform variables that never change can be set once during startup.

(tutorial24.cpp:129)

virtual void RenderSceneCB()
{
    m_pGameCamera->OnRender();
    m_scale += 0.05f;

    ShadowMapPass();
    RenderPass();

    glutSwapBuffers();
}

Nothing has changed in the main render function - first we take care of the global stuff such as the camera and the scale factor which is used for rotating the mesh. Then we do the shadow pass followed by the render pass.

(tutorial24.cpp:141)

virtual void ShadowMapPass()
{
    m_shadowMapFBO.BindForWriting();

    glClear(GL_DEPTH_BUFFER_BIT);

    m_pShadowMapEffect->Enable();

    Pipeline p;
    p.Scale(0.1f, 0.1f, 0.1f);
    p.Rotate(0.0f, m_scale, 0.0f);
    p.WorldPos(0.0f, 0.0f, 3.0f);
    p.SetCamera(m_spotLight.Position, m_spotLight.Direction, Vector3f(0.0f, 1.0f, 0.0f));
    p.SetPerspectiveProj(30.0f, WINDOW_WIDTH, WINDOW_HEIGHT, 1.0f, 50.0f);
    m_pShadowMapEffect->SetWVP(p.GetWVPTrans());
    m_pMesh->Render();

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

This is basically the same shadow pass from the previous tutorial. The only change is that we enable the shadow map technique each time because we toggle between this technique and the lighting technique. Note that even though our scene contains both a mesh and a quad that serves as the ground, only the mesh is rendered into the shadow map. The reason is that the ground cannot cast shadows. This is one of the optimizations that we can do when we know something about the type of the object.

(tutorial24.cpp:168)

virtual void RenderPass()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    m_pLightingEffect->Enable();

    m_pLightingEffect->SetEyeWorldPos(m_pGameCamera->GetPos());
    m_shadowMapFBO.BindForReading(GL_TEXTURE1);

    Pipeline p;
    p.SetPerspectiveProj(30.0f, WINDOW_WIDTH, WINDOW_HEIGHT, 1.0f, 50.0f);

    p.Scale(10.0f, 10.0f, 10.0f);
    p.WorldPos(0.0f, 0.0f, 1.0f);
    p.Rotate(90.0f, 0.0f, 0.0f);
    p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());
    m_pLightingEffect->SetWVP(p.GetWVPTrans());
    m_pLightingEffect->SetWorldMatrix(p.GetWorldTrans());
    p.SetCamera(m_spotLight.Position, m_spotLight.Direction, Vector3f(0.0f, 1.0f, 0.0f));
    m_pLightingEffect->SetLightWVP(p.GetWVPTrans());
    m_pGroundTex->Bind(GL_TEXTURE0);
    m_pQuad->Render();

    p.Scale(0.1f, 0.1f, 0.1f);
    p.Rotate(0.0f, m_scale, 0.0f);
    p.WorldPos(0.0f, 0.0f, 3.0f);
    p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());
    m_pLightingEffect->SetWVP(p.GetWVPTrans());
    m_pLightingEffect->SetWorldMatrix(p.GetWorldTrans());
    p.SetCamera(m_spotLight.Position, m_spotLight.Direction, Vector3f(0.0f, 1.0f, 0.0f));
    m_pLightingEffect->SetLightWVP(p.GetWVPTrans());
    m_pMesh->Render();
}

The render pass starts the same way as in the previous tutorial - we clear both the depth and color buffers, replace the shadow map technique with the lighting technique and bind the shadow map frame buffer object for reading on texture unit 1. Next we render the quad so that it will serve as the ground on which the shadow will appear. It is scaled up a bit, rotated 90 degrees around the X axis (because originally it is facing the camera) and positioned. Note how the WVP is updated based on the location of the camera but for the light WVP we move the camera to the light position. Since the quad model comes without its own texture we manually bind a texture here. The mesh is rendered in the same way.

Next tutorial


Tutorial 25 - SkyBox

Tutorial 25:

SkyBox

Background

A skybox is a technique that makes the scene looks bigger and more impressive by wrapping the viewer with a texture that goes around the camera 360 degrees. The texture is often a combination between the sky and a terrain type such as mountains, skyscapers, etc. As the player explores his surroundings he keeps seeing a part of the skybox hovering above the real models and filling up all those empty pixels. Here's an example of a skybox from the game Half-Life:

The idea behind the skybox is to render a big cube and place the viewer at its center. As the camera moves the cube follows it so that the viewer can never reach the "horizon" of the scene. This is similar to real life where we see the sky "touch" the earth on the horizon but when we move toward the horizon it remains at the same distance from us (pending on land type, etc).

A special type of texture is mapped on the cube. This texture is created in such a way that if it is cut and folded properly it creates a box where the contents along the edges of the internal faces are perfectly aligned with one another and create a sense of continuity for someone who is located inside the box. For example, see the following texture:

If we cut away the white margins of the texture above and fold the remaining parts along the white lines we will get a box with the required properties. OpenGL calls such a texture a Cubemap.

In order to sample from the cubemap we will use a 3D texture coordinate instead of the 2D coordinate that we have been using thus far. The texture sampler will use this 3D coordinate as a vector and will first find out which of the six faces of the cubemap contains the required texel and then fetch it from within that face. This process can be seen in the following picture which is taken from above (looking down at the box):

The proper face is selected based on the highest magnitude component of the texture coordinate. In the above example we see that Z has the largest magnitude (Y cannot be seen but let's assume it is smaller than Z). Since Z has a positive sign the texture sampler will use the face entitled 'PosZ' and will fetch the texel from there (the remaining faces are 'NegZ', 'PosX', 'NegX', 'PosY' and 'NegY').

The skybox technique can actually be implemented using a sphere as well as a box. The only difference is that the length of all possible direction vectors in a sphere is equal (since they represent the radius of the sphere) while in a box there are different lengths. The mechanism for texel fetching remains the same. A skybox which uses a sphere is sometimes called a skydome. This is what we will use for the demo of this tutorial. You should play with both options and see what works best for you.

Source walkthru

(ogldev_cubemap_texture.h:28)

class CubemapTexture
{
public:

    CubemapTexture(const string& Directory,
        const string& PosXFilename,
        const string& NegXFilename,
        const string& PosYFilename,
        const string& NegYFilename,
        const string& PosZFilename,
        const string& NegZFilename);

    ~CubemapTexture();

    bool Load();

    void Bind(GLenum TextureUnit);

private:

    string m_fileNames[6];
    GLuint m_textureObj;
};

This class wraps the OpenGL implementation of the cubemap texture and provides a simple interface to load and use the cubemap. The constructor takes a directory and six filenames of image files that contain the cubemap faces. For simplicity we assume that all files exist in the same directory. We need to call the function Load() once during startup in order to load the image files and create the OpenGL texture object. The attributes of the class are the image filenames (stored with the full path this time) and the OpenGL texture object handle. This single handle provides access to all six faces of the cubemap. During runtime Bind() must be called with the proper texture unit in order to make the cubemap available to the shader.

(cubemap_texture.cpp:60)

bool CubemapTexture::Load()
{
    glGenTextures(1, &m_textureObj);
    glBindTexture(GL_TEXTURE_CUBE_MAP, m_textureObj);

    Magick::Image* pImage = NULL;
    Magick::Blob blob;

    for (unsigned int i = 0 ; i < ARRAY_SIZE_IN_ELEMENTS(types) ; i++) {
        pImage = new Magick::Image(m_fileNames[i]);

        try {
            pImage->write(&blob, "RGBA");
        }
        catch (Magick::Error& Error) {
            cout << "Error loading texture '" << m_fileNames[i] << "': " << Error.what() << endl;
            delete pImage;
            return false;
        }

        glTexImage2D(types[i], 0, GL_RGB, pImage->columns(), pImage->rows(), 0, GL_RGBA,
            GL_UNSIGNED_BYTE, blob.data());
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

        delete pImage;
    }

    return true;
}

The function that loads the cubemap texture starts by generating a texture object. This object is bound to the special GL_TEXTURE_CUBE_MAP target. After that we loop over the 'types' array which contains the GL enums that represent the cubemap faces (GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, etc). These enums match the attribute string vector 'm_fileNames' which simplifies the loop. The image files are loaded one by one by ImageMagick and then specified to OpenGL using glTexImage2D(). Note that each call to this function is done using the proper GL enum for that face (which is why the 'types' array and 'm_fileNames' must match). After the cubemap is loaded and populated we setup some configuration flags. You should be familiar with all these flags except GL_TEXTURE_WRAP_R. This enum simply refers to the third dimension of the texture coordinate. We set it to the same clamping mode as the other dimensions.

(cubemap_texture.cpp:95)

void CubemapTexture::Bind(GLenum TextureUnit)
{
    glActiveTexture(TextureUnit);
    glBindTexture(GL_TEXTURE_CUBE_MAP, m_textureObj);
}

This function must be called before the texture can be used for drawing the skybox. The target for the bind function is GL_TEXTURE_CUBE_MAP which is the same enum we used in the Load() function.

(skybox_technique.h:25)

class SkyboxTechnique : public Technique {
public:

    SkyboxTechnique();

    virtual bool Init();

    void SetWVP(const Matrix4f& WVP);
    void SetTextureUnit(unsigned int TextureUnit);

private:

    GLuint m_WVPLocation;
    GLuint m_textureLocation;
};

The skybox is rendered using its own special technique. This technique has only a couple of attributes that the caller must specify - a WVP matrix to transform the box or the sphere and a texture to map on it. Let's see the internals of this class.

(skybox.vs)

#version 330

layout (location = 0) in vec3 Position;

uniform mat4 gWVP;

out vec3 TexCoord0;

void main()
{
    vec4 WVP_Pos = gWVP * vec4(Position, 1.0);
    gl_Position = WVP_Pos.xyww;
    TexCoord0 = Position;
}

This is the vertex shader of the skybox technique. It's actually quite simple but you must be aware of a couple of tricks. The first trick is that we transform the incoming position vector using the WVP matrix as always, but in the vector that goes to the fragment shader we override the Z component with the W component. What happens here is that after the vertex shader is complete the rasterizer takes gl_Position vector and performs perspective divide (division by W) in order to complete the projection. When we set Z to W we guarantee that the final Z value of the position will be 1.0. This Z value is always mapped to the far Z. This means that the skybox will always fail the depth test against the other models in the scene. That way the skybox will only take up the background left between the models and everything else will be infront of it, which is exactly what we expect from it.

The second trick here is that we use the original position in object space as the 3D texture coordinate. This makes sense because the way sampling from the cubemap works is by shooting a vector from the origin through a point in the box or sphere. So the position of the point actually becomes the texture coordinate. The vertex shader passes the object space coordinate of each vertex as the texture coordinate (8 vertices in the case of a cube and probably much more for a sphere) and it gets interpolated by the rasterizer for each pixel. This gives us the position of the pixel which we can use for sampling.

(skybox.fs)

#version 330

in vec3 TexCoord0;

out vec4 FragColor;

uniform samplerCube gCubemapTexture;

void main()
{
    FragColor = texture(gCubemapTexture, TexCoord0);
}

The fragment shader is extremely simple. The only thing worth noting here is that we use a 'samplerCube' rather than a 'sampler2D' in order to access the cubemap.

(skybox.h:27)

class SkyBox
{
public:
    SkyBox(const Camera* pCamera, const PersProjInfo& p);

    ~SkyBox();

    bool Init(const string& Directory,
        const string& PosXFilename,
        const string& NegXFilename,
        const string& PosYFilename,
        const string& NegYFilename,
        const string& PosZFilename,
        const string& NegZFilename);

    void Render();

private:
    SkyboxTechnique* m_pSkyboxTechnique;
    const Camera* m_pCamera;
    CubemapTexture* m_pCubemapTex;
    Mesh* m_pMesh;
    PersProjInfo m_persProjInfo;
};

Rendering of the skybox involves several components - a technique, a cubemap texture and a box or sphere model. To simplify its usage this class is suggested as a solution that brings all these components under the same roof. It is initialized once during startup with the directory and filenames of the cubemap texture and then used during runtime by calling Render(). A single function call takes care of everything. Note that in addition to the above components the class also have access to the camera and the perspective projection values (FOV, Z and screen dimensions). This is so that it can populate the Pipeline class properly.

void SkyBox::Render()
{
    m_pSkyboxTechnique->Enable();

    GLint OldCullFaceMode;
    glGetIntegerv(GL_CULL_FACE_MODE, &OldCullFaceMode);
    GLint OldDepthFuncMode;
    glGetIntegerv(GL_DEPTH_FUNC, &OldDepthFuncMode);

    glCullFace(GL_FRONT);
    glDepthFunc(GL_LEQUAL);

    Pipeline p;
    p.Scale(20.0f, 20.0f, 20.0f);
    p.Rotate(0.0f, 0.0f, 0.0f);
    p.WorldPos(m_pCamera->GetPos().x, m_pCamera->GetPos().y, m_pCamera->GetPos().z);
    p.SetCamera(m_pCamera->GetPos(), m_pCamera->GetTarget(), m_pCamera->GetUp());
    p.SetPerspectiveProj(m_persProjInfo);
    m_pSkyboxTechnique->SetWVP(p.GetWVPTrans());
    m_pCubemapTex->Bind(GL_TEXTURE0);
    m_pMesh->Render();

    glCullFace(OldCullFaceMode);
    glDepthFunc(OldDepthFuncMode);
}

This function takes care of rendering the skybox. We start by enabling the skybox technique. Then a new OpenGL API is introduced - glGetIntegerv(). This function returns the state of OpenGL for the enum specified as the first parameter. The second parameter is the address of an array of integers that receives the state (in our case only a single integer is enough). We must use the proper Get* function according to the type of the state - glGetIntegerv(), glGetBooleanv(), glGetInteger64v(), glGetFloatv() and glGetDoublev(). The reason why glGetIntegerv() is used here is because we are going to change a couple of common state values that are usually set in glut_backend.cpp in all of the tutorials. We want to do that in a way which is transparent to the other parts of the code and one solution is to retrieve the current status, make the proper changes and finally restore the original state. That way the rest of the system doesn't need to know that something was changed.

The first thing that we change is the culling mode. Usually, we want to cull the triangles that are facing away from the camera. However, in the case of a skybox the camera is placed inside of a box so we want to see their front, rather than their back. The problem is that in the generic sphere model which is used here the external triangles are considered front facing while the internal are backfacing (this is a dependency on the order of the vertices). We can either change the model or reverse the culling state of OpenGL. The later solution is preferable so that the same sphere model can remain generic and usable for other cases. Therefore, we tell OpenGL to cull front facing triangles.

The second thing that we change is the depth test function. By default, we tell OpenGL that an incoming fragment wins the depth test if its Z value is less than the stored one. However, in the case of a skybox the Z value is always the far Z (see above). The far Z is clipped when the depth test function is set to "less than". To make it part of the scene we change the depth function to "less than or equal".

The next thing this function does is to calculate the WVP matrix. Note that the world position of the skybox is set at the camera. This will keep the camera at its center the whole time. After that the cubemap texture is bound to texture unit 0 (this texture unit was also configured in SkyboxTechnique when it was created in SkyBox::Init()). Then the sphere mesh is rendered. Finally, the original cull mode and depth function are restored.

An interesting performance tip is to always render the skybox last (after all the other models). The reason is that we know that it will always be behind the other objects in the scene. Some GPUs have optimization mechanisms that allow them to do an early depth test and discard a fragment if it fails the test without executing the fragment shader. This is especially helpful in the case of the skybox because then the fragment shader will only run for the pixel encompasing the "background" of the scene and not the ones that are covered by the other models. But to make it happen we must get the depth buffer populated with all the Z values so that by the time the skybox is rendered all the information is already there.

Next tutorial


Tutorial 26 - Normal Mapping

Tutorial 26:

Normal Mapping

Background

Our lighting technique results are not too bad. The light is nicely interpolated over the model and conveys a sense of realism to the scene. This, however, can be improved tremendously. In fact, the same interpolation is actually an obstacle because sometimes, especially when the undelying texture represents a bumpy surface, it makes it look too flat. As an example, take a look at the following two images:

The left image definitely looks better the than the right one. It conveys the sense of the bumpiness of the stone much better while the right image looks too smooth for a brick texture. The left image has been rendered using a technique known as Normal Mapping (a.k.a Bump Mapping) and this is the subject of this tutorial.

The idea behind normal mapping is that instead of interpolating the vertex normals across the triangle face (which creates the smoothness we are trying to get rid off) they can simply be sampled from a texture. This represents the real world better because most surfaces (especially the ones we are interested in for gaming) are not that smooth such that light will be reflected back in accordance with the way we interpolate the normals. Instead, the bumps on the surface will make it reflect back at different directions, according to the general direction of the surface at the specific location where the light hits. For each texture these normals can be calculated and stored in a special texture which is called a normal map. During lighting calculations in the fragment shader the specific normal for each pixel is sampled and used as usual. The following images show the difference between the normals in regular lighting and normal mapping:

We now have our normal map and the true (or at least a good approximation of) surface normals are stored in it. Can we simply go ahead and use it? no. Think for a moment on the cube with the brick texture above. The same texture is applied on all six faces and therefore, the same normal map that goes along with it. The problem is that each face is pointing at a different direction so its interaction with an arbitrary light ray is different. If we use the normal vectors from map without any modification we would get incorrect results because the same normal vector cannot be correct for six faces pointing at different directions! For example, the normals of the top face point in the general direction of (0,1,0), even on a very bumpy surface. However, the normals of the bottom face point in the general direction of (0,-1,0). The point is that the normals are defined in their own private coordinate space and some conversion must be done in order to bring them to world space where they can participate in lighting calculations. In a sense, this concept is very similar to what we did with vertex normals. They were defined in the object local space and we transformed them to world space using the world matrix.

Let's define the coordinate system in which the normal vectors exist. This coordinate system requires three orthogonal axes of unit length. Since the normal is part of a 2D texture and 2D textures have two orthogonal axis U and V (both of unit length) the common practice is to map the X component of the system to the U axis and the Y component to the V axis. Remember that U goes from left to right and V from bottom to top (the origin in that system is the bottom left corner of the texture). The Z component of the system is considered to be going straight up from the texture and is perpendicular to both X and Y:

The normal vectors can now be specified in reference to that coordinate system and stored in the RGB texels of the texture. Note that even on a bumpy surface we still expect the general direction of the normals to point out from the texture. i.e. the Z component is the dominant one while the X and Y just make the vector tilt a bit (or a lot) from side to side. Storing the XYZ vector in an RGB texel makes most normal maps rather bluish as in the following example:

Here's the first five texels of the top row of this normal map (when going from left to right): (136,102,248), (144,122,255), (141,145,253), (102, 168, 244) and (34,130,216). The dominance of the Z here cannot be mistaken.

What we do next is to go over all the triangles in our model and place the normal map on each one in a way that the texture coordinates of each vertex will match their location on the map. For example, say that the texture coordinate of a given triangle are (0.5,0), (1, 0.5) and (0,1). The normal map will be placed as follows:

In the picture above the coordinate system on the bottom left corner represents the object local space.

In addition to texture coordinates the three vertices also have 3D coordinates that represent their position in the object local space. When we placed the texture on top of the triangle above we've essentially given a value to the U and V vectors of the texture in the object local space. If we now calculate U and V in the object local space (as well as U cross V which is the normal to the texture) we can generate a transformation matrix to move the normals from the map into the object local space. From there they can be transformed to world space as usual and take part in lighting calculation. The common practice is to call the U vector in the object local space the Tangent and the V vector in the object local space the Bitangent. The transformation matrix that we need to generate is called a TBN matrix (Tangent-Bitangent-Normal). These Tangent-Bitangent-Normal vectors define a coordinate system known as Tangent (or texture ) space. Therefore, the normals in the map are stored in tangent/texture space. We will now find out how to calculate U and V in object space.t

Let's take a look at the picture above more generically. We have triangle with three vertices at positions P0, P1 and P2 and texture coordinates (U0,V0), (U1,V1) and (U2,V2):

We want to find the vectors T (representing the tangent) and B (representing the bitangent) in object space. We can see that the two triangle edges E1 and E2 can be written as a linear combination of T and B:

This can also be written as follows:

It is now very easy to move to a matrix form:

We want to extract the matrix on the right hand side. To do that we can multiply both ends of the equation by the inverse of the matrix marked in red above:

Now we have the following:

After calculating the matrix inverse we get:

We can run this process on every triangle in the mesh and generate tangent and bitangent vectors per triangle (same vectors for the three triangle vertices). The common practice is to store a per-vertex tangent/bitangent by averaging all the tangents/bitangents of the triangles that share that vertex (same as we do for vertex normals). The reason is to smooth out the effect as we travel across the triangle face and avoid hard breaks on the edges of neighboring triangles. The third element of this coordinate system, the normal, is the cross product of the tangent and bitangent. This trio of Tangent-Bitangent-Normal can now serve as a basis for a coordinate system and be used to transform the normal from the normal map into the local object space. The next step is to transform it to world space and use it for lighting calculations. However, we can optimize it a bit by transforming the Tangent-Bitangent-Normal themselves into world space and only then transform the normal from the map. This will provide the normal in world space directly.

In this tutorial we will do the following:

  1. Supply the tangent vector to the vertex shader.
  2. Transform the tangent vector to world space and pass it to the fragment shader.
  3. Use the tangent vector and normal (both in world space) to calculate the bitangent in the fragment shader.
  4. Generate a world space transformation matrix using the tangent-bitangent-normal.
  5. Sample the normal from the normal map.
  6. Transform the normal to world space using the above matrix.
  7. Continue lighting calculations as usual.

There is one peculiarity which we will need to address in our code. On the pixel level the tangent-bitangent-normal are not really an orthonormal basis (three unit length vectors that are perpendicular to one another). Two reasons contribute to that - first, we average the tangents and normal per vertex according to the triangles that share it, and second, the tangents and normals are interpolated by the rasterizer and on the pixel level we see the interpolated result. This makes the tangent-bitangnet-normal loose some of their "orthonormal qualities". But to transform from tangent space into world space we will need an orthonormal basis. The solution is to use the Gram-Schmidt process. This process takes a group of vectors and turns them into an orthonormal basis. In a nutshell, the process is as follows: select vector 'A' from the group and normalize it. Then select vector 'B' and break it into two components (the two components are vectors whose sum is 'B') where the first component points in the direction of 'A' and the second component is perpendicular to it. Now replace 'B' by the component that is perpendicular to 'A' and normalize it. Continue this process on all vectors in the group.

The end result of all this is that we are not using the mathematically correct tangent-bitangent-normal vectors but we are getting the required smoothness to avoid hard breaks on triangle edges.

Source walkthru

(mesh.h:33)

struct Vertex
{
    Vector3f m_pos;
    Vector2f m_tex;
    Vector3f m_normal;
    Vector3f m_tangent;

    Vertex() {}

    Vertex( const Vector3f& pos,
            const Vector2f& tex,
            const Vector3f& normal,
            const Vector3f& Tangent )
    {
        m_pos = pos;
        m_tex = tex;
        m_normal = normal;
        m_tangent = Tangent;
    }
};

This is our new Vertex structure with the new addition of the tangent vector. We will calculate the bitangent in the fragment shader. Note that the normal of the tangent space is identical to the regular triangle normal (since the texture and triangle are parallel). Therefore, the vertex normals in the two coordinate system are also identical.

for (unsigned int i = 0 ; i < Indices.size() ; i += 3) {
    Vertex& v0 = Vertices[Indices[i]];
    Vertex& v1 = Vertices[Indices[i+1]];
    Vertex& v2 = Vertices[Indices[i+2]];

    Vector3f Edge1 = v1.m_pos - v0.m_pos;
    Vector3f Edge2 = v2.m_pos - v0.m_pos;

    float DeltaU1 = v1.m_tex.x - v0.m_tex.x;
    float DeltaV1 = v1.m_tex.y - v0.m_tex.y;
    float DeltaU2 = v2.m_tex.x - v0.m_tex.x;
    float DeltaV2 = v2.m_tex.y - v0.m_tex.y;

    float f = 1.0f / (DeltaU1 * DeltaV2 - DeltaU2 * DeltaV1);

    Vector3f Tangent, Bitangent;

    Tangent.x = f * (DeltaV2 * Edge1.x - DeltaV1 * Edge2.x);
    Tangent.y = f * (DeltaV2 * Edge1.y - DeltaV1 * Edge2.y);
    Tangent.z = f * (DeltaV2 * Edge1.z - DeltaV1 * Edge2.z);

    Bitangent.x = f * (-DeltaU2 * Edge1.x - DeltaU1 * Edge2.x);
    Bitangent.y = f * (-DeltaU2 * Edge1.y - DeltaU1 * Edge2.y);
    Bitangent.z = f * (-DeltaU2 * Edge1.z - DeltaU1 * Edge2.z);

    v0.m_tangent += Tangent;
    v1.m_tangent += Tangent;
    v2.m_tangent += Tangent;
}

for (unsigned int i = 0 ; i < Vertices.size() ; i++) {
    Vertices[i].m_tangent.Normalize();
}

This piece of code is an implementation of the algorithm that calculates the tangent vectors (described in the background section). The Indices array is traversed and the triangle vectors are retrieved from the Vertices array by their indices. We calculale the two edges by substracting the first vertex from the second and the third vertices. We do a similar thing with the texture coordinates and calculate the deltas along the U and V axes of the two edges. 'f' represents the fraction that appears on the right hand side of the final equation in the background section. Once 'f' is known both the tangent and bitangent can be calculated by multiplying it by the product of the two matrices. Note that the calculation of the bitangent appears for the sake of completeness. The only thing that we really need is the tangent which we accumulate into the three vertices. The last thing that we do is scan the Vertices array and normalize the tangents.

Now that you fully understand both the theory and imlementation I can tell you that we won't be using this code in the tutorial. The Open Asset Import Library has a handy post processing flag called 'aiProcess_CalcTangentSpace' which does exactly that and calculates the tangent vectors for us (it's good to know the implementation anyway in case you need to do it yourself in a future project). We only need to specify it when loading the model and then we can access the 'mTangents' array in the aiMesh class and fetch the tangents from there. Check the code for more details.

(mesh.cpp:195)

void Mesh::Render()
{
    ...
    glEnableVertexAttribArray(3);

    for (unsigned int i = 0 ; i < m_Entries.size() ; i++) {
        ...
        glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)32);
    }
    ...
    glDisableVertexAttribArray(3);
}

Since the Vertex structure has grown we need to make a few modification to the render function of the Mesh class. The fourth vertex attribute is enabled and we specify the location of the tangent in byte 32 (just after the normal) from the start of the vertex. Finally, the fourth attribute is disabled.

(lighting.vs)

layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
layout (location = 2) in vec3 Normal;
layout (location = 3) in vec3 Tangent;

uniform mat4 gWVP;
uniform mat4 gLightWVP;
uniform mat4 gWorld;

out vec4 LightSpacePos;
out vec2 TexCoord0;
out vec3 Normal0;
out vec3 WorldPos0;
out vec3 Tangent0;

void main()
{
    gl_Position = gWVP * vec4(Position, 1.0);
    LightSpacePos = gLightWVP * vec4(Position, 1.0);
    TexCoord0 = TexCoord;
    Normal0 = (gWorld * vec4(Normal, 0.0)).xyz;
    Tangent0 = (gWorld * vec4(Tangent, 0.0)).xyz;
    WorldPos0 = (gWorld * vec4(Position, 1.0)).xyz;
}

This is the updated vertex shader. There isn't a lot of new stuff here as most of the changes are in the fragment shader. The new addition is the tangent which is passed as an input, transformed to world space and passed on as an output.

(lighting.fs:132)

vec3 CalcBumpedNormal()
{
    vec3 Normal = normalize(Normal0);
    vec3 Tangent = normalize(Tangent0);
    Tangent = normalize(Tangent - dot(Tangent, Normal) * Normal);
    vec3 Bitangent = cross(Tangent, Normal);
    vec3 BumpMapNormal = texture(gNormalMap, TexCoord0).xyz;
    BumpMapNormal = 2.0 * BumpMapNormal - vec3(1.0, 1.0, 1.0);
    vec3 NewNormal;
    mat3 TBN = mat3(Tangent, Bitangent, Normal);
    NewNormal = TBN * BumpMapNormal;
    NewNormal = normalize(NewNormal);
    return NewNormal;
}

void main()
{
    vec3 Normal = CalcBumpedNormal();
    ...

The code above contains most of the changes in the fragment shader. All handling of the normal is encapsulated in the CalcBumpedNormal() function. We start by normalizing both the normal and the tangent vectors. The third line is the Gramm-Schmidt process. dot(Tangent, Normal) gives us the length of the projection of the tangent along the normal vector. The product of this length by the normal itself is the component of the tangent along the normal. Substract that from the tangent and we get a new vector which is perpendicular to the normal. This is our new tangent (just remember to normalize it as well...). A cross product between the tangent and the normal gives us the bitangent. Next, we sample the normal map and get the normal for this pixel (in tangent space). 'gNormalMap' is a new uniform of the sampler2D type to which we must bind the normal map before the draw. The normal is stored as a color so its components are in the range [0-1]. We transform it back to its original format using the function 'f(x) = 2 * x - 1'. This function maps 0 to -1 and 1 to 1 and is simply the reverse of what happened when the normal map was generated.

We now need to transform the normal from tangent space into world space. We create a 3x3 matrix called TBN using one of the constructors of the 'mat3' type. This constructor takes three vectors as parameters and generates a matrix by placing the first parameter in the top row, the second in the middle and the third in the bottom row. If you wonder why this order and not another simply remember that the tangent is mapped to the X axis, the bitangent to the Y and the normal to the Z (see picture above). In the standard 3x3 identity matrix the top row contains the X axis, the middle the Y axis and the bottom the Z axis. We simply match this order. The tangent space normal is multiplied by the TBN matrix and the result is normalized before it is returned to the caller. This is the final pixel normal.

The sample that accompanies this tutorial comes with three JPEG files:

  1. 'bricks.jpg' is the color texture.
  2. 'normal_map.jpg' is the normal map that was generated from 'bricks.jpg'.
  3. 'normal_up.jpg' is a trivial normal map where all normals point straight upwards. When this normal map is used the effect is as if no normal mapping takes place. It is simpler to bind this texture when we want to disable normal mapping then to use two different techniques (albeit less efficient). You can use the 'b' key to toggle between normal-mapping and no-normal-mapping and see the effect.

The normal map is bound to texture unit 2 which is now the standard texture unit for that purpose (0 is the color and 1 is the shadow map).

Note on normal map generation:

There are many ways to generate a normal map. For this tutorial I used gimp which is free and open source and its normal map plugin. Once you have the plugin installed simply load the texture that you plan to use for the model, go to Filters->Map->Normalmap. You will be able to change many aspects of the normal map and configure it in different ways. When satisfied click 'OK'. The normal map will replace the original texture in the main view of gimp. Save it under a new filename and use it in your samples.

Next tutorial


Tutorial 27 - Billboarding and the Geometry Shader

Tutorial 27:

Billboarding and the Geometry Shader

Background

We've been utilizing the vertex and the fragment shaders from early on in this series of tutorials but in fact we left out an important shader stage called the Geometry Shader (GS). This type of shader was introduced by Microsoft in DirectX10 and was later incorporated into the core OpenGL in version 3.2. While the VS is executed per vertex and the FS is executed per pixel the GS is executed per primitive. This means that if we are drawing triangles each invocation of the GS receives exactly one triangle; if we are drawing lines each invocation of the GS receives exactly one line, etc. This provides the GS a unique view of the model where the connectivity between the vertices is exposed to the developer, allowing her to develop new techniques that are based on that knowledge.

While the vertex shader always takes one vertex as input and outputs one vertex (i.e. it cannot create or destroy vertices on the fly) the GS has the unique capability of making changes to the primitives that are passing through it. These changes include:

  • Changing the topology of the incoming primitives. The GS can receive primitives in any topology type but can only output point lists, line strips and triangle strips (the strip topologies are described below).
  • The GS takes one primitive as input and can either drop it altogether or output one or more primitives (this means that it can produce both less and more vertices than what it got). This capability is known as growing geometry. We will take advantage of this capability in this tutorial.

Geometry shaders are optional. If you compile a program without a GS the primitives will simply flow directly from the vertex shader down to the fragment shader. That's why we've been able to get to this point without mentioning them.

Triangle lists are constructed using trios of vertices. Vertices 0-2 are the first triangle, vertices 3-5 are the second and so forth. To calculate the number of triangles generated from any number of vertices simply divide the number of vertices by 3 (dropping the remainder). Triangle strips are more efficient because instead of adding 3 vertices to get a new triangle most of the time we just need to add one vertex. To construct a triangle strip start out with 3 vertices for the first triangle. When you add a fourth vertex you get the second triangle which is constructed from vertices 1-3. When you add a fifth vertex you get the third triangle which is constructed from vertices 2-4, etc. So from the second triangle and on every new vertex is joined with the previous two to create a new triangle. Here's an example:

As you can see, 7 triangles were created from just 9 vertices. If this was a triangle list we would have only 3 triangles.

Triangle strips have an important property with regard to the winding order inside the triangles - the order is reversed on the odd triangles. This means that the order is as follows: [0,1,2], [1,3,2], [2,3,4], [3,5,4], etc. The following picture shows that ordering:

Now that we understand the concept of geometry shaders let's see how they can help us implement a very useful and popular technique called billboarding. A billboard is a quad which always faces the camera. As the camera moves around the scene the billboard turns with it so that the vector from the billboard to the camera is always perpedicular to the billboard face. This is the same idea as billboards in the real world that are placed along the highways in a way that will make them as visible as possible to the cars that are passing by. Once we got the quad to face the camera it is very easy to texture map it with the image of a monster, tree or whatever and create a large number of scene objects that always face the camera. Billboards are often used to create a forest where a large number of trees is required in order to create the effect. Since the texture on the billboard is always facing the camera the player is fooled into thinking that the object has real depth where in fact it is completely flat. Each billboard requires only 4 vertices and therefore it it much cheaper in comparison to a full blown model.

In this tutorial we create a vertex buffer and populate it with world space locations for the billboards. Each location is just a single point (3D vector). We will feed the locations into the GS and grow each location into a quad. This means that the input topology of the GS will be point list while the output topology will be triangle strip. Taking advantage of triangle strips we will create a quad using 4 vertices:

The GS will take care of turning the quad to face the camera and will attach the proper texture coordinates to each outgoing vertex. The fragment shader will only need to sample the texture and provide the final color.

Let's see how we can make the billboard always face the camera. In the following picture the black dot represents the camera and the red dot represents the location of the billboard. Both dots are in world space and while it looks like they are located on a surface which is parallel to the XZ plane they don't have to be. Any two points will do.

We now create a vector from the billboard location to the camera:

Next we add the vector (0,1,0):

Now do a cross product between these two vectors. The result is a vector which is perpedicular to the surface created by the two vectors. This vector points in the exact direction along which we need to extend the point and create a quad. The quad will be perpedicular to the vector from the original point to the camera, which is what we want. Looking at the same scene from above we get the following (the yellow vector is the result of the cross product):

One of the things that often confuses developers is in what order to do the cross product (A cross B or B cross A?). The two options produce two vectors that are opposite to one another. Knowing in advance the resulting vector is critical because we need to output the vertices such that the two triangles that make up the quad will be in clockwise order when looking at them from the point of view of the camera. The left hand rule comes to our rescue here. This rule says that if you are standing at the location of the billboard and your forefinger is pointing towards the camera and your middle finger is pointing upwards (towards the sky) then your thumb will point along the result of "forefinger" cross "middle finger" (the remaining two fingers are often kept clamped here). In this tutorial we call the result of the cross product the "right" vector because it points toward the right when looking at your hand like that from the camera point of view. Doing a "middle finger" cross "forefinger" will simply generate the "left" vector.

(We are using the left hand rule because we are working in a left hand coordinate system (Z grows as we move further into the scene). The right hand coordinate system is exactly the reverse).

Source walkthru

(billboard_list.h:27)

class BillboardList
{
public:
    BillboardList();
    ~BillboardList();

    bool Init(const std::string& TexFilename);

    void Render(const Matrix4f& VP, const Vector3f& CameraPos);

private:
    void CreatePositionBuffer();

    GLuint m_VB;
    Texture* m_pTexture;
    BillboardTechnique m_technique;
};

The BillboardList class encapsultes everything you need in order to generate billboards. The Init() function of the class takes the filename that contains the image which will be texture mapped on the billboard. The Render() function is called from the main render loop and takes care of setting up the state and rendering the billboard. This function needs two parameters: the combined view and projection matrix and the location of the camera in world space. Since the billboard location is specified in world space we go directly to view and projection and skip the world transformation part. The class has three private attributes: a vertex buffer to store the location of the billboards, a pointer to the texture to map on the billboard and the billboard technique that contains the relevant shaders.

(billboard_list.cpp:80)

void BillboardList::Render(const Matrix4f& VP, const Vector3f& CameraPos)
{
    m_technique.Enable();
    m_technique.SetVP(VP);
    m_technique.SetCameraPosition(CameraPos);

    m_pTexture->Bind(COLOR_TEXTURE_UNIT);

    glEnableVertexAttribArray(0);

    glBindBuffer(GL_ARRAY_BUFFER, m_VB);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vector3f), 0); // position

    glDrawArrays(GL_POINTS, 0, NUM_ROWS * NUM_COLUMNS);

    glDisableVertexAttribArray(0);
}

This function enables the billboard technique, sets the required state into OpenGL and draws the points that are turned into quads in the GS. In this demo the billboards are laid out in strict rows and columns which explains why we multiply them to get the number of points in the buffer. Note that we are using point list as our input topology. The GS will need to match that.

(billboard_technique.h:24)

class BillboardTechnique : public Technique
{
public:

    BillboardTechnique();

    virtual bool Init();

    void SetVP(const Matrix4f& VP);
    void SetCameraPosition(const Vector3f& Pos);
    void SetColorTextureUnit(unsigned int TextureUnit);

private:

    GLuint m_VPLocation;
    GLuint m_cameraPosLocation;
    GLuint m_colorMapLocation;
};

This is the interface of the billboard technique. It requires only three parameters in order to do its job: the combined view/projection matrix, the position of the camera in world space and the number of the texture unit where the billboard texture is bound.

(billboard.vs)

#version 330

layout (location = 0) in vec3 Position;

void main()
{
    gl_Position = vec4(Position, 1.0);
}

This is the VS of the billboard technique and with most of the action taking place in the GS you cannot ask for a simpler VS. The vertex buffer contains only position vectors and since they are already specified in world space we only need to pass them through to the GS. That's it.

(billboard.gs:1)

#version 330

layout (points) in;
layout (triangle_strip) out;
layout (max_vertices = 4) out;

The core of the billboard technique is located in the GS. Let's take a look at it piece by piece. We start by declaring some global stuff using the 'layout' keyword. We tell the pipeline that the incoming topology is point list and the outgoing topology is triangle strip. We also tell it that we will emit no more than four vertices. This keyword is used to give the graphics driver a hint about the maximum number of vertices that can be emitted by the GS. Knowning the limit in advance gives the driver an opportunity to optimize the behavior of the GS for the particular case. Since we know that we are going to emit a quad for each incoming vertex we declare the maximum as four vertices.

(billboard.gs:7)

uniform mat4 gVP;
uniform vec3 gCameraPos;

out vec2 TexCoord;

The GS gets the position in world space so it only needs a view/projection matrix. It also needs the camera location in order to calculate how to orient the billboard towards it. The GS generates texture coordinates for the FS so we must declare them.

(billboard.gs:12)

void main()
{
    vec3 Pos = gl_in[0].gl_Position.xyz;

The line above is unique to the GS. Since it is executed on a complete primitive we actually have access to each of the vertices that comprise it. This is done using the built-in variable 'gl_in'. This variable is an array of structures that contains, among other things, the position that was written into gl_Position in the VS. To access it we go to the slot we are interested in using the index of the vertex. In this specific example the input topology is point list so there is only a single vertex. We access it using 'gl_in[0]'. If the input topology was a triangle we could also have written 'gl_in[1]' and 'gl_in[2]'. We only need the first three components of the position vector and we extract them to a local variable using '.xyz'.

    vec3 toCamera = normalize(gCameraPos - Pos);
    vec3 up = vec3(0.0, 1.0, 0.0);
    vec3 right = cross(toCamera, up);

Here we make the billboard face the camera per the explanation at the end of the background section. We do a cross product between the vector from the point to the camera and a vector that points straight up. This provides the vector that points right when looking at the point from the camera point of view. We will now use it to 'grow' a quad around the point.

    Pos -= (right * 0.5);
    gl_Position = gVP * vec4(Pos, 1.0);
    TexCoord = vec2(0.0, 0.0);
    EmitVertex();

    Pos.y += 1.0;
    gl_Position = gVP * vec4(Pos, 1.0);
    TexCoord = vec2(0.0, 1.0);
    EmitVertex();

    Pos.y -= 1.0;
    Pos += right;
    gl_Position = gVP * vec4(Pos, 1.0);
    TexCoord = vec2(1.0, 0.0);
    EmitVertex();

    Pos.y += 1.0;
    gl_Position = gVP * vec4(Pos, 1.0);
    TexCoord = vec2(1.0, 1.0);
    EmitVertex();

    EndPrimitive();
}

The point in the vertex buffer is considered to be at the center of the bottom of the quad. We need to generate two front facing triangles from it. We start by going left to the bottom left corner of the quad. This is done by substracting half of the 'right' vector from the point. Next we calculate the position in clip space by mutiplying the point by the view/projection matrix. We also set the texture coordinate to (0,0) because we plan to cover the entire texture space using the quad. To send the newly generated vertex down the pipe we call the built-in function EmitVertex(). After this functionn is called the variables that we have written to are considered undefined and we have to set new data for them. In a similar way we generate the top left and bottom right corners of the quad. This is the first front facing triangle. Since the output topology of the GS is triangle strip we only need one more vertex for the second triangle. It will be structured using the new vertex and the last two vertices (which are the quad diagonal). The fourth and final vertex is the top right corner of the quad. To end the triangle strip we call the built-in function EndPrimitive().

(billboard.fs)

#version 330

uniform sampler2D gColorMap;

in vec2 TexCoord;
out vec4 FragColor;

void main()
{
    FragColor = texture2D(gColorMap, TexCoord);

    if (FragColor.r == 0 && FragColor.g == 0 && FragColor.b == 0) {
        discard;
    }
}

The FS is very simple - most of its work is to sample the texture using the texture coordinates generated by the GS. There is a new feature here - the built-in keyword 'discard' is used in order to drop a pixel completely on certain cases. The picture of the hell-knight from Doom which is included in this tutorial shows the monster on a black background. Using this texture as-is will make the billboard look like a full sized card which is much larger than the monster itself. To overcome this we test the texel color and if it is black we drop the pixel. This allows us to select only the pixels that actually make up the monster. Try to disable 'discard' and see the difference.

Next tutorial


Tutorial 28 - Particle System using Transform Feedback

Tutorial 28:

Particle System using Transform Feedback

Background

Particle System is a general name of a large number of techniques that simulate natural phenomena such as smoke, dust, fireworks, rain, etc. The common theme in all these phenomena is that they are composed of a large amount of small particles that move together in a way which is characteristic of each type of phenomenon.

In order to simulate a natural phenomenon made from particles we usually maintain the position as well as other attributes for each particle (velocity, color, etc) and perform the following steps once per frame:

  1. Update the attributes of each particle. This step usually involves some math calculations (ranging from very simple to very complex - depending on the complexity of the phenomenon).
  2. Render the particles (as simple colored points or full blown texture mapped billboard quads).

In the past step 1 usually took place on the CPU. The application would access the vertex buffer, scan its contents and update the attributes of each and every particle. Step 2 was more straightforward and took place on the GPU as any other type of rendering. There are two problems with this approach:

  1. Updating the particles on the CPU requires the OpenGL driver to copy the contents of the vertex buffer from the GPU memory (on discrete cards this means over the PCI bus) to the CPU memory. The phenomena that we are insterested in usually require a large amount of particles. 10,000 particles is not a rare number in that regard. If each particle takes up 64 bytes and we are running at 60 frames per second (very good frame rate) this means copying back and forth 640K from the GPU to the CPU 60 times each second. This can have an negative effect on the performance of the application. As the number of particles grows larger the effect increases.
  2. Updating the particle attributes means running the same mathematical formula on different data items. This is a perfect example of distributed computing that the GPU excels at. Running it on the CPU means serializing the entire update process. If our CPU is multi core we can take advantage of it and reduce the total amount of time but that requires more work from the application. Running the update process on the GPU means that we get parallel execution for free.

DirectX10 introduced a new feature known as Stream Output that is very useful for implementing particle systems. OpenGL followed in version 3.0 with the same feature and named it Transform Feedback. The idea behind this feature is that we can connect a special type of buffer (called Transform Feedback Buffer right after the GS (or the VS if the GS is absent) and send our transformed primitives to it. In addition, we can decide whether the primitives will also continue on their regular route to the rasterizer. The same buffer can be connected as a vertex buffer in the next draw and provide the vertices that were output in the previous draw as input into the next draw. This loop enables the two steps above to take place entirely on the GPU with no application involvement (other than connecting the proper buffers for each draw and setting up some state). The following diagram shows the new architecture of the pipeline:

How many primitives end up in the transform feedback buffer? well, if there is no GS the answer is simple - it is based on the number of vertices from the draw call parameters. However, if the GS is present the number of primitives is unknown. Since the GS is capable of creating and destroying primitives on the fly (and can also include loops and branches) we cannot always calculate the total number of primitives that will end up in the buffer. So how can we draw from it later when we don't know exactly the number of vertices it contains? To overcome this challenge transform feedback also introduced a new type of draw call that does not take the number of vertices as a parameter. The system automatically tracks the number of vertices for us for each buffer and later uses that number internally when the buffer is used for input. If we append several times to the transform feedback buffer (by drawing into it several times without using it as input) the number of vertices is increased accordingly. We have the option of reseting the offset inside the buffer whenever we want and the system will also reset the number of vertices.

In this tutorial we will use transform feedback in order to simulate the effect of fireworks. Fireworks are relatively easy to simulate in terms of the math involved so we will be able to focus on getting transform feedback up and running. The same framework can later be used for other types of particle systems as well.

OpenGL enforces a general limitation that the same resource cannot be bound for both input and output in the same draw call. This means that if we want to update the particles in a vertex buffer we actually need two transform feedback buffers and toggle between them. On frame 0 we will update the particles in buffer A and render the particles from buffer B and on frame 1 we will update the particles in buffer B and render the particles from buffer A. All this is transparent to the viewer.

In addition, we will also have two techniques - one technique will be responsible for updating the particles and the other for rendering. We will use the billboarding technique from the previous tutorial for rendering so make sure you are familiar with it.

Source walkthru

(particle_system.h:29)

class ParticleSystem
{
public:
    ParticleSystem();

    ~ParticleSystem();

    bool InitParticleSystem(const Vector3f& Pos);

    void Render(int DeltaTimeMillis, const Matrix4f& VP, const Vector3f& CameraPos);

private:

    bool m_isFirst;
    unsigned int m_currVB;
    unsigned int m_currTFB;
    GLuint m_particleBuffer[2];
    GLuint m_transformFeedback[2];
    PSUpdateTechnique m_updateTechnique;
    BillboardTechnique m_billboardTechnique;
    RandomTexture m_randomTexture;
    Texture* m_pTexture;
    int m_time;
};

The ParticleSystem class encapsulates all the mechanics involved in managing the transform feedback buffer. One instance of this class is created by the application and initialized with the world space position of the fireworks launcher. In the main render loop the ParticleSystem::Render() function is called and takes three parameters: the delta time from the previous call in milliseconds, the product of the viewport and projection matrices and the world space position of the camera. The class also has a few attributes: an indicator for the first time Render() is called, two indices that specify which buffer is currently the vertex buffer (input) and which is the transform feedback buffer (output), two handles for the vertex buffers, two handles for the transform feedback objects, the update and render techniques, a texture that contains random numbers, the texture that will be mapped on the particles and the current global time variable.

(particle_system.cpp:31)

struct Particle
{
    float Type;
    Vector3f Pos;
    Vector3f Vel;
    float LifetimeMillis;
};

Each particle has the above structure. A particle can be either a launcher, a shell or a secondary shell. The launcher is static and is responsible for generating the other particles. It is unique in the system. The launcher periodically creates shell particles and fires them upwards. After a few seconds the shells explode into secondary shells that fly into random directions. All particles except the launcher has a lifetime which is tracked by the system in milliseconds. When the lifetime reaches a certain threshold the particle is removed. Each particle also has a current position and velocity. When a particle is created it is given some velocity (a vector). This velocity is influenced by gravity which pulls the particle down. On every frame we use the velocity to update the world position of the particle. This position is used later to render the particle.

(particle_system.cpp:67)

bool ParticleSystem::InitParticleSystem(const Vector3f& Pos)
{
    Particle Particles[MAX_PARTICLES];
    ZERO_MEM(Particles);

    Particles[0].Type = PARTICLE_TYPE_LAUNCHER;
    Particles[0].Pos = Pos;
    Particles[0].Vel = Vector3f(0.0f, 0.0001f, 0.0f);
    Particles[0].LifetimeMillis = 0.0f;

    glGenTransformFeedbacks(2, m_transformFeedback);
    glGenBuffers(2, m_particleBuffer);

    for (unsigned int i = 0; i < 2 ; i++) {
        glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, m_transformFeedback[i]);
        glBindBuffer(GL_ARRAY_BUFFER, m_particleBuffer[i]);
        glBufferData(GL_ARRAY_BUFFER, sizeof(Particles), Particles, GL_DYNAMIC_DRAW);
        glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, m_particleBuffer[i]);
    }

This is the first part of the initialization of the particle system. We set up storage for all the particles on the stack and initialize just the first particle as a launcher (the remaining particles will be created at render time). The position of the launcher is also the starting position of all the particles it is going to create and the velocity of the launcher is their starting velocity (the launcher itself is static). We are going to use two transform feedback buffers and toggle between them (drawing into one while using the other as input and vice verse) so we create two transform feedback objects using the function glGenTransformFeedbacks. The transform feedback object encapsulates all the state that is attached to the transform feedback object. We also create two buffer objects - one for each transform feedback object. We then perform the same series of operations for both objects (see below).

We start by binding a transform feedback object to the GL_TRANSFORM_FEEDBACK target using glBindTransformFeedback() function. This makes the object "current" so that following operations (relevant to transform feedback) are performed on it. Next we bind the the corresponding buffer object to the GL_ARRAY_BUFFER which makes it a regular vertex buffer and load the contents of the particle array into it. Finally we bind the corresponding buffer object to the GL_TRANSFORM_FEEDBACK_BUFFER target and specify the buffer index as zero. This makes this buffer a transform feedback buffer and places it as index zero. We can have the primitives redirected into more than one buffer by binding several buffers at different indices. Here we only need one buffer. So now we have two transform feedback objects with corresponding buffer objects that can serve both as vertex buffers as well as transform feedback buffers.

We won't review the remainder of the InitParticleSystem() function because there is nothing new there. We simply need to initialize the two techniques (members of the ParticleSystem class) and set some static state into them as well as load the texture that will be mapped on the particles. Check the code for more details.

(particle_system.cpp:124)

void ParticleSystem::Render(int DeltaTimeMillis, const Matrix4f& VP, const Vector3f& CameraPos)
{
    m_time += DeltaTimeMillis;

    UpdateParticles(DeltaTimeMillis);

    RenderParticles(VP, CameraPos);

    m_currVB = m_currTFB;
    m_currTFB = (m_currTFB + 1) & 0x1;
}

This is the main render function of the ParticleSystem class. It is responsible for updating the global time counter and toggling between the two buffer indices ('m_currVB' is the current vertex buffer and is initialized to 0 while 'm_currTFB' is the current transform feedback buffer and is initialized to 1). The main job of this function is to call the two private functions that update the particle attributes and then render them. Let's take a look at how we update the particles.

(particle_system.cpp:137)

void ParticleSystem::UpdateParticles(int DeltaTimeMillis)
{
    m_updateTechnique.Enable();
    m_updateTechnique.SetTime(m_time);
    m_updateTechnique.SetDeltaTimeMillis(DeltaTimeMillis);

    m_randomTexture.Bind(RANDOM_TEXTURE_UNIT);

We start the particle update by enabling the corresponding technique and setting some dynamic state into it. The technique will need to know the amount of time that has passed from the previous render because this is the factor in the movement equation and it needs the global time as a semi random seed for accessing the random texture. We dedicate GL_TEXTURE3 as the texture unit for binding random textures. The random texture is used to provide directions for the generated particles (we will later see how this texture is created).

    glEnable(GL_RASTERIZER_DISCARD);

The next function call is something that we haven't seen before. Since the only purpose of the draw call further down this function is to update the transform feedback buffer we prefer to cut the flow of primitives after that and prevent them from also being rasterized to the screen. We have another draw call later on that does that. Calling glEnable() with the GL_RASTERIZER_DISCARD flag tells the pipeline to discard all primitives before they reach the rasterizer (but after the optional transform feedback stage).

    glBindBuffer(GL_ARRAY_BUFFER, m_particleBuffer[m_currVB]);
    glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, m_transformFeedback[m_currTFB]);

The next two calls handle the toggling between the roles of the two buffers that we have created. 'm_currVB' is used as an index (either 0 or 1) into the array of VBs and we bind the buffer in that slot as a vertex buffer (for input). 'm_currTFB' is used as an index (always opposing 'm_currVB') into the transform feedback object array and we bind the object in that slot as transform feedback (which brings along with it the attached state - the actual buffer).

    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    glEnableVertexAttribArray(2);
    glEnableVertexAttribArray(3);

    glVertexAttribPointer(0,1,GL_FLOAT,GL_FALSE,sizeof(Particle),0); // type
    glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,sizeof(Particle),(const GLvoid*)4); // position
    glVertexAttribPointer(2,3,GL_FLOAT,GL_FALSE,sizeof(Particle),(const GLvoid*)16); // velocity
    glVertexAttribPointer(3,1,GL_FLOAT,GL_FALSE,sizeof(Particle),(const GLvoid*)28); // lifetime

We already know the next few function calls. They simply set up the vertex attributes of the particles in the vertex buffer. You will later see how we make sure that the input layout is the same as the output layout.

    glBeginTransformFeedback(GL_POINTS);

The real fun starts here. glBeginTransformFeedback() makes transform feedback active. All the draw calls after that, and until glEndTransformFeedback() is called, redirect their output to the transform feedback buffer according to the currently bound transform feedback object. This function also takes a topology parameter. The way transform feedback works is that only complete primitives (i.e. lists) can be written into the buffer. This means that if you draw four vertices in triangle strip topology or six vertices in triangle list topology, you end up with six vertices (two triangles) in the feedback buffer in both cases. The available topologies to this function are therefore:

  • GL_POINTS - the draw call topology must also be GL_POINTS.
  • GL_LINES - the draw call topology must be GL_LINES, GL_LINE_LOOP or GL_LINE_STRIP.
  • GL_TRIANGLES - the draw call topology must be GL_TRIANGLES, GL_TRIANGLE_STRIP or GL_TRIANGLE_FAN.

    if (m_isFirst) {
        glDrawArrays(GL_POINTS, 0, 1);
        m_isFirst = false;
    }
    else {
        glDrawTransformFeedback(GL_POINTS, m_transformFeedback[m_currVB]);
    }

As described earlier, we have no way of knowing how many particles end up in the buffer and transform feedback supports this. Since we generate and destroy particles based on the launcher frequency and each particle lifetime, we cannot tell the draw call how many particles to process. This is all true - except for the very first draw. In this case we know that our vertex buffer contains only the launcher and the "system" doesn't have any record of previous transform feedback activity so it cannot tell the number of particles on its own. This is why the first draw must be handled explicitly using a standard glDrawArrays() function of a single point. The remaining draw calls will be done using glDrawTransformFeedback(). This function doesn't need to be told how many vertices to process. It simply checks the input buffer and draws all the vertices that have been previously written into it (when it was bound as a transform feedback buffer). Note that whenever we bind a transform feedback object the number of vertices in the buffer becomes zero because we called glBindBufferBase() on that buffer while the transform feedback object was originally bound (see the initialization part) with the parameter zero as the offset. OpenGL remembers that so we don't need to call glBindBufferBase() again. It simply happens behind the scenes when the transform feedback object is bound.

glDrawTransformFeedback() takes two parameters. The first one is the topology. The second one is the transform feedback object to which the current vertex buffer is attached. Remember that the currently bound transform feedback object is m_transformFeedback[m_currTFB]. This is the target of the draw call. The number of vertices to process as input comes from the transform feedback object which was bound as a target in the previous time we went through ParticleSystem::UpdateParticles(). If this is confusing, simply remember that when we draw into transform feedback object #1 we want to take the number of vertices to draw from transform feedback #0 and vice versa. Today's input is tomorrow's output.

    glEndTransformFeedback();

Every call to glBeginTransformFeedback() must be paired with glEndTransformFeedback(). If you miss that things will break pretty quick.

    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);
    glDisableVertexAttribArray(2);
    glDisableVertexAttribArray(3);
}

The end of the function is standard. When we get to this point all the particles have been updated. Let's see how to render them in their new positions.

(particle_system.cpp:177)

void ParticleSystem::RenderParticles(const Matrix4f& VP, const Vector3f& CameraPos)
{
    m_billboardTechnique.Enable();
    m_billboardTechnique.SetCameraPosition(CameraPos);
    m_billboardTechnique.SetVP(VP);
    m_pTexture->Bind(COLOR_TEXTURE_UNIT);

We start the actual rendering by enabling the billboarding technique and setting some state into it. Each particle will be extended into a quad and the texture that we bind here will be mapped on its face.

    glDisable(GL_RASTERIZER_DISCARD);

Rasterization was disabled while we were writing into the feedback buffer. We enable it by disabling the GL_RASTERIZER_DISCARD feature.

    glBindBuffer(GL_ARRAY_BUFFER, m_particleBuffer[m_currTFB]);

When we wrote into the transform feedback buffer we bound m_transformFeedback[m_currTFB] as the transform feedback object (the target). That object has m_particleBuffer[m_currTFB] as the attached vertex buffer. We now bind this buffer to provide the input vertices for rendering.

    glEnableVertexAttribArray(0);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Particle), (const GLvoid*)4); // position

    glDrawTransformFeedback(GL_POINTS, m_transformFeedback[m_currTFB]);

    glDisableVertexAttribArray(0);
}

The particle in the transform feedback buffer has four attributes. In order to render it we only need position so only a single attribute is enabled. Make sure that the stride (distance between that attribute in two consecutive vertices) is set to sizeof(Particle) to accomodate the three attributes that we ignore. Failing to do so will result in a corrupted image.

In order to draw we use glDrawTransformFeedback() again. The second parameter is the transform feedback object that matches the input vertex buffer. This object "knows" how many vertices to draw.

(ps_update_technique.cpp:151)

bool PSUpdateTechnique::Init()
{
    if (!Technique::Init()) {
        return false;
    }

    if (!AddShader(GL_VERTEX_SHADER, pVS)) {
        return false;
    }

    if (!AddShader(GL_GEOMETRY_SHADER, pGS)) {
        return false;
    }

    const GLchar* Varyings[4];
    Varyings[0] = "Type1";
    Varyings[1] = "Position1";
    Varyings[2] = "Velocity1";
    Varyings[3] = "Age1";

    glTransformFeedbackVaryings(m_shaderProg, 4, Varyings, GL_INTERLEAVED_ATTRIBS);

    if (!Finalize()) {
        return false;
    }

    m_deltaTimeMillisLocation = GetUniformLocation("gDeltaTimeMillis");
    m_randomTextureLocation = GetUniformLocation("gRandomTexture");
    m_timeLocation = GetUniformLocation("gTime");
    m_launcherLifetimeLocation = GetUniformLocation("gLauncherLifetime");
    m_shellLifetimeLocation = GetUniformLocation("gShellLifetime");
    m_secondaryShellLifetimeLocation = GetUniformLocation("gSecondaryShellLifetime");

    if (m_deltaTimeMillisLocation == INVALID_UNIFORM_LOCATION ||
        m_timeLocation == INVALID_UNIFORM_LOCATION ||
        m_randomTextureLocation == INVALID_UNIFORM_LOCATION) {
        m_launcherLifetimeLocation == INVALID_UNIFORM_LOCATION ||
        m_shellLifetimeLocation == INVALID_UNIFORM_LOCATION ||
        m_secondaryShellLifetimeLocation == INVALID_UNIFORM_LOCATION) {
        return false;
    }

    return true;
}

You now understand the mechanics of creating a transform feedback object, attaching a buffer to it and rendering into it. But there is still the question of what exactly goes into the feedback buffer? Is it the entire vertex? Can we specify only a subset of the attributes and what is the order between them? The answer to these questions lies in the code in boldface above. This function initializes the PSUpdateTechnique which handles the update of the particles. We use it within the scope of glBeginTransformFeedback() and glEndTransformFeedback(). To specify the attributes that go into the buffer we have to call glTransformFeedbackVaryings() before the technique program is linked. This function takes four parameters: the program handle, an array of strings with the name of the attributes, the number of strings in the array and either GL_INTERLEAVED_ATTRIBS or GL_SEPARATE_ATTRIBS. The strings in the array must contain names of output attributes from the last shader before the FS (either VS or GS). When transform feedback is active these attributes will be written into the buffer per vertex. The order will match the order inside the array. The last parameter to glTransformFeedbackVaryings() tells OpenGL either to write all the attributes as a single structure into a single buffer (GL_INTERLEAVED_ATTRIBS). Or to dedicate a single buffer for each attribute (GL_SEPARATE_ATTRIBS). If you use GL_INTERLEAVED_ATTRIBS you can only have a single transform feedback buffer bound (as we do). If you use GL_SEPARATE_ATTRIBS you will need to bind a different buffer to each slot (according to the number of attributes). Remember that the slot is specified as the second parameter to glBindBufferBase(). In addition, you are limited to no more than GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS attribute slots (which is usually 4).

Other than glTransformFeedbackVaryings() the initialization stuff is pretty standard. But note that the FS is missing from it. If we disable rasterization when we update the particles we don't need a FS...

(ps_update.vs)

#version 330

layout (location = 0) in float Type;
layout (location = 1) in vec3 Position;
layout (location = 2) in vec3 Velocity;
layout (location = 3) in float Age;

out float Type0;
out vec3 Position0;
out vec3 Velocity0;
out float Age0;

void main()
{
    Type0 = Type;
    Position0 = Position;
    Velocity0 = Velocity;
    Age0 = Age;
}

This is the VS of the particle update technique and as you can see - it is very simple. All it does is pass through the vertices to the GS (where the real action takes place).

(ps_update.gs)

#version 330

layout(points) in;
layout(points) out;
layout(max_vertices = 30) out;

in float Type0[];
in vec3 Position0[];
in vec3 Velocity0[];
in float Age0[];

out float Type1;
out vec3 Position1;
out vec3 Velocity1;
out float Age1;

uniform float gDeltaTimeMillis;
uniform float gTime;
uniform sampler1D gRandomTexture;
uniform float gLauncherLifetime;
uniform float gShellLifetime;
uniform float gSecondaryShellLifetime;

#define PARTICLE_TYPE_LAUNCHER 0.0f
#define PARTICLE_TYPE_SHELL 1.0f
#define PARTICLE_TYPE_SECONDARY_SHELL 2.0f

That's the start of the GS in the particle update technique with all the declarations and definitions that we will need. We are going to get points as input and provide points as output. All the attributes we will get from the VS will also end up in the transform feedback buffer (after having gone through some processing). There are a few uniform variables that we depend on and we also enable the application to configure the frequency of the launcher and the lifetime of the shell and the secondary shell (the launcher generates one shell according to its frequency and the shell explodes to secondary shells after its configured lifetime is expired).

vec3 GetRandomDir(float TexCoord)
{
    vec3 Dir = texture(gRandomTexture, TexCoord).xyz;
    Dir -= vec3(0.5, 0.5, 0.5);
    return Dir;
}

This is a utility function that we will use to generate a random direction for the shells. The directions are stored in a 1D texture whose elements are 3D vectors (floating point). We will later see how we populate the texture with random vectors. This function simply takes a floating point value and uses it to sample from the texture. Since all the values in the texture are in the [0.0-1.0] range we substract the vector (0.5,0.5,0.5) from the sampled result in order to move the values into the [-0.5 - 0.5] range. This allows the particles to fly in all directions.

void main()
{
    float Age = Age0[0] + gDeltaTimeMillis;

    if (Type0[0] == PARTICLE_TYPE_LAUNCHER) {
        if (Age >= gLauncherLifetime) {
            Type1 = PARTICLE_TYPE_SHELL;
            Position1 = Position0[0];
            vec3 Dir = GetRandomDir(gTime/1000.0);
            Dir.y = max(Dir.y, 0.5);
            Velocity1 = normalize(Dir) / 20.0;
            Age1 = 0.0;
            EmitVertex();
            EndPrimitive();
            Age = 0.0;
        }

        Type1 = PARTICLE_TYPE_LAUNCHER;
        Position1 = Position0[0];
        Velocity1 = Velocity0[0];
        Age1 = Age;
        EmitVertex();
        EndPrimitive();
    }

The main function of the GS contains the processing of the particles. We start by updating the age of the particle at hand and then we branch according to its type. The code above handles the case of the launcher particle. If the launcher's lifetime has expired we generate a shell particle and emit it into the transform feedback buffer. The shell gets the position of the launcher as a starting point and a random direction from the random texture. We use the global time as a pseudo random seed (not really random but the results are good enough). We make sure the minimum Y value of the direction is 0.5 so that the shell is emitted in the general direction of the sky. The direction vector is then normalized and divided by 20 to provide the velocity vector (you may need to tune that for your system). The age of the new particle is ofcourse zero and we also reset the age of the launcher to get that process started again. In addition, we always output the launcher itself back into the buffer (else no more particles will be created).

    else {
        float DeltaTimeSecs = gDeltaTimeMillis / 1000.0f;
        float t1 = Age0[0] / 1000.0;
        float t2 = Age / 1000.0;
        vec3 DeltaP = DeltaTimeSecs * Velocity0[0];
        vec3 DeltaV = vec3(DeltaTimeSecs) * (0.0, -9.81, 0.0);

Before we start handling the shell and the secondary shell we setup a few variables that are common to both. The delta time is translated from milliseconds to seconds. We translate the old age of the particle (t1) and the new age (t2) to seconds as well. The change in the position is calculated according to the equation 'position = time * velocity'. Finally we calculate the change in velocity by multiplying the delta time by the gravity vector. The particle gains a velocity vector when it is born, but after that the only force that affects it (ignoring wind, etc) is gravity. The speed of a falling object on earth increases by 9.81 meters per second for every second. Since the direction is downwards we get a negative Y component and zero on the X and Z. We use a bit of a simplified calculation here but it serves its purpose.

        if (Type0[0] == PARTICLE_TYPE_SHELL) {
            if (Age < gShellLifetime) {
                Type1 = PARTICLE_TYPE_SHELL;
                Position1 = Position0[0] + DeltaP;
                Velocity1 = Velocity0[0] + DeltaV;
                Age1 = Age;
                EmitVertex();
                EndPrimitive();
            }
            else {
                for (int i = 0 ; i < 10 ; i++) {
                    Type1 = PARTICLE_TYPE_SECONDARY_SHELL;
                    Position1 = Position0[0];
                    vec3 Dir = GetRandomDir((gTime + i)/1000.0);
                    Velocity1 = normalize(Dir) / 20.0;
                    Age1 = 0.0f;
                    EmitVertex();
                    EndPrimitive();
                }
            }
        }

We now take care of the shell. As long as the age of this particle hasn't reached its configured lifetime it remains in the system and we only update its position and velocity based on the deltas we calculated earlier. Once it reaches the end of its life it is destroyed and instead we generate 10 secondary particles and emit them into the buffer. They all gain the position of their parent shell but each gets its own random velocity vector. In the case of the secondary shell we don't limit the direction so the explosion looks real.

        else {
            if (Age < gSecondaryShellLifetime) {
                Type1 = PARTICLE_TYPE_SECONDARY_SHELL;
                Position1 = Position0[0] + DeltaP;
                Velocity1 = Velocity0[0] + DeltaV;
                Age1 = Age;
                EmitVertex();
                EndPrimitive();
            }
        }
    }
}

Handling of the secondary shell is similar to the shell, except that when it reaches the end of its life it simply dies and no new particle is generated.

(random_texture.cpp:37)

bool RandomTexture::InitRandomTexture(unsigned int Size)
{
    Vector3f* pRandomData = new Vector3f[Size];

    for (unsigned int i = 0 ; i < Size ; i++) {
        pRandomData[i].x = RandomFloat();
        pRandomData[i].y = RandomFloat();
        pRandomData[i].z = RandomFloat();
    }

    glGenTextures(1, &m_textureObj);
    glBindTexture(GL_TEXTURE_1D, m_textureObj);
    glTexImage1D(GL_TEXTURE_1D, 0, GL_RGB, Size, 0.0f, GL_RGB, GL_FLOAT, pRandomData);
    glTexParameterf(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_REPEAT);

    delete [] pRandomData;

    return GLCheckError();
}

The RandomTexture class is a useful tool that can provide random data from within the shaders. It is a 1D texture with the GL_RGB internal format and floating point data type. This means that every element is a vector of 3 floating point values. Note that we set the wrap mode to GL_REPEAT. This allows us to use any texture coordinate to access the texture. If the texture coordinate is more than 1.0 it is simply wrapped around so it always retrieves a valid value. In this series of tutorials the texture unit 3 will be dedicated for random textures. You can see the setup of the texture units in the header file engine_common.h.

Next tutorial


Tutorial 29 - 3D Picking

Tutorial 29:

3D Picking

Background

The ability to match a mouse click on a window showing a 3D scene to the primitive (let's assume a triangle) who was fortunate enough to be projected to the exact same pixel where the mouse hit is called 3D Picking. This can be useful for various interactive use cases which require the application to map a mouse click by the user (which is 2D in nature) to something in the local/world space of the objects in the scene. For example, you can use it to select an object or part of it to be the target for future operations (e.g. deletion, etc). In this tutorial demo we render a couple of objects and show how to mark the "touched" triangle in red and make it stand out.

To implement 3D picking we will take advantage of an OpenGL feature that was introduced in the shadow map tutorial (#23) - the Framebuffer Object (FBO). Previously we used the FBO for depth buffering only because we were interested in comparing the depth of a pixel from two different viewpoints. For 3D picking we will use both a depth buffer as well as a color buffer to store the indices of the rendered triangles.

The trick behind 3D picking is very simple. We will attach a running index to each triangle and have the FS output the index of the triangle that the pixel belongs to. The end result is that we get a "color" buffer that doesn't really contain colors. Instead, for each pixel which is covered by some primitive we get the index of this primitive. When the mouse is clicked on the window we will read back that index (according to the location of the mouse) and render the select triangle red. By combining a depth buffer in the process we guarantee that when several primitives are overlapping the same pixel we get the index of the top-most primitive (closest to the camera).

This, in a nutshell, is 3D picking. Before going into the code, we need to make a few design decisions. For example, how do we deal with multiple objects? how do we deal with multiple draw calls per object? Do we want the primitive index to increase from object to object so that each primitive in the scene have a unique index or will it reset per object?

The code in this tutorial takes a general purpose approach which can be simplified as needed. We will render a three level index for each pixel:

  1. The index of the object that the pixel belongs to. Each object in the scene will get a unique index.
  2. The index of the draw call within the object. This index will reset at the start of a new object.
  3. The primitive index inside the draw call. This index will reset at the start of each draw call.

When we read back the index for a pixel we will actually get the above trio. We will then need to work our way back to the specific primitive.

We will need to render the scene twice. Once to a so called "picking texture" that will contain the primitive indices and a second time to the actual color buffer. Therefore, the main render loop will have a picking phase and a rendering phase.

Note: the spider model that is used for the demo comes from the Assimp source package. It contains multiple VBs which allows us to test this case.

Source walkthru

(picking_texture.h:23)

            class PickingTexture
{
public:
PickingTexture();

~PickingTexture();

bool Init(unsigned int WindowWidth, unsigned int WindowHeight);

void EnableWriting();

void DisableWriting();

struct PixelInfo {
float ObjectID;
float DrawID;
float PrimID;

PixelInfo()
                {
ObjectID = 0.0f;
DrawID = 0.0f;
PrimID = 0.0f;
}
};

PixelInfo ReadPixel(unsigned int x, unsigned int y);

private:
GLuint m_fbo;
GLuint m_pickingTexture;
GLuint m_depthTexture;
};

The PickingTexture class represents the FBO which we will render the primitive indices into. It encapsulates the framebuffer object handle, a texture object for the index info and a texture object for the depth buffer. It is initialized with the same window width and height as our main window and provides three key functions. EnableWriting() must be called at the start of the picking phase. After that we render all the relevant objects. At the end we call DisableWriting() to go back to the default framebuffer. To read back the index of a pixel we call ReadPixel() with its screen space coordinate. This function returns a structure with the three indices (or IDs) that were described in the background section. If the mouse click didn't touch any object at all the PrimID field of the PixelInfo structure will contain 0xFFFFFFFF.

(picking_texture.cpp:48)

bool PickingTexture::Init(unsigned int WindowWidth, unsigned int WindowHeight)
{
    // Create the FBO
    glGenFramebuffers(1, &m_fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);

    // Create the texture object for the primitive information buffer
    glGenTextures(1, &m_pickingTexture);
    glBindTexture(GL_TEXTURE_2D, m_pickingTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, WindowWidth, WindowHeight,
                0, GL_RGB, GL_FLOAT, NULL);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                m_pickingTexture, 0);

    // Create the texture object for the depth buffer
    glGenTextures(1, &m_depthTexture);
    glBindTexture(GL_TEXTURE_2D, m_depthTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, WindowWidth, WindowHeight,
                0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
                m_depthTexture, 0);

    // Disable reading to avoid problems with older GPUs
    glReadBuffer(GL_NONE);

    glDrawBuffer(GL_COLOR_ATTACHMENT0);

    // Verify that the FBO is correct
    GLenum Status = glCheckFramebufferStatus(GL_FRAMEBUFFER);

    if (Status != GL_FRAMEBUFFER_COMPLETE) {
        printf("FB error, status: 0x%x\n", Status);
        return false;
    }

    // Restore the default framebuffer
    glBindTexture(GL_TEXTURE_2D, 0);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    return GLCheckError();
}

The above code initializes the PickingTexture class. We generate a FBO and bind it to the GL_FRAMEBUFFER target. We then generate two texture objects (for pixel info and depth). Note that the internal format of the texture that will contain the pixel info is GL_RGB32F. This means each texel is a vector of 3 floating points. Even though we are not initializing this texture with data (last parameter of glTexImage2D is NULL) we still need to supply correct format and type (7th and 8th params). The format and type that match GL_RGB32F are GL_RGB and GL_FLOAT, respectively. Finally we attach this texture to the GL_COLOR_ATTACHMENT0 target of the FBO. This will make it the target of the output from the fragment shader.

The texture object of the depth buffer is created and attached in the exact same way as in the shadow map tutorial so we will not review it again here. After everything is initialized we check the status of the FBO and restore the default object before returning.

(picking_texture.cpp:82)

void PickingTexture::EnableWriting()
{
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo);
}

Before we start rendering into the picking texture we need to enable it for writing. This means binding the FBO to the GL_DRAW_FRAMEBUFFER.

(picking_texture.cpp:88)

void PickingTexture::DisableWriting()
{
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
}

After we finish rendering into the picking texture we tell OpenGL that from now on we want to render into the default framebuffer by binding zero to the GL_DRAW_FRAMEBUFFER target.

PickingTexture::PixelInfo PickingTexture::ReadPixel(unsigned int x, unsigned int y)
{
    glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo);
    glReadBuffer(GL_COLOR_ATTACHMENT0);

    PixelInfo Pixel;
    glReadPixels(x, y, 1, 1, GL_RGB, GL_FLOAT, &Pixel);

    glReadBuffer(GL_NONE);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);

    return Pixel;
}

This function takes a coordinate on the screen and returns the corresponding texel from the picking texture. This texel is 3-vector of floats which is exactly what the structure PixelInfo contains. To read from the FBO we must first bind it to the GL_READ_FRAMEBUFFER target. Then we need to specify which color buffer to read from using the function glReadBuffer(). The reason is that the FBO can contain multiple color buffers (which the FS can render into simultaneously) but we can only read from one buffer at a time. The function glReadPixels does the actual reading. It takes a rectangle which is specified using its bottom left corner (first pair of params) and its width/height (second pair of params) and reads the results into the address given by the last param. The rectangle in our case is one texel in size. We also need to tell this function the format and data type because for some internal formats (such as signed or unsigned normalized fixed point) the function is capable of converting the internal data to a different type on the way out. In our case we want the raw data so we use GL_RGB as the format and GL_FLOAT as the type. After we finish we must reset the reading buffer and the framebuffer.

(picking.vs)

#version 330

layout (location = 0) in vec3 Position;

uniform mat4 gWVP;

void main()
{
    gl_Position = gWVP * vec4(Position, 1.0);
}

This is the VS of the PickingTechnique class. This technique is responsible for rendering the pixel info into the PickingTexture object. As you can see, the VS is very simple since we only need to transform the vertex position.

(picking.fs)

#version 330

uniform uint gDrawIndex;
uniform uint gObjectIndex;

out vec3 FragColor;

void main()
{
    FragColor = vec3(float(gObjectIndex), float(gDrawIndex),float(gl_PrimitiveID + 1));
}

The FS of PickingTechnique writes the pixel information into the picking texture. The object index and draw index are the same for all pixels (in the same draw call) so they come from uniform variables. In order to get the primitive index we use the built-in variable gl_PrimitiveID. This is a running index of the primitives which is automatically maintained by the system. gl_PrimitiveID can only be used in the GS and PS. If the GS is enabled and the FS wants to use gl_PrimitiveID, the GS must write gl_PrimitiveID into one of its output variables and the FS must declare a variable by the same name for input. In our case we have no GS so we can simply use gl_PrimitiveID.

The system resets gl_PrimitiveID to zero at the start of the draw. This makes it difficult for us to distinguish between "background" pixels and pixels that are actually covered by objects (how would you know whether the pixel is in the background or belongs to the first primitive?). To overcome this we increment the index by one before writing it to the output. This means that background pixels can be identified because their primitive ID is zero while pixels covered by objects have 1...n as a primitive ID. We will see later that we compensate this when we use the primitive ID to render the specific triangle.

(render_callbacks.h:21)

class IRenderCallbacks
{
public:
    virtual void DrawStartCB(unsigned int DrawIndex) = 0;
};

The picking technique requires the application to update the draw index before each draw call. This presents a design problem because the current mesh class (in the case of a mesh with multiple VBs) internally iterates over the vertex buffers and submit a separate draw call per IB/VB combination. This doesn't give us the chance to update the draw index. The solution we adopt here is the interface class above. The PickingTechnique class inherits from this interface and implements the method above. The Mesh::Render() function now takes a pointer to the above interface and calls the only function in it before the start of a new draw. This provides a nice separation between the Mesh class and any technique that wishes to get a callback before a draw is submitted.

(mesh.cpp:201)

void Mesh::Render(IRenderCallbacks* pRenderCallbacks)
{
    ...

    for (unsigned int i = 0 ; i < m_Entries.size() ; i++) {

        ...

        if (pRenderCallbacks) {
            pRenderCallbacks->DrawStartCB(i);
        }

        glDrawElements(GL_TRIANGLES, m_Entries[i].NumIndices, GL_UNSIGNED_INT, 0);
    }

    ...
}

The code above shows part of the updated Mesh::Render() function with the new code marked in bold. If the caller is not interested in getting a callback for each draw it can simply pass NULL as the function argument.

(picking_technique.cpp:93)

void PickingTechnique::DrawStartCB(unsigned int DrawIndex)
{
    glUniform1ui(m_drawIndexLocation, DrawIndex);
}

This is the implementation of IRenderCallbacks::DrawStartCB() by the inheriting class PickingTechnique. The function Mesh::Render() provides the draw index which is passed as a shader uniform variable. Note that PickingTechnique also has a function to set the object index but this one is called directly by the main application code without the need for the mechanism above.

(tutorial29.cpp:108)

virtual void RenderSceneCB()
{
    m_pGameCamera->OnRender();

    PickingPhase();
    RenderPhase();

    glutSwapBuffers();
}

This is the main render function. The functionality has been split into two core phases, one to draw the objects into the picking texture, and the other to render the objects and handle the mouse click.

(tutorial29.cpp:119)

void PickingPhase()
{
    Pipeline p;
    p.Scale(0.1f, 0.1f, 0.1f);
    p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());
    p.SetPerspectiveProj(m_persProjInfo);

    m_pickingTexture.EnableWriting();

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    m_pickingEffect.Enable();

    for (unsigned int i = 0 ; i < ARRAY_SIZE_IN_ELEMENTS(m_worldPos) ; i++) {
        p.WorldPos(m_worldPos[i]);
        m_pickingEffect.SetObjectIndex(i);
        m_pickingEffect.SetWVP(p.GetWVPTrans());
        m_pMesh->Render(&m_pickingEffect);
    }

    m_pickingTexture.DisableWriting();
}

The picking phase starts by setting up the Pipeline object in the usual way. We then enable the picking texture for writing and clear the color and depth buffer. glClear() works on the currently bound framebuffer - the picking texture in our case. The 'm_worldPos' array contains the world position of the two object instances that are rendered by the demo (both using the same mesh object for simplicity). We loop over the array, set the position in the Pipeline object one by one and render the object. For each iteration we also update the object index into the picking technique. Note how the Mesh::Render() function takes the address of the picking technique object as a parameter. This allows it to call back into the technique before each draw call. Before leaving, we disable writing into the picking texture which restores the default framebuffer.

(tutorial29.cpp:144)

void RenderPhase()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    Pipeline p;
    p.Scale(0.1f, 0.1f, 0.1f);
    p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());
    p.SetPerspectiveProj(m_persProjInfo);

    // If the left mouse button is clicked check if it hit a triangle
    // and color it red
    if (m_leftMouseButton.IsPressed) {
        PickingTexture::PixelInfo Pixel = m_pickingTexture.ReadPixel(m_leftMouseButton.x,
                                                WINDOW_HEIGHT - m_leftMouseButton.y - 1);
        if (Pixel.PrimID != 0) {
            m_simpleColorEffect.Enable();
            p.WorldPos(m_worldPos[(uint)Pixel.ObjectID]);
            m_simpleColorEffect.SetWVP(p.GetWVPTrans());
            // Must compensate for the decrement in the FS!
            m_pMesh->Render((uint)Pixel.DrawID, (uint)Pixel.PrimID - 1);
        }
    }

    // render the objects as usual
    m_lightingEffect.Enable();
    m_lightingEffect.SetEyeWorldPos(m_pGameCamera->GetPos());

    for (unsigned int i = 0 ; i < ARRAY_SIZE_IN_ELEMENTS(m_worldPos) ; i++) {
        p.WorldPos(m_worldPos[i]);
        m_lightingEffect.SetWVP(p.GetWVPTrans());
        m_lightingEffect.SetWorldMatrix(p.GetWorldTrans());
        m_pMesh->Render(NULL);
    }
}

After the picking phase comes the rendering phase. We setup the Pipeline same as before. We then check if the left mouse button is pressed. If it is we use PickingTexture::ReadPixel() to fetch the pixel information. Since the FS increments the primitive ID it writes to the picking texture all background pixels have an ID of 0 while covered pixels have ID of 1 or more. If the pixel is covered by an object we enable a very basic technique that simply returns the red color from the FS. We update the Pipeline object with the world position of the selected object using the pixel information. We use a new render function of the Mesh class that takes the draw and primitive IDs as parameters and draws the requested primitive in red (note that we must decrement the primitive ID because the Mesh class starts the primitive count at zero). Finally, we render the primitives as usual.

(glut_backend.cpp:60)

static void MouseCB(int Button, int State, int x, int y)
{
    s_pCallbacks->MouseCB(Button, State, x, y);
}

static void InitCallbacks()
{
    ...
    glutMouseFunc(MouseCB);
}

This tutorial requires the application to trap mouse clicks. The function glutMouseFunc() does exactly that. There is a new callback function for that in the ICallbacks interface (which the main application class inherits from). You can use enums such as GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON, and GLUT_RIGHT_BUTTON to identify the button which was pressed (first argument to MouseCB()). The 'State' parameter tells us whether the button was pressed (GLUT_DOWN) or released (GLUT_UP).

Next tutorial


Tutorial 30 - Basic Tessellation

Tutorial 30:

Basic Tessellation

Background

Tessellation is an exciting new feature in OpenGL 4.x. The core problem that Tessellation deals with is the static nature of 3D models in terms of their detail and polygon count. The thing is that when we look at a complex model such as a human face up close we prefer to use a highly detailed model that will bring out the tiny details (e.g. skin bumps, etc). A highly detailed model automatically translates to more triangles and more compute power required for processing. When we render the same model at a greater distance we prefer to use a lower detailed model and allow more compute resources to the objects that are closer to the camera. This is simply a matter of balancing GPU resources and diverting more resources to the area near the camera where small details are more noticeable.

One possible way to solve this problem using the existing features of OpenGL is to generate the same model at multiple levels of detail (LOD). For example, highly detailed, average and low. We can then select the version to use based on the distance from the camera. This, however, will require more artist resources and often will not be flexible enough. What we need is a way to start with a low polygon model and subdivide each triangle on the fly into smaller triangles. This, in a nutshell, is Tessellation. Being able to do all this dynamically on the GPU and also select the level of detail per triangle is part of what the Tessellation pipeline in OpenGL 4.x provides.

Tessellation has been defined and integrated into the OpenGL spec after several years of research both in the academia as well as the industry. Its design was heavily influenced by the mathematical background of geometric surfaces and curves, Bezier patches and subdivision. We will engage Tessellation in two steps. In this tutorial we will focus on the new mechanics of the pipeline in order to get Tessellation up and running without too much mathematical hassle. The technique itself will be simple but it will expose all the relevant components. In the next tutorial we will study Bezier patches and see how to apply them to a Tessellation technique.

Let's take a look at how Tessellation has been implemented in the graphics pipeline. The core components that are responsible for Tessellation are two new shader stages and in between them a fixed function stage that can be configured to some degree but does not run a shader. The first shader stage is called Tessellation Control Shader (TCS), the fixed function stage is called the Primitive Generator (PG), and the second shader stage is called Tessellation Evaluation Shader (TES). Here's a diagram showing the location of the new stages in the pipeline:

The TCS works on a group of vertices called Control Points (CP). The CPs don't have a well defined polygonal form such as a triangle, square, pentagon or whatever. Instead, they define a geometric surface. This surface is usually defined by some polynomial formula and the idea is that moving a CP has an effect on the entire surface. You are probably familiar with some graphic software that allows you to define surfaces or curves using a set of CPs and shape them by moving the CPs. The group of CPs is usually called a Patch. The yellow surface in the following picture is defined by a patch with 16 CPs:

The TCS takes an input patch and emits an output patch. The developer has the option in the shader to do some transformation on the CPs or even add/delete CPs. In addition to the output patch the control shader calculates a set of numbers called Tessellation Levels (TL). The TLs determine the Tessellation level of detail - how many triangles to generate for the patch. Since all this happens in a shader the developer has the freedom to use any algorithm in order to calculate the TLs. For example, we can decide that the TLs will be 3 if the rasterized triangle is going to cover less than a 100 pixels, 7 in case of 101 to 500 pixels and 12.5 for everything above that (we will later see how the value of the TL translates into coarser or finer Tesssellation). Another algoritm can be based on a distance from the camera. The nice thing about all of this is that each patch can get different TLs according to its own characteristics.

After the TCS finishes comes the fixed function PG whose job is to do the actual subdivision. This is probably the most confusing point for newcomers. The thing is that the PG doesn't really subdivides the output patch of the TCS. In fact, it doesn't even have access to it. Instead, it takes the TLs and subdivides what is called a Domain. The domain can either be a normalized (in the range of 0.0-1.0) square of 2D coordinates or an equilateral triangle defined by 3D barycentric coordinates:

Barycentric coordinates of a triangle is a method of defining a location inside a triangle as a combination of the weight of the three vertices. The vertices of the triangle as designated as U, V and W and as the location gets closer to one vertex its weight increases while the weight of the other vertices decreases. If the location is exactly on a vertex the weight of that vertex is 1 while the other two are zero. For example, the barycentric coordinate of U is (1,0,0), V is (0,1,0) and W is (0,0,1). The center of the triangle is on the barycentric coordinate of (1/3,1/3,1/3). The interesting property of barycentric coordinates is that if we sum up the individual components of the barycentric coordinate of each and every point inside the triange we always get 1. For simplicity let's focus on the triangle domain from now on.

The PG takes the TLs and based on their values generates a set of points inside the triangle. Each point is defined by its own barycentric coordinate. The developer can configure the output topology to be either points or triangles. If points are chosen then the PG simply sends them down the pipeline to be rasterized as points. If triangles are chosen the PG connects all the points together so that the entire face of the triangle is tessellated with smaller triangles:

In general, the TLs tell the PG the number of segments on the outer edge of the triangle and the number rings towards the center

So how do the small triangles in the above picture relate to the patch that we saw earlier? Well, it depends on what you want to do with Tessellation. One very simple option (and the one that we will use in this tutorial) is to skip the whole notion of curved geometric surfaces with their polynomial representation and simply say that the triangles from your model are simply mapped to patches. In that case the 3 triangle vertices become our 3 CPs and the original triangle is both the input and output patch of the TCS. We use the PG to tessellate the triangle domain and generate small "generic" triangles represented by barycentric coordinates and use a linear combination of these coordinates (i.e. multiply them by the attributes of the original triangle) in order to tessellate the triangles of the original model. In the next tutorial we will see an actual use of the patch as a representative of a geometric surface. At any rate, remember that the PG ignores both the input and output patch of the TCS. All it cares about are the per patch TLs.

So after the PG has finished subdividing the triangle domain we still need someone to take the results of this subdivision and do something with it. After all, the PG doesn't even have access to the patch. Its only output are barycentric coordinates and their connectivity. Enter the TES. This shader stage has access both to the output patch of the TCS and the barycentric coordinates that the PG generated. The PG executes the TES on every barycentric coordinate and the job of the TES is to generate a vertex for that point. Since the TES has access to the patch it can take stuff from it such as position, normal, etc and use them to generate the vertex. After the PG executes the TES on the three barycentric coordinates of a "small" triangle it takes the three vertices the TES generated and sends them down as a complete triangle for rasterization.

The TES is similar to the VS in the sense that it always has a single input (the barycentric coordinate) and a single output (the vertex). The TES cannot generate more than one vertex per invocation nor can it decide to drop the vertex. The main purpose of the TES that the architects of Tessellation in OpenGL envisioned is to evaluate the surface equation at the given domain location. In simpler terms this means placing the barycentric coordinate in the polynomial that represents the surface and calculate the result. The result is the position of the new vertex which can then be transformed and projected as usual. As you can see, when dealing with geometric surfaces the higher we choose our TLs, the more domain locations we get and by evaluating them in the TES we get more vertices that better represent the true mathematical surface. In this tutorial the evaluation of the surface equation will simply be a linear combination.

After the TES has processed the domain locations the PG takes the new vertices and sends them as triangles to the next stages of the pipeline. After the TES comes either the GS or the rasterizer and from here on everything runs as usual.

Let's summarize the entire pipeline:

  1. The VS is executed on every vertex in a patch. The patch comprises several CPs from the vertex buffer (up to a limit defined by the driver and GPU).
  2. The TCS takes the vertices that have been processed by the VS and generates an output patch. In addition, it generates TLs.
  3. Based on the configured domain, the TLs it got from the TCS and the configured output topology, the PG generates domain location and their connectivity.
  4. The TES is executed on all generated domain locations.
  5. The primitives that were generated in step 3 continue down the pipe. The output from the TES is their data.
  6. Processing continues either at the GS or at the rasterizer.

Source walkthru

(tutorial30.cpp:80)

GLint MaxPatchVertices = 0;
glGetIntegerv(GL_MAX_PATCH_VERTICES, &MaxPatchVertices);
printf("Max supported patch vertices %d\n", MaxPatchVertices);
glPatchParameteri(GL_PATCH_VERTICES, 3);

When Tessellation is enabled (i.e. when we have either a TCS or a TES) the pipeline needs to know how many vertices comprise each input patch. Remember that a patch does not necessarily have a defined geometric form. It is simply a list of control points. The call to glPatchParameteri() in the code excerpt above tells the pipeline that the size of the input patch is going to be 3. That number can be up to what the driver defines as GL_MAX_PATCH_VERTICES. This value can be different from one GPU/driver to another so we fetch it using glGetIntegerv() and print it.

(lighting.vs)

#version 410 core

layout (location = 0) in vec3 Position_VS_in;
layout (location = 1) in vec2 TexCoord_VS_in;
layout (location = 2) in vec3 Normal_VS_in;

uniform mat4 gWorld;

out vec3 WorldPos_CS_in;
out vec2 TexCoord_CS_in;
out vec3 Normal_CS_in;

void main()
{
    WorldPos_CS_in = (gWorld * vec4(Position_VS_in, 1.0)).xyz;
    TexCoord_CS_in = TexCoord_VS_in;
    Normal_CS_in = (gWorld * vec4(Normal_VS_in, 0.0)).xyz;
}

This is our VS and the only difference between it and the previous ones is that we are no longer transforming the local space coordinates to clip space (by multiplying by the world-view-projection matrix). The reason is that there is simply no point in that. We expect to generate a lot of new vertices that will need that transformation anyway. Therefore, this action is postponed until we get to the TES.

(lighting.cs)

#version 410 core

// define the number of CPs in the output patch
layout (vertices = 3) out;

uniform vec3 gEyeWorldPos;

// attributes of the input CPs
in vec3 WorldPos_CS_in[];
in vec2 TexCoord_CS_in[];
in vec3 Normal_CS_in[];

// attributes of the output CPs
out vec3 WorldPos_ES_in[];
out vec2 TexCoord_ES_in[];
out vec3 Normal_ES_in[];

This is the start of the TCS. It is executed once per CP in the output patch and we start by defining the number of CPs in the output patch. Next we define a uniform variable that we will need in order to calculate the TLs. After that we have a few input and output CP attributes. In this tutorial we have the same structure for both the input and output patch but it doesn't always have to be this way. Each input and output CP has a world position, texture coordinate and normal. Since we can have more than one CP in the input and output patches each attribute is defined using the array modifier []. This allows us to freely index into any CP.

(lighting.cs:33)

void main()
{
    // Set the control points of the output patch
    TexCoord_ES_in[gl_InvocationID] = TexCoord_CS_in[gl_InvocationID];
    Normal_ES_in[gl_InvocationID] = Normal_CS_in[gl_InvocationID];
    WorldPos_ES_in[gl_InvocationID] = WorldPos_CS_in[gl_InvocationID];

We start the main function of the TCS by copying the input CP into the output CP. This function is executed once per output CP and the builtin variable gl_InvocationID contains the index of the current invocation. The order of execution is undefined because the GPU probably distributes the CPs across several of its cores and runs them in parallel. We use gl_InvocationID as an index into both the input and output patch.

(lighting.cs:40)

    // Calculate the distance from the camera to the three control points
    float EyeToVertexDistance0 = distance(gEyeWorldPos, WorldPos_ES_in[0]);
    float EyeToVertexDistance1 = distance(gEyeWorldPos, WorldPos_ES_in[1]);
    float EyeToVertexDistance2 = distance(gEyeWorldPos, WorldPos_ES_in[2]);

    // Calculate the tessellation levels
    gl_TessLevelOuter[0] = GetTessLevel(EyeToVertexDistance1, EyeToVertexDistance2);
    gl_TessLevelOuter[1] = GetTessLevel(EyeToVertexDistance2, EyeToVertexDistance0);
    gl_TessLevelOuter[2] = GetTessLevel(EyeToVertexDistance0, EyeToVertexDistance1);
    gl_TessLevelInner[0] = gl_TessLevelOuter[2];
}

After generating the output patch we calculate the TLs. The TLs can be set differently for each output patch. OpenGL provides two builtin arrays of floating points for the TLs: gl_TessLevelOuter (size 4) and gl_TessLevelInner (size 2). In the case of a triangle domain we can use only the first 3 members of gl_TessLevelOuter and the first member from gl_TessLevelInner (in addition to the triangle domain there are also the quad and isoline domain that provide different access to arrays). gl_TessLevelOuter[] roughly determines the number of segments on each edge and gl_TessLevelInner[0] roughly determines how many rings the triangle will contain. If we designate the triangle vertices as U, V and W then the corresponding edge for each vertex is the one which is opposite to it:

The algorithm we use to calculate the TLs is very simple and is based on the distance in world space between the camera and the vertices. It is implemented in the function GetTessLevel (see below). We calculate the distance between the camera and each vertex and call GetTessLevel() three times to update each member in gl_TessLevelOuter[]. Each entry is mapped to an edge according to the picture above (TL of edge 0 goes to gl_TessLevelOuter[0], etc) and the TL for that edge is calculated based on the distance from the camera to the two vertices that create it. The inner TL is selected the same as the TL of edge W.

You can use any algorithm that you want to calculate the TLs. For example, one algorithm estimates the size of the final triangle on the screen in pixels and sets the TLs such that no tessellated triangle becomes smaller than a given number of pixels.

(lighting.cs:18)

float GetTessLevel(float Distance0, float Distance1)
{
    float AvgDistance = (Distance0 + Distance1) / 2.0;

    if (AvgDistance <= 2.0) {
        return 10.0;
    }
    else if (AvgDistance <= 5.0) {
        return 7.0;
    }
    else {
        return 3.0;
    }
}

This function calculates the TL for an edge based on the distance from the camera to the two vertices of the edge. We take the average distance and set the TL to 10 or 7 or 3. As the distance grows we prefer a smaller TL so as not to waste GPU cycles.

(lighting.es)

#version 410 core

layout(triangles, equal_spacing, ccw) in;

This is the start of the TES. The 'layout' keyword defines three configuration items:

  • triangles this is the domain the PG will work on. The other two options are quads and isolines.
  • equal_spacing means that the triangle edges will be subdivided into segments with equal lengths (according to the TLs). You can also use fractional_even_spacing or fractional_odd_spacing that provide a smoother transition between the lengths of the segments whenever the TL crosses an even or odd integer. For example, if you use fractional_odd_spacing and the TL is 5.1 it means there will be 2 very short segments and 5 longer segments. As the TL grows towards 7 all the segments become closer in length. When the TL hits 7 two new very short segments are created. fractional_even_spacing is the same with even integer TLs.
  • ccw means that the PG will emit triangles in counter-clockwise order (you can also use cw for clockwise order). You may be wondering why we are doing that while our front facing triangles in clockwise order. The reason is that the model I supplied with this tutorial (quad2.obj) was generated by Blender in counter clockwise order. I could also have specified the Assimp flag 'aiProcess_FlipWindingOrder' when loading the model and use 'cw' here. I simply didn't want to change 'mesh.cpp' at this point. The bottom line is that whatever you do, make sure you are consistent.

Note that you can also specify each configuration item with its own layout keyword. The scheme above simply saves some space.

(lighting.es:5)

uniform mat4 gVP;
uniform sampler2D gDisplacementMap;
uniform float gDispFactor;

in vec3 WorldPos_ES_in[];
in vec2 TexCoord_ES_in[];
in vec3 Normal_ES_in[];

out vec3 WorldPos_FS_in;
out vec2 TexCoord_FS_in;
out vec3 Normal_FS_in;

The TES can have uniform variables just like any other shader. The displacement map is basically a height map which means that every texel represents the height at this location. We will use it to generate bumps on the surface of our mesh. In addition, the TES can also access the entire TCS output patch. Finally, we declare the attributes of our output vertex. Note that the array modifier is not present here because the TES always outputs a single vertex.

(lighting.es:27)

void main()
{
    // Interpolate the attributes of the output vertex using the barycentric coordinates
    TexCoord_FS_in = interpolate2D(TexCoord_ES_in[0], TexCoord_ES_in[1], TexCoord_ES_in[2]);
    Normal_FS_in = interpolate3D(Normal_ES_in[0], Normal_ES_in[1], Normal_ES_in[2]);
    Normal_FS_in = normalize(Normal_FS_in);
    WorldPos_FS_in = interpolate3D(WorldPos_ES_in[0], WorldPos_ES_in[1], WorldPos_ES_in[2]);

This is the main function of the TES. Let's recap what we have when we get here. The mesh vertices were processed by the VS and the world space position and normal were calculated. The TCS got each triangle as a patch with 3 CPs and simply passed it through to the TES. The PG subdivided an equilateral triangle into smaller triangles and executed the TES for every generated vertex. In each TES invocation we can access the barycentric coordinates (a.k.a Tessellation Coordinates) of the vertex in the 3D-vector gl_TessCoord. Since the barycentric coordinates within a triangle represent a weight combination of the 3 vertices we can use it to interpolate all the attributes of the new vertex. The functions interpolate2D() and interpolate3D() (see below) do just that. They take an attribute from the CPs of the patch and interpolate it using gl_TessCoord.

(lighting.es:35)

    // Displace the vertex along the normal
    float Displacement = texture(gDisplacementMap, TexCoord_FS_in.xy).x;
    WorldPos_FS_in += Normal_FS_in * Displacement * gDispFactor;
    gl_Position = gVP * vec4(WorldPos_FS_in, 1.0);
}

Having each triangle of the original mesh subdivided into many smaller triangles doesn't really contribute much to the general appearance of the mesh because the smaller triangles are all on the same plane of the original triangle. We would like to offset (or displace) each vertex in a way that will match the contents of our color texture. For example, if the texture contains the image of bricks or rocks we would like our vertices to move along the edges of the bricks or rocks. To do that we need to complement the color texture with a displacement map. There are various tools and editors that generate a displacement map and we are not going to go into the specifics here. You can find more information on the web. To use the displacement map we simply need to sample from it using the current texture coordinate and this will give us the height of this vertex. We then displace the vertex in world space by multiplying the vertex normal by the height and by a displacement factor uniform variable that can be controlled by the application. So every vertex is displaced along its normal based on its height. Finally, we multiply the new world space position by the view-projection matrix and set it into 'gl_Position'.

(lighting.es:17)

vec2 interpolate2D(vec2 v0, vec2 v1, vec2 v2)
{
    return vec2(gl_TessCoord.x) * v0 + vec2(gl_TessCoord.y) * v1 + vec2(gl_TessCoord.z) * v2;
}

vec3 interpolate3D(vec3 v0, vec3 v1, vec3 v2)
{
    return vec3(gl_TessCoord.x) * v0 + vec3(gl_TessCoord.y) * v1 + vec3(gl_TessCoord.z) * v2;
}

These two function interpolate between a trio of 2D-vectors and 3D-vectors using 'gl_TessCoord' as a weight.

(lighting_technique.cpp:277)

bool LightingTechnique::Init()
{
    ...
    if (!AddShader(GL_TESS_CONTROL_SHADER, pTessCS)) {
        return false;
    }

    if (!AddShader(GL_TESS_EVALUATION_SHADER, pTessES)) {
        return false;
    }
    ...

We have two new shader stage so we must compile them.

(mesh.cpp:226)

glDrawElements(GL_PATCHES, m_Entries[i].NumIndices, GL_UNSIGNED_INT, 0);

Finally, we have to use GL_PATCHES as the primitive type instead of GL_TRIANGLES.

The Demo

The demo in this tutorial shows how to tessellate a quad terrain and displace vertices along the rocks in the color texture. You can use '+' and '-' on the keyboard to update the displacement factor and by that control the displacement level. You can also switch to wireframe mode using 'z' and see the actual triangles generated by the Tessellation process. It is interesting to move closer and further away from the terrain in wireframe mode and see how the Tessellation level changes based on the distance. This is why we need the TCS.

Next tutorial


Tutorial 31 - PN Triangles Tessellation

Tutorial 31:

PN Triangles Tessellation

Background

In the previous tutorial we got introduced to Tessellation in OpenGL 4.x (this tutorial relies heavily on the material covered by the previous one so make sure you are familiar with it). We enabled all the relevant stages and learned how to subdivide our mesh and displace the vertices that were created by the Tessellation process in order to transform a dull flat quad into a complex rocky terrain. Usage of the Tessellation pipeline was fairly simple, though. The evaluation in the TES was just an interpolation of the vertices of the original triangle using the barycentric coordinates generated by the PG. Since the results of the interpolation were located on the plane of the original triangle we had to use displacement mapping in order to create bumps on the surface.

In this tutorial we will explore a more advanced Tessellation technique known as PN (Point-Normal) Triangles. This technique was the subject of a 2001 paper by Vlachos et al and was also covered in a GDC2011 presenation called "Tessellation On Any Budget" by John McDonald. The idea explored by these papers was to replace each triangle in the original mesh by a geometric surface known as a Bezier Surface in order to smooth out a low polygon mesh.

Bezier Surfaces were invented by Pierre Bezier in the 1960s as a method of describing the curves of automobile bodies. In a nutshell, a Bezier Surface is polynomial function which described a smooth and continuous surface which is fully contained within a set of control points (CP). The polynomial has a special attribute whereas by moving a CP the surface is affected mostly in the vicinity of that CPs. The effect becomes less visible as we move away from that CP. You can picture this as a highly delicate and flexible cloth lying on the floor. If you pull the cloth upwards at a specific point the curve that will be formed will become less and less noticeable in the distant parts of the cloth (if the cloth was infinitely flexible the effect may even become non-existant at some point).

The polynomial of the Bezier surface is defined over the unit square. That is, by plugging into the function various combinations of two numbers in the range [0-1] we get a point in 3D space which is exactly on the smooth surface that the polynomial describes. If you plug in many pairs of numbers in the unit square and plot the result on the screen you will eventually get a good approximation of the surface.

We are going to use a special case of a Bezier Surface called a Bezier Triangle which has the following form:

Let's decipher this step by step. 'u/v/w' are barycentric coordinates (i.e. they always maintain the equation 'u + v + w = 1'). The ten 'Bxyz' are CPs. We are going to deviate a bit from the classical definition of a Bezier Triangle and place the CPs as follows:

As you can see, the general form of the CPs resembles a somewhat puffy surface on top of a triangle. By evaluating a lot of barycentric coordinates in the polynomial above we will get an exproximation of that surface in 3D space.

Let's see how to integrate these mathematical concepts into the Tessellation pipeline. We are going to start with a triangle and this will be our input patch (same as in the previous tutorial). We will generate the 10 CPs and determine the TLs in the TCS. The PG will subdivide the triangle domain according to the TLs and the TES will be executed for each new point. The TES will plug the barycentric coordinates from the PG and the 10 CPs from the TCS into the polynomial of the Bezier triangle and the result will be a coordinate on the puffy surface. From here on things will run as usual.

The one thing we still need to figure out is how to generate the CPs. The method suggested by the PN Triangles algorithm is as follows:

  • The original vertices of the triangle remain unchanged (and are named B003, B030 and B300).
  • Two midpoints are generated on each edge - one on 1/3 of the way the other on 2/3.
  • Each midpoint is projected on the plane created by the nearest vertex and its normal:

The picture above shows the triangle from the side. Each of the two endpoints has its own normal (in green) from the original mesh. The combination of a point and a normal creates a plane. We take the two midpoints that were calculated earlier and project them to the plane of the nearest vertex (see the dashed arrows).

  • In order to calculate the position of B111 we take a vector from the original triangle center (average of the three vertices) to the average of the 6 midpoints (after projection). We continue along that vector for one half of its length.

The reasoning behind this scheme is very simple. When you have an area of the mesh which is fairly flat it means that most vertex normals there will point towards the same general direction which will not be far off from the true triangle normal. This means that when we project the midpoints on the planes they would not move away very far from the triangle surface. This will result in a mild "puffiness" in that area. But if the area is very curved it means the midpoints would move further away to overcome the jagged nature of that area. In the demo you can see that we start with a low polygon model of Suzanne, Blender's mascot character, which has about 500 polygons. Breaking of the silhouette is very noticeable, particularly around Suzanne's head. By projecting the midpoints as described above to create CPs and using the Tessellator to evaluate the Bezier Triangle created by this CPs we are able to provide a much smoother model without any artistic resources.

References:

  • Vlachos Alex, Jorg Peters, Chas Boyd and Jason L. Mitchell. "Curved PN Triangles". Proceedings of the 2001 Symposium interactive 3D graphics (2001): 159-66.
  • John McDonald. "Tessellation On Any Budget". Game Developers Conference, 2011.

Source walkthru

(lighting.vs:13)

void main()
{
    WorldPos_CS_in = (gWorld * vec4(Position_VS_in, 1.0)).xyz;
    TexCoord_CS_in = TexCoord_VS_in;
    Normal_CS_in = normalize((gWorld * vec4(Normal_VS_in, 0.0)).xyz);
}

The VS contains only one change from the previous tutorial - the normal must be normalized after the world transformation. The reason is that the TCS relies on the normal having a unit length. Otherwise, the new CPs above the surface won't be generated correctly. If the world transformation contains a scaling operation the normals won't have unit length and have to be normalized.

(lighting.cs)

#version 410 core

// define the number of CPs in the output patch
layout (vertices = 1) out;

uniform float gTessellationLevel;

// attributes of the input CPs
in vec3 WorldPos_CS_in[];
in vec2 TexCoord_CS_in[];
in vec3 Normal_CS_in[];

struct OutputPatch
{
    vec3 WorldPos_B030;
    vec3 WorldPos_B021;
    vec3 WorldPos_B012;
    vec3 WorldPos_B003;
    vec3 WorldPos_B102;
    vec3 WorldPos_B201;
    vec3 WorldPos_B300;
    vec3 WorldPos_B210;
    vec3 WorldPos_B120;
    vec3 WorldPos_B111;
    vec3 Normal[3];
    vec2 TexCoord[3];
};

// attributes of the output CPs
out patch OutputPatch oPatch;

This is the start of the TCS with the changes marked in bold face. The first thing to note is that we are outputing a single CP. You may find this odd since the whole idea behind PN Triangles is to create a Bezier triangle with 10 CPs on top of the original triangle. So why are we declaring a single output CP instead of 10? the reason is that the main TCS function will be executed as many times as the defined output CPs value. In this algorithm we need to treat some of the points a bit differently than the others which makes it a bit difficult to use the same function for all points. Instead, I've encapsulated all the data of the output patch in the OutputPatch struct above and declared an output variable called oPatch of that type. The TCS main function will run once for each patch and this struct will be populated with data for all the 10 CPs. The implementation that McDonald presented in GDC 2011 (see references) provides a version which may be more efficient. In his version the TCS is executed three times which enables the GPU to distribute the work of a single patch across three threads. In general, if the output CPs are generated using the same algorithm it is better (from a performance point of view) to implement that algorithm as-is in the TCS and have it execute for as many output CPs as you need.

Another thing to note is that oPatch is prefixed by the builtin keyword patch. This keyword says that the variable contains data which pertains to the entire patch and not the current output CP. The compiler can use that as a hint to make sure that the code that updates such a variable will run once per patch instead of once per CP (since GPUs will strive to update each output CP in a different HW thread).

The final change in this section is that the eye position uniform variable has been replaced with a tessellation level variable. Instead of setting the TL according to the distance from this camera (as in the previous tutorial) we allow the user to configure it using the '+' and '-' keys. This makes it simpler to stand close to the model and see the effect of changing the TL.

(lighting.cs:76)

void main()
{
    // Set the control points of the output patch
    for (int i = 0 ; i < 3 ; i++) {
       oPatch.Normal[i] = Normal_CS_in[i];
       oPatch.TexCoord[i] = TexCoord_CS_in[i];
    }

    CalcPositions();

    // Calculate the tessellation levels
    gl_TessLevelOuter[0] = gTessellationLevel;
    gl_TessLevelOuter[1] = gTessellationLevel;
    gl_TessLevelOuter[2] = gTessellationLevel;
    gl_TessLevelInner[0] = gTessellationLevel;
}

This is the main function of the TCS. The three normals and texture coordinates are copied as-is from the input into the output patch. The 10 CPs that we are going to generate contain only a position value. This is done in a dedicated function called CalcPositions() which is executed next. Finally, the TLs are set according to the uniform variable.

(lighting.cs:41)

void CalcPositions(
{
    // The original vertices stay the same
    oPatch.WorldPos_B030 = WorldPos_CS_in[0];
    oPatch.WorldPos_B003 = WorldPos_CS_in[1];
    oPatch.WorldPos_B300 = WorldPos_CS_in[2];

    // Edges are names according to the opposing vertex
    vec3 EdgeB300 = oPatch.WorldPos_B003 - oPatch.WorldPos_B030;
    vec3 EdgeB030 = oPatch.WorldPos_B300 - oPatch.WorldPos_B003;
    vec3 EdgeB003 = oPatch.WorldPos_B030 - oPatch.WorldPos_B300;

    // Generate two midpoints on each edge
    oPatch.WorldPos_B021 = oPatch.WorldPos_B030 + EdgeB300 / 3.0;
    oPatch.WorldPos_B012 = oPatch.WorldPos_B030 + EdgeB300 * 2.0 / 3.0;
    oPatch.WorldPos_B102 = oPatch.WorldPos_B003 + EdgeB030 / 3.0;
    oPatch.WorldPos_B201 = oPatch.WorldPos_B003 + EdgeB030 * 2.0 / 3.0;
    oPatch.WorldPos_B210 = oPatch.WorldPos_B300 + EdgeB003 / 3.0;
    oPatch.WorldPos_B120 = oPatch.WorldPos_B300 + EdgeB003 * 2.0 / 3.0;

    // Project each midpoint on the plane defined by the nearest vertex and its normal
    oPatch.WorldPos_B021 = ProjectToPlane(oPatch.WorldPos_B021, oPatch.WorldPos_B030,
                                          oPatch.Normal[0]);
    oPatch.WorldPos_B012 = ProjectToPlane(oPatch.WorldPos_B012, oPatch.WorldPos_B003,
                                         oPatch.Normal[1]);
    oPatch.WorldPos_B102 = ProjectToPlane(oPatch.WorldPos_B102, oPatch.WorldPos_B003,
                                         oPatch.Normal[1]);
    oPatch.WorldPos_B201 = ProjectToPlane(oPatch.WorldPos_B201, oPatch.WorldPos_B300,
                                         oPatch.Normal[2]);
    oPatch.WorldPos_B210 = ProjectToPlane(oPatch.WorldPos_B210, oPatch.WorldPos_B300,
                                         oPatch.Normal[2]);
    oPatch.WorldPos_B120 = ProjectToPlane(oPatch.WorldPos_B120, oPatch.WorldPos_B030,
                                         oPatch.Normal[0]);

    // Handle the center
    vec3 Center = (oPatch.WorldPos_B003 + oPatch.WorldPos_B030 + oPatch.WorldPos_B300) / 3.0;
    oPatch.WorldPos_B111 = (oPatch.WorldPos_B021 + oPatch.WorldPos_B012 + oPatch.WorldPos_B102 +
                          oPatch.WorldPos_B201 + oPatch.WorldPos_B210 + oPatch.WorldPos_B120) / 6.0;
    oPatch.WorldPos_B111 += (oPatch.WorldPos_B111 - Center) / 2.0;
}

This function builds the Bezier triangle on top of the original triangle according to the method described in the background section. The names of the relevant members of the OutputPatch structure match the picture above to make it easier to review. The logic is very simple and follows the algorithm pretty much step by step.

(lighting.cs:32)

vec3 ProjectToPlane(vec3 Point, vec3 PlanePoint, vec3 PlaneNormal)
{
    vec3 v = Point - PlanePoint;
    float Len = dot(v, PlaneNormal);
    vec3 d = Len * PlaneNormal;
    return (Point - d);
}

This function is used by CalcPositions() to project a midpoint on the plane defined by the nearest vertex and its normal. The idea is that by doing a dot product between the normal and the vector 'v' from the vertex to the point we want to project we get the length of the projection of 'v' on the normal (the normal must be of unit length). This is exactly the distance between the point and the closest point on the plane (i.e. its projection). We multiply the length by the normal and substract it from point in order to reach the projection. The following picture illustrates this calculation:

P1 and P2 are located on different half spaces created by the plane. When we project v1 on the green normal we get the length of d1. Multiply that length by the normal to receive d1 itself. Now substract it from P1 to get its projection on the plane. When we project v2 on the green normal we get the length of d2 but it is a negative value. Multiply that by the normal to receive d2 itself (negative length means it reverses the normal). Now substract it from P2 to get its projection on the plane. The conclusion: this method works correctly no matter on which side of the plane our point is.

(lighting.es)

#version 410 core

layout(triangles, equal_spacing, ccw) in;

uniform mat4 gVP;

struct OutputPatch
{
    vec3 WorldPos_B030;
    vec3 WorldPos_B021;
    vec3 WorldPos_B012;
    vec3 WorldPos_B003;
    vec3 WorldPos_B102;
    vec3 WorldPos_B201;
    vec3 WorldPos_B300;
    vec3 WorldPos_B210;
    vec3 WorldPos_B120;
    vec3 WorldPos_B111;
    vec3 Normal[3];
    vec2 TexCoord[3];
};

in patch OutputPatch oPatch;

out vec3 WorldPos_FS_in;
out vec2 TexCoord_FS_in;
out vec3 Normal_FS_in;

vec2 interpolate2D(vec2 v0, vec2 v1, vec2 v2)
{
    return vec2(gl_TessCoord.x) * v0 + vec2(gl_TessCoord.y) * v1 + vec2(gl_TessCoord.z) * v2;
}

vec3 interpolate3D(vec3 v0, vec3 v1, vec3 v2)
{
    return vec3(gl_TessCoord.x) * v0 + vec3(gl_TessCoord.y) * v1 + vec3(gl_TessCoord.z) * v2;
}

void main()
{
    // Interpolate the attributes of the output vertex using the barycentric coordinates
    TexCoord_FS_in = interpolate2D(oPatch.TexCoord[0], oPatch.TexCoord[1], oPatch.TexCoord[2]);
    Normal_FS_in = interpolate3D(oPatch.Normal[0], oPatch.Normal[1], oPatch.Normal[2]);

    float u = gl_TessCoord.x;
    float v = gl_TessCoord.y;
    float w = gl_TessCoord.z;

    float uPow3 = pow(u, 3);
    float vPow3 = pow(v, 3);
    float wPow3 = pow(w, 3);
    float uPow2 = pow(u, 2);
    float vPow2 = pow(v, 2);
    float wPow2 = pow(w, 2);

    WorldPos_FS_in = oPatch.WorldPos_B300 * wPow3 +
                    oPatch.WorldPos_B030 * uPow3 +
                    oPatch.WorldPos_B003 * vPow3 +
                    oPatch.WorldPos_B210 * 3.0 * wPow2 * u +
                    oPatch.WorldPos_B120 * 3.0 * w * uPow2 +
                    oPatch.WorldPos_B201 * 3.0 * wPow2 * v +
                    oPatch.WorldPos_B021 * 3.0 * uPow2 * v +
                    oPatch.WorldPos_B102 * 3.0 * w * vPow2 +
                    oPatch.WorldPos_B012 * 3.0 * u * vPow2 +
                    oPatch.WorldPos_B111 * 6.0 * w * u * v;

    gl_Position = gVP * vec4(WorldPos_FS_in, 1.0);
}

This is the entire TES with changes from the previous tutorial marked in bold face. The normal and texture coordinates are interpolated the same as before. In order to calculate the world space position we plug the barycentric coordinates into the Bezier triangle equation we saw in the background section. The builtin function pow() is used in order to calculate the power of a number. We transform the world space position to clip space and continue as usual.

Next tutorial


Tutorial 32 - Vertex Array Objects

Tutorial 32:

Vertex Array Objects

Background

The Vertex Array Object (a.k.a VAO) is a special type of object that encapsulates all the data that is associated with the vertex processor. Instead of containing the actual data, it holds references to the vertex buffers, the index buffer and the layout specification of the vertex itself. The advantage is that once you set up the VAO for a mesh you can bring in the entire mesh state by simply binding the VAO. After that you can render the mesh object and you don't need to worry about all of its state. The VAO remembers it for you. If your application needs to deal with meshes whose vertex layout slightly differs from one another the VAO takes care of it also. Just make sure to set up the correct layout when you create the VAO and forget about it. From now on it "sticks" to the VAO and becomes active whenever that VAO is used.

When used correctly, VAOs can also represent an optimization opportunity for the driver of the GPU. If the VAO is set up once and used multiple times the driver can take advantage of knowing the mapping between the index buffer and the vertex buffers as well as the vertex layout in the buffers. Obviously, this depends on the specific driver that you are using and it is not guaranteed that all drivers will behave the same. At any rate, keep in mind that it is best to set up the VAO once and then reuse it over and over.

In this tutorial we are going to update the Mesh class and base it on top of a VAO. In addition, we will organize the vertex data in the buffers in a method known as SOA (Structure Of Arrays). Up till now our vertex was represented as a structure of attributes (position, etc) and the vertex buffer contained structures of vertices lined up one after the other. This is called AOS (Array Of Structure). SOA is simply a transpose of this scheme. Instead of an array of attribute structures we have one structure that contains multiple arrays. Each array contains only one attribute. In order to setup the vertex the GPU uses the same index to read one attribute from each array. This method can sometimes be more approriate for some of the 3D file formats and it is interesting to see different ways of accomplishing the same thing.

The following picture illustrates AOS and SOA:

Source walkthru

(ogldev_basic_mesh.h:50)

class Mesh
{
public:
    Mesh();

    ~Mesh();

    bool LoadMesh(const std::string& Filename);

    void Render();

private:
    bool InitFromScene(const aiScene* pScene, const std::string& Filename);
    void InitMesh(const aiMesh* paiMesh,
                std::vector& Positions,
                std::vector& Normals,
                std::vector& TexCoords,
                std::vector& Indices);

    bool InitMaterials(const aiScene* pScene, const std::string& Filename);
    void Clear();

#define INVALID_MATERIAL 0xFFFFFFFF

#define INDEX_BUFFER 0
#define POS_VB 1
#define NORMAL_VB 2
#define TEXCOORD_VB 3

    GLuint m_VAO;
    GLuint m_Buffers[4];

    struct MeshEntry {
        MeshEntry()
        {
            NumIndices = 0;
            BaseVertex = 0;
            BaseIndex = 0;
            MaterialIndex = INVALID_MATERIAL;
        }

        unsigned int BaseVertex;
        unsigned int BaseIndex;
        unsigned int NumIndices;
        unsigned int MaterialIndex;
    };

     std::vector m_Entries;
    std::vector m_Textures;
};

All the changes in this tutorial are encapsulated in the mesh class whose declaration appears above with changes marked in bold face. We have switched from an array of VB/IB elements to four buffers - index buffer, position buffer, normal buffer and texture coordinates buffer. In addition, the Mesh class has a new member called m_VAO that stores the vertex array object. Since our model can be made of multiple subcomponents each with its own texture we have a vector called m_Entries that contains the material index as well as the location of the subcomponent. NumIndices is the number of indices in the subcomponent, BaseVertex is where the subcomponent starts in the vertex buffers and BaseIndex is where the subcomponent starts inside the index buffer (because all the subcomponents are stored one after the other inside the same buffers). Before rendering a subcomponent of the mesh we need to bind its texture and then submit a draw command for subcomponent vertices. We will later see how to do this.

(ogldev_basic_mesh.cpp:60)

bool Mesh::LoadMesh(const string& Filename)
{
    // Release the previously loaded mesh (if it exists)
    Clear();

    // Create the VAO
    glGenVertexArrays(1, &m_VAO);
    glBindVertexArray(m_VAO);

    // Create the buffers for the vertices atttributes
    glGenBuffers(ARRAY_SIZE_IN_ELEMENTS(m_Buffers), m_Buffers);

    bool Ret = false;
    Assimp::Importer Importer;

    const aiScene* pScene = Importer.ReadFile(Filename.c_str(), aiProcess_Triangulate |
                                aiProcess_GenSmoothNormals | aiProcess_FlipUVs);

    if (pScene) {
        Ret = InitFromScene(pScene, Filename);
    }
    else {
        printf("Error parsing '%s': '%s'\n", Filename.c_str(), Importer.GetErrorString());
    }

    // Make sure the VAO is not changed from outside code
    glBindVertexArray(0);

    return Ret;
}

Not much has changed in the main function that loads the mesh. We generate the VAO using glGenVertexArrays() by providing the number of elements in an array of GLuint and the address of the array itself (in our case we only need one GLuint). After that we bind the VAO using glBindVertexArray(). There can only be one VAO bound at any time. From now on, any change to the state of the vertex processor will affect this VAO. The four buffers are generated using glGenBuffers() and the mesh is loaded using the Open Asset Import Library (see below). A very important function call is glBindVertexArray(0) at the end of the function. By binding zero as the VAO we guarentee that no further changes to the vertex processor will affect our VAO (OpenGL will never generate a VAO with the value of zero so this is safe).

(ogldev_basic_mesh.cpp:90)

bool Mesh::InitFromScene(const aiScene* pScene, const string& Filename)
{
    m_Entries.resize(pScene->mNumMeshes);
    m_Textures.resize(pScene->mNumMaterials);

    // Prepare vectors for vertex attributes and indices
    vector Positions;
    vector Normals;
    vector TexCoords;
    vector Indices;

    unsigned int NumVertices = 0;
    unsigned int NumIndices = 0;

    // Count the number of vertices and indices
    for (unsigned int i = 0 ; i < m_Entries.size() ; i++) {
        m_Entries[i].MaterialIndex = pScene->mMeshes[i]->mMaterialIndex;
        m_Entries[i].NumIndices = pScene->mMeshes[i]->mNumFaces * 3;
        m_Entries[i].BaseVertex = NumVertices;
        m_Entries[i].BaseIndex = NumIndices;

        NumVertices += pScene->mMeshes[i]->mNumVertices;
        NumIndices += m_Entries[i].NumIndices;
    }

    // Reserve space in the vectors for the vertex attributes and indices
    Positions.reserve(NumVertices);
    Normals.reserve(NumVertices);
    TexCoords.reserve(NumVertices);
    Indices.reserve(NumIndices);

    // Initialize the meshes in the scene one by one
    for (unsigned int i = 0 ; i < m_Entries.size() ; i++) {
        const aiMesh* paiMesh = pScene->mMeshes[i];
        InitMesh(paiMesh, Positions, Normals, TexCoords, Indices);
    }

    if (!InitMaterials(pScene, Filename)) {
        return false;
    }

    // Generate and populate the buffers with vertex attributes and the indices
    glBindBuffer(GL_ARRAY_BUFFER, m_Buffers[POS_VB]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(Positions[0]) * Positions.size(), &Positions[0],
                    GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);

    glBindBuffer(GL_ARRAY_BUFFER, m_Buffers[TEXCOORD_VB]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(TexCoords[0]) * TexCoords.size(), &TexCoords[0],
                    GL_STATIC_DRAW);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0);

    glBindBuffer(GL_ARRAY_BUFFER, m_Buffers[NORMAL_VB]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(Normals[0]) * Normals.size(), &Normals[0],
                    GL_STATIC_DRAW);
    glEnableVertexAttribArray(2);
    glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, 0);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_Buffers[INDEX_BUFFER]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices[0]) * Indices.size(), &Indices[0],
                    GL_STATIC_DRAW);

    return true;
}

This is the next level of details in terms of loading the mesh. The Open Asset Import Library (Assimp) has loaded the mesh data into an aiScene structure and we have a pointer to it. We now need to load it into GL buffers and attach them to the VAO. We do this by using STL vectors. We have a vector per GL buffer. We count the number of vertices and indices in the aiScene structure and for each aiMesh struct we store its material index, index count, base vertex and base index in the m_Entries array. We also reserve place in the vectors accordingly. We then go over each aiMesh structure inside the aiScene and initialize it. The vectors are passed by reference to InitMesh() which allows it to keep on populating them as we go. Materials are initialized same as before.

The last part of the function is where things become interesting. The position, normal and texture coordinates buffers are bound one by one to the GL_ARRAY_BUFFER target. Any further operation on that target affects the currently bound buffer and such changes remain attached to that buffer when a new buffer is bound to the same target. For each of the three buffers we:

  1. Populate with data using glBufferData().
  2. Enable the corresponding vertex attribute using glEnableVertexAttribArray().
  3. Configure the vertex attribute (number of components, component type, etc) using glVertexAttribPointer().

The index buffer is initialized by binding it to the GL_ELEMENT_ARRAY_BUFFER target. We only need to populate it with the indices and that's it. The buffers are now initialized and everything we did is encapsulated in the VAO.

(ogldev_basic_mesh.cpp:152)

void Mesh::InitMesh(const aiMesh* paiMesh,
                     vector& Positions,
                     vector& Normals,
                     vector& TexCoords,
                     vector& Indices)
{
    const aiVector3D Zero3D(0.0f, 0.0f, 0.0f);

    // Populate the vertex attribute vectors
    for (unsigned int i = 0 ; i < paiMesh->mNumVertices ; i++) {
        const aiVector3D* pPos = &(paiMesh->mVertices[i]);
        const aiVector3D* pNormal = &(paiMesh->mNormals[i]);
        const aiVector3D* pTexCoord = paiMesh->HasTextureCoords(0) ?
                                      &(paiMesh->mTextureCoords[0][i]) : &Zero3D;

        Positions.push_back(Vector3f(pPos->x, pPos->y, pPos->z));
        Normals.push_back(Vector3f(pNormal->x, pNormal->y, pNormal->z));
        TexCoords.push_back(Vector2f(pTexCoord->x, pTexCoord->y));
    }

    // Populate the index buffer
    for (unsigned int i = 0 ; i < paiMesh->mNumFaces ; i++) {
        const aiFace& Face = paiMesh->mFaces[i];
        assert(Face.mNumIndices == 3);
        Indices.push_back(Face.mIndices[0]);
        Indices.push_back(Face.mIndices[1]);
        Indices.push_back(Face.mIndices[2]);
    }
}

This function is responsible for loading each aiMesh structure that is contained in the aiScene. Note how the vectors are passed by reference and accessed using the push_back() function of the STL vector class.

(ogldev_basic_mesh.cpp:236)

void Mesh::Render()
{
    glBindVertexArray(m_VAO);

    for (unsigned int i = 0 ; i < m_Entries.size() ; i++) {
        const unsigned int MaterialIndex = m_Entries[i].MaterialIndex;

        assert(MaterialIndex < m_Textures.size());

        if (m_Textures[MaterialIndex]) {
            m_Textures[MaterialIndex]->Bind(GL_TEXTURE0);
        }

        glDrawElementsBaseVertex(GL_TRIANGLES,
                                  m_Entries[i].NumIndices,
                                  GL_UNSIGNED_INT,
                                  (void*)(sizeof(unsigned int) * m_Entries[i].BaseIndex),
                                  m_Entries[i].BaseVertex);
    }

    // Make sure the VAO is not changed from the outside
    glBindVertexArray(0);
}

Finally, we've reached the render function. We start by binding our VAO and...this is all we need to do in terms of setting up the state for the vertex processor! whatever state is already there has now been replaced by the state that we have set up when we initialized the VAO. Now we need to draw the subcomponents of the mesh and bind the proper texture before each one. For that we use the information in the m_Entries array and a new draw function called glDrawElementsBaseVertex(). This function takes the topology, the number of indices and their type. The fourth parameter tells it where to start in the index buffer. The problem is that the indices that Assimp supplied for each aiMesh structure starts at zero and we have accumulated them into the same buffer. So now we need to tell the draw function the offset in bytes in the buffer where the indices of the subcomponent start. We do this by multiplying the base index of the current entry by the size of an index. Since the vertex attributes have also been accumulated into their own buffers we do the same with the fifth parameter - the base vertex. Note that we are providing it as an index rather than as a byte offset because there can be multiple vertex buffers with different types of attributes (and therefore differen strides). OpenGL will need to multiply the base vertex by the stride of each buffer in order to get the offset of that buffer. Nothing we need to worry about.

Before leaving we reset the current VAO back to zero and the reason is the same as when we initially created the VAO - we don't want outside code to bind a VB (for example) and change our VAO unintentinally.

(ogldev_basic_mesh.cpp:50)

glDeleteVertexArrays(1, &m_VAO);

The above function deletes the VAO. It does not delete the buffers that are bound to it (they may be bound to multiple VAOs at the same time).

Next tutorial


Tutorial 33 - Instanced Rendering

Tutorial 33:

Instanced Rendering

Background

Imagine that you want to render a scene where a huge army is moving across the land. You have a model of a soldier and you want to render a few thousand soldiers. One way to do it is to dispatch as many draw calls as there are soldiers while changing relevant uniform variables in between. For example, each soldier is located in a different spot, each soldier can be up to 10% taller or shorter than the average, etc. Therefore, we must update the WVP matrix between draw calls as well as any other variable which is pertinent to the specific soldier. This scheme can have a large overhead. There has to be a better way.

Enter instanced rendering. An Instance is a single occurence of the model that you want to render (in our case, a soldier). Instanced rendering means that we can render multiple instances in a single draw call and provide each instance with some unique attributes. We are going to cover two methods for doing that.

In the first method instance specific attributes (e.g. WVP matrix) go into a seperate vertex buffer. Usually the vertex processor makes one step inside the VBs for each vertex. In the case of VBs with instance data that step occurs only after all the "regular" vertices have already been drawn. VBs with instance data simply provide attributes that are common to all the vertices. Consider the following example:

What we have here is a model that contains 100 vertices. Each vertex has a position, normal and texture coordinates. Each of these three data items has its own vertex buffer. In addition, we have a fourth vertex buffer that contains three WVP matrices. The plan is to draw the 100 vertices while applying the first WVP matrix on each of their positions, then draw them again using the second WVP matrix and then with the third matrix. We will do this in one draw call instead of three. The WVP matrix will go into an input variable of the vertex but since the fourth VB is marked as having instance data the matrix will not change until all the vertices have been drawn.

The second method uses a built-in shader variable called gl_InstanceID which, not suprisingly, tells us the current instance index. We can use this index to locate instance specific data in uniform variable arrays.

Source walkthru

(mesh.h:50)

class Mesh
{
    public:
...
        void Render(unsigned int NumInstances, const Matrix4f* WVPMats, const Matrix4f* WorldMats);
...
    private:
...
#define INDEX_BUFFER 0
#define POS_VB 1
#define NORMAL_VB 2
#define TEXCOORD_VB 3
#define WVP_MAT_VB 4
#define WORLD_MAT_VB 5

        GLuint m_VAO;
        GLuint m_Buffers[6];
...

These are the changes to the Mesh class. The Render() function now takes two arrays that contain the WVP and world matrices for all the instances and NumInstances is the number of matrices in each array. We also added two VBs to store them.

(mesh.cpp:91)

bool Mesh::InitFromScene(const aiScene* pScene, const string& Filename)
{
    ...
    // Generate and populate the buffers with vertex attributes and the indices
    glBindBuffer(GL_ARRAY_BUFFER, m_Buffers[POS_VB]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(Positions[0]) * Positions.size(), &Positions[0],
                GL_STATIC_DRAW);
    glEnableVertexAttribArray(POSITION_LOCATION);
    glVertexAttribPointer(POSITION_LOCATION, 3, GL_FLOAT, GL_FALSE, 0, 0);

    glBindBuffer(GL_ARRAY_BUFFER, m_Buffers[TEXCOORD_VB]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(TexCoords[0]) * TexCoords.size(), &TexCoords[0],
                GL_STATIC_DRAW);
    glEnableVertexAttribArray(TEX_COORD_LOCATION);
    glVertexAttribPointer(TEX_COORD_LOCATION, 2, GL_FLOAT, GL_FALSE, 0, 0);

    glBindBuffer(GL_ARRAY_BUFFER, m_Buffers[NORMAL_VB]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(Normals[0]) * Normals.size(), &Normals[0],
                GL_STATIC_DRAW);
    glEnableVertexAttribArray(NORMAL_LOCATION);
    glVertexAttribPointer(NORMAL_LOCATION, 3, GL_FLOAT, GL_FALSE, 0, 0);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_Buffers[INDEX_BUFFER]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices[0]) * Indices.size(), &Indices[0],
                GL_STATIC_DRAW);

    glBindBuffer(GL_ARRAY_BUFFER, m_Buffers[WVP_MAT_VB]);

    for (unsigned int i = 0; i < 4 ; i++) {
        glEnableVertexAttribArray(WVP_LOCATION + i);
        glVertexAttribPointer(WVP_LOCATION + i, 4, GL_FLOAT, GL_FALSE, sizeof(Matrix4f),
                                (const GLvoid*)(sizeof(GLfloat) * i * 4));
        glVertexAttribDivisor(WVP_LOCATION + i, 1);
    }

    glBindBuffer(GL_ARRAY_BUFFER, m_Buffers[WORLD_MAT_VB]);

    for (unsigned int i = 0; i < 4 ; i++) {
        glEnableVertexAttribArray(WORLD_LOCATION + i);
        glVertexAttribPointer(WORLD_LOCATION + i, 4, GL_FLOAT, GL_FALSE, sizeof(Matrix4f),
                                (const GLvoid*)(sizeof(GLfloat) * i * 4));
        glVertexAttribDivisor(WORLD_LOCATION + i, 1);
    }

    return GLCheckError();
}

The above code creates and populates the various VBs of the mesh with the vertex data. The bold part has been added and shows how to make VBs contain instance data. We start by binding the buffer of the WVP matrices as usual. Since a WVP matrix is a 4x4 matrix and we plan to provide it as an input variable to the VS we cannot enable just one vertex attribute for it because a vertex attribute can contain no more than 4 floating points or integers. Therefore, we have a loop which enables and configures 4 consecutive vertex attributes. Each attribute will contain one vector from the matrix. Next we configure the attributes. Each of the four attributes is made up of four floating points and the distance between an attribute in one matrix and the next is exactly the size of a 4x4 matrix. In addition, we don't want OpenGL to normalize the input for us. This explains parameters 2-5 in the function glVertexAttribPointer(). The last parameter is simply the offset of the attribute inside one instance data. The first vector goes to offset 0, the second to 16, etc.

The function glVertexAttribDivisor() is what makes this an instance data rather than vertex data. It takes two parameters - the first one is the vertex array attribute and the second tells OpenGL the rate by which the attribute advances during instanced rendering. It basically means the number of times the entire set of vertices is rendered before the attribute is updated from the buffer. By default, the divisor is zero. This causes regular vertex attributes to be updated from vertex to vertex. If the divisor is 10 it means that the first 10 instances will use the first piece of data from the buffer, the next 10 instances will use the second, etc. We want to have a dedicated WVP matrix for each instance so we use a divisor of 1.

We repeat these steps for all four vertex array attributes of the matrix. We then do the same with the world matrix. Note that unlike the other vertex attributes such as the position and the normal we don't upload any data into the buffers. The reason is that the WVP and world matrices are dynamic and will be updated every frame. So we just set things up for later and leave the buffers uninitialized for now.

(mesh.cpp:253)

void Mesh::Render(unsigned int NumInstances, const Matrix4f* WVPMats, const Matrix4f* WorldMats)
{
    glBindBuffer(GL_ARRAY_BUFFER, m_Buffers[WVP_MAT_VB]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(Matrix4f) * NumInstances, WVPMats, GL_DYNAMIC_DRAW);

    glBindBuffer(GL_ARRAY_BUFFER, m_Buffers[WORLD_MAT_VB]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(Matrix4f) * NumInstances, WorldMats, GL_DYNAMIC_DRAW);
    glBindVertexArray(m_VAO);

    for (unsigned int i = 0 ; i < m_Entries.size() ; i++) {
        const unsigned int MaterialIndex = m_Entries[i].MaterialIndex;

        assert(MaterialIndex < m_Textures.size());

        if (m_Textures[MaterialIndex]) {
            m_Textures[MaterialIndex]->Bind(GL_TEXTURE0);
        }

        glDrawElementsInstancedBaseVertex(GL_TRIANGLES,
                                         m_Entries[i].NumIndices,
                                         GL_UNSIGNED_INT,
                                         (void*)(sizeof(unsigned int) * m_Entries[i].BaseIndex),
                                         NumInstances,
                                         m_Entries[i].BaseVertex);
    }

    // Make sure the VAO is not changed from the outside
    glBindVertexArray(0);
}

This is the updated Render() function of the Mesh class. It now takes two arrays of matrices - the WVP matrix and the world transformation matrix (NumInstances is the size of both arrays). Before we bind our VAO (check out the previous tutorial if you don't know what VAOs are) we bind and upload the matrices into their corresponding vertex buffers. We call glDrawElementsInstancedBaseVertex instead of glDrawElementsBaseVertex. The only change in this function is that it takes the number of instances as the fifth parameter. This means that the same indices (according to the other parameters) will be drawn again and again - NumInstances times altogether. The OpenGL will fetch new data for each vertex from the VBs whose divisor is 0 (the old ones). It will fetch new data from the VBs whose divisor is 1 only after a full instance has been rendered. The general algorithm of this draw call is as follows:

  • for (i = 0 ; i < NumInstances ; i++)
    • if (i mod divisor == 0)
      • fetch attribute i/divisor from VBs with instance data
    • for (j = 0 ; j < NumVertices ; j++)
      • fetch attribute j from VBs with vertex data

(lightning_technique.cpp:25)

#version 330

layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
layout (location = 2) in vec3 Normal;
layout (location = 3) in mat4 WVP;
layout (location = 7) in mat4 World;

out vec2 TexCoord0;
out vec3 Normal0;
out vec3 WorldPos0;
flat out int InstanceID;

void main()
{
    gl_Position = WVP * vec4(Position, 1.0);
    TexCoord0 = TexCoord;
    Normal0 = World * vec4(Normal, 0.0)).xyz;
    WorldPos0 = World * vec4(Position, 1.0)).xyz;
    InstanceID = gl_InstanceID;
};

This is the updated VS. Instead of getting the WVP and world matrics as uniform variables they are now coming in as regular vertex attributes. The VS doesn't care that their values will only be updated once per instance and not per vertex. As discussed above, the WVP matrix takes up locations 3-6 and the world matrix takes up locations 7-10.

The last line of the VS is where we see the second way of doing instanced rendering (the first being passing instance data as vertex attributes). 'gl_InstanceID' is a built-in variable which is available only in the VS. Since we plan to use it in the FS we have to access it here and pass it along in a regular output variable. The type of gl_InstanceID is an integer so we use an output variable of the same type. Since integers cannot be interpolated by the rasterizer we have to mark the output variable as 'flat' (forgetting to do that will trigger a compiler error).

flat in int InstanceID;
...
uniform vec4 gColor[4];

...

void main()
{
    vec3 Normal = normalize(Normal0);
    vec4 TotalLight = CalcDirectionalLight(Normal);

    for (int i = 0 ; i < gNumPointLights ; i++) {
        TotalLight += CalcPointLight(gPointLights[i], Normal);
    }

    for (int i = 0 ; i < gNumSpotLights ; i++) {
        TotalLight += CalcSpotLight(gSpotLights[i], Normal);
    }

    FragColor = texture(gColorMap, TexCoord0.xy) * TotalLight * gColor[InstanceID % 4];
};

To show off the use of gl_InstanceID I've added a uniform array of 4 floating point vectors to the FS. The FS gets the instance ID from the VS and uses the modulo operation to index into the array. The color that was calculated by the lighting equation is multiplied by one of the colors from the array. By placing different colors in the array we are able to get some interesting coloring of the instances.

(tutorial33.cpp:141)

Pipeline p;
p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());
p.SetPerspectiveProj(m_persProjInfo);
p.Rotate(0.0f, 90.0f, 0.0f);
p.Scale(0.005f, 0.005f, 0.005f);

Matrix4f WVPMatrics[NUM_INSTANCES];
Matrix4f WorldMatrices[NUM_INSTANCES];

for (unsigned int i = 0 ; i < NUM_INSTANCES ; i++) {
    Vector3f Pos(m_positions[i]);
    Pos.y += sinf(m_scale) * m_velocity[i];
    p.WorldPos(Pos);
    WVPMatrics[i] = p.GetWVPTrans().Transpose();
    WorldMatrices[i] = p.GetWorldTrans().Transpose();
}

m_pMesh->Render(NUM_INSTANCES, WVPMatrics, WorldMatrices);

The above piece of code is taken from the main render loop and shows how to call the updated Mesh::Render() function. We create a Pipeline object and set all the common stuff into it. The only thing that changes from instance to instance is the world position so we leave it for the loop to handle. We prepare two arrays for the WVP and world matrices. Now we loop over all the instances and fetch their starting position from the m_positions array (which was initialized with random values during startup). We calculate the current position and set it into the Pipeline object. We can now get the WVP and world matrix from the Pipeline object and place them in their proper places in the arrays. But before we do that we have to do something really important which can cause quite a lot of headache for people at first. We have to transpose the matrices.

The thing is that our Matrix class stores its 16 floating point values as a single line in memory. We start at the top left corner of the standard matrix and work our way to the right. When we reach the end we drop to the next row. So basically we travel row by row until we reach the bottom right. You can say that we have four 4-row-vectors one after the other. Each of these row vectors goes into its own vertex input attribute (e.g. the top row vector of the WVP matrix goes to vertex attribute 3, the second goes to attribute 4, the third to attribute 5 and the bottom row vector goes to attribute 6 - this is according to how we setup our VS). On the shader side we declare the WVP and world matrices as having the 'mat4' type. The way that mat4 types variable are initialized by vertex attributes is that each vertex attribute goes into a column-vector in the matrix. For example, in the case of our WVP matrix OpenGL implicitly calls the mat4 constructor like that: mat4 WVP(attribute 3, attribute 4, attribute 5, attribute 6). Attribute 3 becomes the first column from the left, attribute 4 is the second column, etc. This effectively transposes our matrix because each row becomes a column. In order to counter this behavior and keep our matrix correct we transpose it before we load it into the array (code above in bold face).

Notes

If you compile and run this tutorial demo you will notice the FPS (frames per second) counter at the bottom left corner of the window. OpenGL doesn't have a standard library to do font rendering so different people use different methods. I recently discovered freetype-gl by Nicolas Rougier and I really like it. It is available for free under the new BSD license. I've modified the sources a bit to make it simpler to use and included them as part of the demo so you don't need to install anything. If you're interested in the way it is used in the demo just follow 'FontRenderer' in tutorial33.cpp.

Next tutorial


Tutorial 34 - GLFX - An OpenGL Effect Library

Tutorial 34:

GLFX - An OpenGL Effect Library

Background

This tutorial is going to be a bit different than the previous ones. Instead of exploring an OpenGL feature or 3D technique we are going to take a look at GLFX, an effect library for OpenGL. An effect is a text file that can potentially contain multiple shaders and functions and makes it easy to combine them together into programs. This overcomes the limitation of the glShaderSource() function that requires you to specify the text of a single shader stage. This forces you to use a different text file for each shader (or different buffer as we did in previous tutorials). Placing all shaders in the same file makes it simpler to share structure definitions between them. In addition, GLFX provides an easy to use API to translate effect files into GLSL programs which hides some of the complexity of the underlying OpenGL functions.

The idea of effect files is not new. Indeed, Microsoft has had this for years in the DirectX world. I'm sure that gaming studios have their own tools developed inhouse but it's a shame that this has not yet been standardized in OpenGL. The effect library that we will use is an open source project that was created by Max Aizenshtein. You can find the project homepage here.

To install GLFX simply check out the sources and build them by running the following from the command line:

  1. svn checkout http://glfx.googlecode.com/svn/trunk/ libglfx
  2. cd libglfx
  3. cmake .
  4. make
  5. make install (as root)

Note: GLFX is dependant on GLEW. This is no problem if you are using the tutorials as a framework or already using GLEW in your application. If not, you can turn to tutorial 2 for information on how to initialize GLEW.

Source walkthru

Integrating GLFX into the project

Add the following to get access to GLFX api:

#include &ltglfx.h&gt

Generate an effect handle:

int effect = glfxGenEffect();

Parse the effect file (we will take a look at its content momentarily):

if (!glfxParseEffectFromFile(effect, "effect.glsl")) {
#ifdef __cplusplus // C++ error handling
    std::string log = glfxGetEffectLog(effect);
    std::cout << "Error parsing effect: " << log << std::endl;
#else // C error handling
    char log[10000];
    glfxGetEffectLog(effect, log, sizeof(log));
    printf("Error parsing effect: %s:\n", log);
#endif
    return;
}

Compile a program (combination of VS, FS, etc) defined in the effect file using the following:

int shaderProg = glfxCompileProgram(effect, "ProgramName");

if (shaderProg < 0) {
    // same error handling as above
}

The program can now be used by OpenGL as usual:

glUseProgram(shaderProg);

After the effect is no longer needed release its resources using:

glfxDeleteEffect(effect); Using GLFX

Now that we have the basic infrastructure in place let's dive into the effect files. The nice thing about GLFX is that you can continue writing GLSL shaders in pretty much the same way that you are used to. There are a few minor changes and additions and we are going to focus on them.

Place a 'program' section to combine shader stages into a complete GLSL program

program Lighting
{
    vs(410)=VSmain();
    fs(410)=FSmain();
};

In the example above the effect file contains the definition of the functions VSmain() and FSmain() somewhere else. The 'program' section defines an OpenGL program called 'Lighting'. Calling glfxCompileProgram(effect, "Lighting") will cause a compilation and linkage of VSmain() and FSmain() into a single program. Both shaders will be compiled in version 4.10 of GLSL (same as declaring '#version 410' in standard GLSL).

Use 'shader' instead of 'void' to declare main shader functions

The main entry points to shader stages must be declared as 'shader' instead of 'void'. Here's an example:

void calculate_something()
{
    ...
}

shader VSmain()
{
    calculate_something();
}

Include multiple shaders and program in a single effect file

You can place multiple occurrences of the 'program' section in a single effect file. Simply call glfxCompileProgram() for each program that you want to use.

Use structures to pass vertex attributes between shader stages

Instead of defining the in/out variables in the global section of the shader we can use GLSL structures and share them across multiple shader stages. Here's an example:

struct VSoutput
{
    vec2 TexCoord;
    vec3 Normal;
};

shader VSmain(in vec3 Pos, in vec2 TexCoord, in vec3 Normal, out VSOutput VSout)
{
    // do some transformations and update 'VSout'
    VSout.TexCoord = TexCoord;
    VSout.Normal = Normal;
}

shader FSmain(in VSOutput FSin, out vec4 FragColor)
{
    // 'FSin' matches 'VSout' from the VS. Use it
    // to do lighting calculations and write the final output to 'FragColor'
}

Unfortunately, using a structure will only work between shader stages. Input variables to the VS must be handled as separate attributes as we see in the above example. Well, I have an NVIDIA card and input structures to the VS work for me but this is not explicitly allowed by the GLSL spec and many readers have informed me that it doesn't work for them. If it works - great. If not, simply go with the above code.

Use include files to share common functionality between effect files

The keyword 'include' can be used to include one effect file in another:

#include "another_effect.glsl"

The caveat with include files is that they are not parsed by GLFX. They are simply inserted as-is into the including file at the location of the 'include' keyword. This means that you can only place pure GLSL code in them and not GLFX-only keywords such as program/etc. Tip: since part of GLSL syntax is the same as C/C++ (e.g. #define) you can even share definitions between the effect file and your application code.

Use structure suffix to define attribute locations

In the previous tutorials we have used the 'layout(location = ...)' keyword to define the location of an input attribute of the VS. By placing a colon followed by a number after an input VS parameter we can achieve the same goal. Here's an example:

struct VSInput2
{
    vec3 Normal;
    vec3 Tangent;
};

shader VSmain(in vec3 Pos : 5, in vec2 TexCoord : 6, in float colorScale : 10)

The VS above gets the position in attribute 5, the texture coordinate in 6 and the color scale in 10. The idea is very simple - the number after the colon determines the location. If there is no location suffix the attributes simply start at zero.

Use 'interface' instead of 'struct' to place qualifiers on members

GLSL provides qualifiers such as 'flat' and 'noperspective' that can be placed before attributes that are sent from the VS to the FS. These qualifiers cannot be used on structure members. The solution that GLFX provides is a new keyword called 'interface' that enables what 'struct' does not. An 'interface' can only be passed between shader stages. If you need to pass it as a whole to another function you will need to copy the contents to a struct. For example:

interface foo
{
    flat int a;
    noperspective float b;
};

struct bar
{
    int a;
    float b;
}

shader VSmain(out foo f)
{
    // ...
}

void Calc(bar c)
{
    // ...
}

shader FSmain(in foo f)
{
    struct bar c;
    c.a = f.a;
    c.b = f.b;

    Calc(c);
}

Note: 'interface' is a keyword reserved for future use (according to OpenGL 4.2). Its usage in the future in GLFX will be based on changes to the official OpenGL spec.

Tip: use 'glfxc' to verify effect files

'glfxc' is a utility which is part of GLFX. It parses effect files, compiles them using the local OpenGL installation and reports any error it finds. Run it as follows:

glfxc <effect file name> <program name> The Demo

The code of this tutorial has been modified to work with GLFX. Since the changes are very simple I won't go over them here. You should take a look at the source, in particular the classes Technique and LightingTechnique. In addition, the shaders that used to be part of 'lighting_technique.cpp' have been removed and there is an effect file called 'lighting.glsl' in the 'shaders' subdirectory. This file contains the same shaders that you are already familiar with. They have been modified slightly to fit the rules above.

Next tutorial


Tutorial 35 - Deferred Shading - Part 1

Tutorial 35:

Deferred Shading - Part 1

Background

The way we've been doing lighting since tutorial 17 is known as Forward Rendering (or Shading). This is a straightforward approach where we do a set of transformations on the vertices of every object in the VS (mostly translations of the normal and position to clip space) followed by a lighting calculation per pixel in the FS. Since each pixel of every object gets only a single FS invocation we have to provide the FS with information on all light sources and take all of them into account when calculating the light effect per pixel. This is a simple approach but it has its downsides. If the scene is highly complex (as is the case in most modern games) with many objects and a large depth complexity (same screen pixel covered by several objects) we get a lot of wasted GPU cycles. For example, if the depth complexity is 4 it means that the lighting calculations are executed on 3 pixels for nothing because only the topmost pixel counts. We can try to counter that by sorting the objects front to back but that doesn't always work well with complex objects.

Another problem with forward rendering is when there are many light sources. In that case the light sources tend to be rather small with a limited area of effect (else it will overwhelm the scene). But our FS calculates the effect of every light source, even if it is far away from the pixel. You can try to calculate the distance from the pixel to the light source but that just adds more overhead and branches into the FS. Forward rendering simply doesn't scale well with many light sources. Just image the amount of computation the FS needs to do when there are hundreds of light sources...

Deferred shading is a popular technique in many games which targets the specific problem above. The key point behind deferred shading is the decoupling of the geometry calculations (position and normal transformations) and the lighting calculations. Instead of taking each object "all the way", from the vertex buffer into its final resting place in the framebuffer we seperate the processing into two major passes. In the first pass we run the usual VS but instead of sending the processed attributes into the FS for lighting calculations we forward them into what is known as the G Buffer. This is a logical grouping of several 2D textures and we have a texture per vertex attribute. We seperate the attributes and write them into the different textures all at once using a capability of OpenGL called Multiple Render Targets (MRT). Since we are writing the attributes in the FS the values that end up in the G buffer are the result of the interpolation performed by the rasterizer on the vertex attributes. This stage is called the Geometry Pass. Every object is processed in this pass. Because of the depth test, when the geometry pass is complete the textures in the G buffer are populated by the interpolated attributes of the closest pixels to the camera. This means that all the "irrelevant" pixels that have failed the depth test have been dropped and what is left in the G buffer are only the pixels for which lighting must be calculated. Here's a typical example of a G buffer of a single frame:

In the second pass (known as the Lighting Pass) we go over the G buffer pixel by pixel, sample all the pixel attributes from the different textures and do the lighting calculations in pretty much the same way that we are used to. Since all the pixels except the closest ones were already dropped when we created the G buffer we do the lighting calculations only once per pixel.

How do we traverse the G buffer pixel by pixel? The simplest method is to render a screen space quad. But there is a better way. We said earlier that since the light sources are weak with a limited area of influence we expect many pixels to be irrelevant to them. When the influence of a light source on a pixel is small enough it is better to simply ignore it for peformance reasons. In forward rendering there was no efficient way to do that but in deferred shading we can calculate the dimentions of a sphere around the light source (for points lights; for spot lights we use a cone). That sphere represents the area of influence of the light and outside of it we want to ignore this light source. We can use a very rough model of a sphere with a small number of polygons and simply render it with the light source at the center. The VS will do nothing except translate the position into clip space. The FS will be executed only on the relevant pixels and we will do our lighting calculations there. Some people go even further by calculating a minimal bounding quad that covers that sphere from the point of view of the light. Rendering this quad is even lighter because there's only two triangles. These methods are useful to limit the number of pixels for which the FS is executed to only the ones we are really interested in.

We will cover deferred shading in three steps (and three tutorials):

  1. In this tutorial we will populate the G buffer using MRT. We will dump the contents of the G buffer to the screen to make sure we got it correctly.
  2. In the next tutorial we will add the light pass and get lighting working in true deferred shading fashion.
  3. Finally, we will learn how to use the stencil buffer to prevent small points lights from lighting objects that are further off (a problem which will become evident by the end of the second tutorial).

Source walkthru

(gbuffer.h:28)

class GBuffer
{
public:

    enum GBUFFER_TEXTURE_TYPE {
        GBUFFER_TEXTURE_TYPE_POSITION,
        GBUFFER_TEXTURE_TYPE_DIFFUSE,
        GBUFFER_TEXTURE_TYPE_NORMAL,
        GBUFFER_TEXTURE_TYPE_TEXCOORD,
        GBUFFER_NUM_TEXTURES
    };

    GBuffer();

    ~GBuffer();

    bool Init(unsigned int WindowWidth, unsigned int WindowHeight);

    void BindForWriting();

    void BindForReading();

private:

    GLuint m_fbo;
    GLuint m_textures[GBUFFER_NUM_TEXTURES];
    GLuint m_depthTexture;
};

The GBuffer class contains all the textures that the G buffer in deferred shading needs. We have textures for the vertex attributes as well as a texture to serve as our depth buffer. We need this depth buffer because we are going to wrap all the textures in an FBO so the default depth buffer will not be available. FBOs have already been covered in tutorial 23 so we will skip that here.

The GBuffer class also has two methods that will be repeatedly called at runtime - BindForWriting() binds the textures as a target during the geometry pass and BindForReading() binds the FBO as input so its contents can be dumped to the screen.

(gbuffer.cpp:48)

bool GBuffer::Init(unsigned int WindowWidth, unsigned int WindowHeight)
{
    // Create the FBO
    glGenFramebuffers(1, &m_fbo);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo);

    // Create the gbuffer textures
    glGenTextures(ARRAY_SIZE_IN_ELEMENTS(m_textures), m_textures);
    glGenTextures(1, &m_depthTexture);

    for (unsigned int i = 0 ; i < ARRAY_SIZE_IN_ELEMENTS(m_textures) ; i++) {
       glBindTexture(GL_TEXTURE_2D, m_textures[i]);
       glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, WindowWidth, WindowHeight, 0, GL_RGB, GL_FLOAT, NULL);
       glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, m_textures[i], 0);
    }

    // depth
    glBindTexture(GL_TEXTURE_2D, m_depthTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, WindowWidth, WindowHeight, 0, GL_DEPTH_COMPONENT, GL_FLOAT,
                  NULL);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_depthTexture, 0);

    GLenum DrawBuffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3 };
    glDrawBuffers(ARRAY_SIZE_IN_ELEMENTS(DrawBuffers), DrawBuffers);

    GLenum Status = glCheckFramebufferStatus(GL_FRAMEBUFFER);

    if (Status != GL_FRAMEBUFFER_COMPLETE) {
        printf("FB error, status: 0x%x\n", Status);
        return false;
    }

    // restore default FBO
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);

    return true;
}

This is how we initialize the G buffer. We start by creating the FBO and textures for the vertex attributes and the depth buffer. The vertex attributes textures are then initialized in a loop that does the following:

  • Creates the storage area of the texture (without initializing it).
  • Attaches the texture to the FBO as a target.

Initialization of the depth texture is done explicitly because it requires a different format and is attached to the FBO at a different spot.

In order to do MRT we need to enable writing to all four textures. We do that by supplying an array of attachment locations to the glDrawBuffers() function. This array allows for some level of flexibility because if we put GL_COLOR_ATTACHMENT6 as its first index then when the FS writes to the first output variable it will go into the texture that is attached to GL_COLOR_ATTACHMENT6. We are not interested in this complexity in this tutorial so we simply line the attachments one after the other.

Finally, we check the FBO status to make sure everything was done correctly and restore the default FBO (so that further changes will not affect our G buffer). The G buffer is ready for use.

(tutorial35.cpp:105)

virtual void RenderSceneCB()
{
    CalcFPS();

    m_scale += 0.05f;

    m_pGameCamera->OnRender();

    DSGeometryPass();
    DSLightPass();

    RenderFPS();

    glutSwapBuffers();
}

Let's now review the implementation top down. The function above is the main render function and it doesn't have a lot to do. It handles a few "global" stuff such as frame rate calculation and display, camera update, etc. Its main job is to execute the geometry pass followed by the light pass. As I mentioned earlier, in this tutorial we are just generating the G buffer so our "light pass" doesn't really do deferred shading. It just dumps the G buffer to the screen.

(tutorial35.cpp:122)

void DSGeometryPass()
{
    m_DSGeomPassTech.Enable();

    m_gbuffer.BindForWriting();

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    Pipeline p;
    p.Scale(0.1f, 0.1f, 0.1f);
    p.Rotate(0.0f, m_scale, 0.0f);
    p.WorldPos(-0.8f, -1.0f, 12.0f);
    p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());
    p.SetPerspectiveProj(m_persProjInfo);
    m_DSGeomPassTech.SetWVP(p.GetWVPTrans());
    m_DSGeomPassTech.SetWorldMatrix(p.GetWorldTrans());
    m_mesh.Render();

}

We start the geometry pass by enabling the proper technique and setting the GBuffer object for writing. After that we clear the G buffer (glClear() works on the current FBO which is our G buffer). Now that everything is ready we setup the transformations and render the mesh. In a real game we would probably render many meshes here one after the other. When we are done the G buffer will contain the attributes of the closest pixels which will enable us to do the light pass.

(tutorial35.cpp:141)

void DSLightPass()
{
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    m_gbuffer.BindForReading();

    GLsizei HalfWidth = (GLsizei)(WINDOW_WIDTH / 2.0f);
    GLsizei HalfHeight = (GLsizei)(WINDOW_HEIGHT / 2.0f);

    m_gbuffer.SetReadBuffer(GBuffer::GBUFFER_TEXTURE_TYPE_POSITION);
    glBlitFramebuffer(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT,
                    0, 0, HalfWidth, HalfHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);

    m_gbuffer.SetReadBuffer(GBuffer::GBUFFER_TEXTURE_TYPE_DIFFUSE);
    glBlitFramebuffer(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT,
                    0, HalfHeight, HalfWidth, WINDOW_HEIGHT, GL_COLOR_BUFFER_BIT, GL_LINEAR);

    m_gbuffer.SetReadBuffer(GBuffer::GBUFFER_TEXTURE_TYPE_NORMAL);
    glBlitFramebuffer(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT,
                    HalfWidth, HalfHeight, WINDOW_WIDTH, WINDOW_HEIGHT, GL_COLOR_BUFFER_BIT, GL_LINEAR);

    m_gbuffer.SetReadBuffer(GBuffer::GBUFFER_TEXTURE_TYPE_TEXCOORD);
    glBlitFramebuffer(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT,
                    HalfWidth, 0, WINDOW_WIDTH, HalfHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
}

The light pass starts by restoring the default FBO (the screen) and clearing it. Next we bind the FBO of the G buffer for reading. We now want to copy from the G buffer textures into the screen. One way to do that is to write a simple program where the FS samples from a texture and outputs the result. If we draw a full screen quad with texture coordinates that go from [0,0] to [1,1] we would get the result that we want. But there is a better way. OpenGL provides means to copy from one FBO to another using a single call and without all the setup overhead than the other method incurs. The function glBlitFramebuffer() takes the source coordinates, destination coordinates and a couple of other variables and performs the copy operation. It requires the source FBO to be bound to the GL_READ_FRAMEBUFFER and the destination FBO to the GL_DRAW_FRAMEBUFFER (which we did at the start of the function). Since the FBO can have several textures attached to its various attachment locations we must also bind the specific texture to the GL_READ_BUFFER target (because we can only copy from a single texture at a time). This is hidden inside GBuffer::SetReadBuffer() which we will review in a bit. The first four parameters to glBlitframebuffer() defines the source rectangle - bottom X, bottom Y, top X, top Y. The next four parameters define the destination rectangle in the same way.

The ninth parameter says whether we want to read from the color, depth or stencil buffer and can take the values GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT, or GL_STENCIL_BUFFER_BIT. The last parameter determines the way in which OpenGL will handle possible scaling (when the source and destination parameters are not of the same dimensions) and can be GL_NEAREST or GL_LINEAR (looks better than GL_NEAREST but requires more compute resources). GL_LINEAR is the only valid option in the case of GL_COLOR_BUFFER_BIT. In the example above we see how to scale down each source texture into one of the screen quadrants.

(geometry_pass.vs)

#version 330

layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
layout (location = 2) in vec3 Normal;

uniform mat4 gWVP;
uniform mat4 gWorld;

out vec2 TexCoord0;
out vec3 Normal0;
out vec3 WorldPos0;

void main()
{
    gl_Position = gWVP * vec4(Position, 1.0);
    TexCoord0 = TexCoord;
    Normal0 = (gWorld * vec4(Normal, 0.0)).xyz;
    WorldPos0 = (gWorld * vec4(Position, 1.0)).xyz;
}

This is the entire VS of the geometry pass. There is nothing new here. We simple perform the usual transformations and pass the results to the FS.

(geometry_pass.fs)

#version 330

in vec2 TexCoord0;
in vec3 Normal0;
in vec3 WorldPos0;

layout (location = 0) out vec3 WorldPosOut;
layout (location = 1) out vec3 DiffuseOut;
layout (location = 2) out vec3 NormalOut;
layout (location = 3) out vec3 TexCoordOut;

uniform sampler2D gColorMap;

void main()
{
    WorldPosOut = WorldPos0;
    DiffuseOut = texture(gColorMap, TexCoord0).xyz;
    NormalOut = normalize(Normal0);
    TexCoordOut = vec3(TexCoord0, 0.0);
}

The FS is responsible for doing MRT. Instead of outputting a single vector it outputs multiple vectors. Each of these vectors goes to a corresponding index in the array that was previously set by glDrawBuffers(). So in each FS invocation we are writing into the four textures of the G buffer.

(gbuffer.cpp:90)

void GBuffer::BindForWriting()
{
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo);
}

void GBuffer::BindForReading()
{
    glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo);
}

void GBuffer::SetReadBuffer(GBUFFER_TEXTURE_TYPE TextureType)
{
    glReadBuffer(GL_COLOR_ATTACHMENT0 + TextureType);
}

The above three functions are used to change the state of the G buffer to fit the current pass by the main application code.

Next tutorial


Tutorial 36 - Deferred Shading - Part 2

Tutorial 36:

Deferred Shading - Part 2

Background

In the previous tutorial we learned the basics of deferred shading and populated the G-Buffer with the combined results of the geometry pass. If you ran the demo you saw how the contents of the G-Buffer look like. Today we will complete the basic implementation of deferred shading and our final scene should look the same (well, about the same...) as if it was created using forward rendering. By the time we finish this tutorial a problem will become evident. This problem will be fixed in the next tutorial.

Now that the G-Buffer is properly populated we want to use it for lighting. The lighting equations themselves haven't changed at all. The concepts of ambient, diffuse and specular lights are still the same and all the relevant data is spread across the textures in the G-Buffer. For every pixel on the screen we just need to sample the data from the different textures and do the same lighting calculations as before. The only question is: how do we know which pixels to process? In forward rendering that was easy. The VS provided the position in clip space, there was an automatic step that translated this into screen space and the rasterizer was incharge of executing the FS for each pixel inside the screen space triangles. We simply calculated lighting on these pixels. But now that the geometry pass has finished we don't want to use the original objects again. That would defeat the whole purpose of deferred shading.

Instead, we look at things from the point of view of the light source. If we have a directional light in our scene then all the screen pixels are affected by it. In that case we can simply draw a full screen quad. The FS will execute for every pixel and we will shade it as usuall. In the case of a point light we can render a crude sphere model with its center at the light source. The size of the sphere will be set according to the strength of the light. Again, the FS will execute for all pixels inside the sphere and we will use it for lighting. This is exactly one of the strengths of deferred shading - reducing the amount of pixels that must be shaded. Instead of calculating the effect of a small light source on all objects in the scene we take it into account only in its local vicinity. We just need to set the sphere to a size which will bound the area where this light has an actual effect.

The demo in this tutorial is very simple, showing only a few boxes and three light sources. It's a bit ironic that the number of vertices in the bounding sphere is larger than the number of vertices in the actual models. However, you need to remember that in a scene in a modern game you have an order of hundreds of thousands of vertices. In this case it is not that big a deal to add a few dozen vertices by rendering a bounding sphere around each light source. In the following picture you can see the light volume of three light sources:

If we execute the FS only on the pixels inside those gray patches it will dramatically reduce the total number of FS invocations. In the case of a complex scene with a large depth complexity the gap becomes even greater. So now the question is: how to set the size of the bounding box?

We want it to be large enough so that the light wouldn't appear to cut off too abruptly but small enough so that distant pixels where the light is too weak to bother won't be shaded. The solution is simple - use our attenuation model to find the optimal size. The attenuation model uses a constant, linear and exponential components as well as the distance from the light source. Since our FS multiplies the color of light by its intensity (which is usually between 0.0 and 1.0) and then divides by the attenuation, we need to find the distance where the attenuation will cause the result of this division to be lower than some threshold. An 8 bit per channel provides 16,777,216 different colors and is considered a standard color scheme. Each channel allows for 256 different values so let's set our threshold at 1/256 (below this is black). Since the maximum channel of color can be less than 256 the attenuation that will cause it to drop below the threshold can also be less than 256. This is how we calculate the distance:

The above is based on the quadratic equation solution.

Source walkthru

(tutorial36.cpp:142)

virtual void RenderSceneCB()
{
    CalcFPS();

    m_scale += 0.05f;

    m_pGameCamera->OnRender();

    DSGeometryPass();

    BeginLightPasses();

    DSPointLightsPass();

    DSDirectionalLightPass();
    RenderFPS();

    glutSwapBuffers();
}

Let's explore the code changes top down. Not much has changed in the main render function from the previous tutorial. We've added a function to set up common stuff for the light pass (BeginLightPasses()) and separated the pass itself into two functions. One to handle point lights and the other directional lights (spot lights are left as an exercise to the reader).

(tutorial36.cpp:164)

void DSGeometryPass()
{
    m_DSGeomPassTech.Enable();

    m_gbuffer.BindForWriting();

    // Only the geometry pass updates the depth buffer
    glDepthMask(GL_TRUE);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glEnable(GL_DEPTH_TEST);

    glDisable(GL_BLEND);
    Pipeline p;
    p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());
    p.SetPerspectiveProj(m_persProjInfo);
    p.Rotate(0.0f, m_scale, 0.0f);

    for (unsigned int i = 0 ; i < ARRAY_SIZE_IN_ELEMENTS(m_boxPositions) ; i++) {
        p.WorldPos(m_boxPositions[i]);
        m_DSGeomPassTech.SetWVP(p.GetWVPTrans());
        m_DSGeomPassTech.SetWorldMatrix(p.GetWorldTrans());
        m_box.Render();
    }

    // When we get here the depth buffer is already populated and the stencil pass
    // depends on it, but it does not write to it.
    glDepthMask(GL_FALSE);

    glDisable(GL_DEPTH_TEST);
}

There are three main changes in the geometry pass. The first one is that we use the function glDepthMask() to prevent anything but this pass from writing into the depth buffer. The geometry pass needs the depth buffer in order to populate the G-Buffer with the closest pixels. In the light pass we have a single texel per screen pixel so we don't have anything to write into the depth buffer. This brings us to the second change which is to limit the depth test to the geometry pass. No point in doing depth test in the light pass when there is no one to compete against. An important point we must be careful about is to enable writing into the depth buffer before clearing it. glClear() does not touch the depth buffer if the depth mask is set to FALSE. The last change is that we disable blending. We will see later how the light pass uses blending in order to append multiple light sources together. In the geometry pass it is irrelevant.

(tutorial36.cpp:199)

void BeginLightPasses()
{
    glEnable(GL_BLEND);
    glBlendEquation(GL_FUNC_ADD);
    glBlendFunc(GL_ONE, GL_ONE);

    m_gbuffer.BindForReading();
    glClear(GL_COLOR_BUFFER_BIT);
}

Before we start the actual light passes we have the function above to take care of some common stuff. As mentioned earlier, we need blending for both light types because each light source is handled by its own draw call. In forward rendering we accumulated the results of all light sources in the FS but now each FS invocation only deals with a single light source. We need a way to accumulate the lights together and blending is the answer. Blending is simply a function that takes a source color (output of a FS) and a destination color (from the framebuffer) and performs some calculation on them. Blending is often used in order to create the illusion of transparency because it is capable of taking partial values from the source and destination and blend them together. In our case we set the blend equation to be GL_FUNC_ADD. This means that the GPU will simply add the source and the destination. Since we want true addition we set the blend function to be GL_ONE for the source and destination. The result is: 1 * src + 1 * dst. Oh, and we need to enable blending before we do that...

After we took care of blending we set the G-Buffer for reading and clear the color buffer. We are now ready for the light passes.

(tutorial36.cpp:210)

void DSPointLightsPass()
{
    m_DSPointLightPassTech.Enable();
    m_DSPointLightPassTech.SetEyeWorldPos(m_pGameCamera->GetPos());

    Pipeline p;
    p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());
    p.SetPerspectiveProj(m_persProjInfo);

    for (unsigned int i = 0 ; i < ARRAY_SIZE_IN_ELEMENTS(m_pointLight); i++) {
        m_DSPointLightPassTech.SetPointLight(m_pointLight[i]);
        p.WorldPos(m_pointLight[i].Position);
        float BSphereScale = CalcPointLightBSphere(m_pointLight[i]);
        p.Scale(BSphereScale, BSphereScale, BSphereScale);
        m_DSPointLightPassTech.SetWVP(p.GetWVPTrans());
        m_bsphere.Render();
    }
}

In the point light pass we simply render a bounding sphere for each point light. The bounding sphere center is set at the location of the light source and the function CalcPointLightBSphere() calculates the size of the sphere according to the parameters of the light.

(tutorial36.cpp:275)

float CalcPointLightBSphere(const PointLight& Light)
{
    float MaxChannel = fmax(fmax(Light.Color.x, Light.Color.y), Light.Color.z);

    float ret = (-Light.Attenuation.Linear + sqrtf(Light.Attenuation.Linear * Light.Attenuation.Linear -
        4 * Light.Attenuation.Exp * (Light.Attenuation.Exp - 256 * MaxChannel * Light.DiffuseIntensity)))
            /
        (2 * Light.Attenuation.Exp);
    return ret;
}

This function calculates the size of the bounding box for the specified light source. This is a direct implementation of the formula given in the background section.

(tutorial36.cpp:230)

void DSDirectionalLightPass()
{
    m_DSDirLightPassTech.Enable();
    m_DSDirLightPassTech.SetEyeWorldPos(m_pGameCamera->GetPos());
    Matrix4f WVP;
    WVP.InitIdentity();
    m_DSDirLightPassTech.SetWVP(WVP);
    m_quad.Render();
}

Handling directional light (we support only one such light source) is even simpler. We just need a full screen quad to reach all the pixels. The quad model that we use go from (-1,-1) to (1,1) so we want our WVP matrix to be the identity matrix. This will leave the vertices as-is and after perspective divide and screen space transform we will get a quad that goes from (0,0) to (SCREEN_WIDTH,SCREEN_HEIGHT).

(light_pass.vs)

#version 330

layout (location = 0) in vec3 Position;

uniform mat4 gWVP;

void main()
{
    gl_Position = gWVP * vec4(Position, 1.0);
}

The VS of the light passes is as simple as it gets. In the case of directional light the WVP matrix is the identity matrix so the position is simply passed through. In the case of a point light we get the projection of the bounding sphere on the screen. These are the pixels that we want to shade.

(dir_light_pass.fs:108)

void main()
{
    vec2 TexCoord = CalcTexCoord();
    vec3 WorldPos = texture(gPositionMap, TexCoord).xyz;
    vec3 Color = texture(gColorMap, TexCoord).xyz;
    vec3 Normal = texture(gNormalMap, TexCoord).xyz;
    Normal = normalize(Normal);

    FragColor = vec4(Color, 1.0) * CalcDirectionalLight(WorldPos, Normal);
}

(point_light_pass.fs:109)

void main()
{
    vec2 TexCoord = CalcTexCoord();
    vec3 WorldPos = texture(gPositionMap, TexCoord).xyz;
    vec3 Color = texture(gColorMap, TexCoord).xyz;
    vec3 Normal = texture(gNormalMap, TexCoord).xyz;
    Normal = normalize(Normal);

    FragColor = vec4(Color, 1.0) * CalcPointLight(WorldPos, Normal);
}

These are the fragment shaders for the directional and point lights. We have separate functions for them because internally they use different logic. In cases like that it is better from a performance point of view to use separate shaders than adding a branch inside the shader. The internal functions for light calculation are practically the same as the ones we've been using for quite some time now. We sample the G Buffer in order to get the world position, color and normal. In the previous tutorial we also had a place in the G Buffer for the texture coordinates but it is better to save that space and calculate it on the fly. This is very easy and is done in the function below.

(dir_light_pass.fs:101, point_light_pass.fs:101)

vec2 CalcTexCoord()
{
   return gl_FragCoord.xy / gScreenSize;
}

We need to sample from the G Buffer according to the location of the pixel on the screen. GLSL provides a handy built-in variable called gl_FragCoord which is exactly what we need. It is a 4D vector which contains the screen space coordinates of the current pixel in its XY components, the depth of the pixels in its Z component and 1/W in its W component. We need to supply the screen width and height to the FS and by dividing the screen space position by the dimensions of the screen we get a value between 0 and 1 that can serve as a texture coordinate for accessing the entire range of the G Buffer.

(gbuffer.cpp:49)

bool GBuffer::Init(unsigned int WindowWidth, unsigned int WindowHeight)
{
    ...
    for (unsigned int i = 0 ; i < ARRAY_SIZE_IN_ELEMENTS(m_textures) ; i++) {
        ...
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        ...
    }
    ...
}

We need to do a minor addition to the initialization of the G Buffer. In the previous tutorial we rendered into it and then used a blit operation to copy it to the default framebuffer. Since we are going to use it for actual sampling and there is a 1 to 1 mapping between a screen pixel and G Buffer texel we set the filtering type to GL_NEAREST. This prevents unnecessary interpolation between the texels that might create some fine distortions.

(gbuffer.cpp:98)

void GBuffer::BindForReading()
{
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);

    for (unsigned int i = 0 ; i < ARRAY_SIZE_IN_ELEMENTS(m_textures); i++) {
        glActiveTexture(GL_TEXTURE0 + i);
        glBindTexture(GL_TEXTURE_2D, m_textures[GBUFFER_TEXTURE_TYPE_POSITION + i]);
    }
}

On the same note, we need to do some modifications to the way we bind the G Buffer for reading before starting the light pass. Instead of binding it to the GL_READ_FRAMEBUFFER target we disconnect it from the GL_DRAW_FRAMEBUFFER target by binding the default FB instead. Finally, we bind the three textures to the appropriate texture units so we can sample from them in the FS.

Problems, problems, ...

There are a couple of problems with our current implementation of deferred shading. The first one you will probably notice is that when the camera enters the light volume the light disappears. The reason is that we only render the front face of the bounding sphere so once inside it is culled away. If we disable back face culling then due to blending we will get an increased light when outside the sphere (because we will render both faces) and only half of it when inside (when only the back face is rendered).

The second problem is that the bounding sphere doesn't really bound the light and sometimes objects that are outside of it are also lit because the sphere covers them in screen space so we calculate lighting on them.

We will deal with these problems in the next tutorial.

Next tutorial


Tutorial 37 - Deferred Shading - Part 3

Tutorial 37:

Deferred Shading - Part 3

Background

Our implementation of deferred shading may seem ok right now, but when looking closely you can see a couple of problems that were mentioned at the end of the previous tutorial. The first one is that due to back face culling the light disappears as soon as the camera enters the light volume. The second problem is related to bounding the area effected by the light. The thing is that since we are selecting the pixels to do lighting calculations on by drawing up a sphere around the light source and that sphere gets projected to screen space before rasterization, every pixel covered by the sphere in screen space enters the calculation, even if it is very far away (and effectively outside the light volume).

What helps us solve these problems is a feature of OpenGL known as the Stencil Buffer. The stencil buffer lives side by side with the color and depth buffer and shares their resolution (for every pixel in the color buffer there is a pixel in the stencil buffer). The type of a pixel in the stencil buffer is an integer and it is usually one byte in width. The stencil buffer serves roughly the same purpose as stencil paper in the real world. A stencil paper is usually used to print letters or any other type of design by having the desired pattern cut into the paper itself. In OpenGL the stencil buffer can be used to limit the pixels where the pixel shader is executed.

The stencil buffer is connected with the Stencil Test which is a per-fragment operation we are seeing here for the first time. In a similar manner to the depth test, the stencil test can be used to discard pixels prior to pixel shader execution. It works by comparing the value at the current pixel location in the stencil buffer with a reference value. There are several comparison functions available:

  • Always pass
  • Always fail
  • Less/greater than
  • Less/greater than or equal
  • Equal
  • Not equal

Based on the result of both the stencil test as well as the depth test you can define an action known as the stencil operation on the stored stencil value. The following operations are available:

  • Keep the stencil value unchanged
  • Replace the stencil value with zero
  • Increment/decrement the stencil value
  • Invert the bits of the stencil value

You can configure different operations for each of the following cases:

  • Stencil test failure
  • Depth test failure
  • depth test success

In addition, you can configure different stencil tests and stencil operations for the two faces of each polygon. For example, you can set the comparison function for the front face to be 'Less Than' with a reference value of 3 while the comparison function for the back face is 'Equal' with a reference value of 5. The same goes for the stencil operation.

This, in a nutshell, is the stencil test. So how can it help us solve the above problems? Well, we are going to take advantage of the ability to increment and decrement the stencil value based on the result of the depth test on the front and back faces of the polygons. Consider the following picture:

The picture shows 3 objects - A, B and C and a yellow sphere that is centered on a light source. The sphere gets projected on the virtual screen and according to the previous tutorial we need to render the light volume and for each rasterized pixel calculate the lighting effect. It is very simple to see that while the entire red line (in reality this is a rectangle because we are looking down at the scene) will reach the fragment shader only a very small subset of it is really relevant because only object B is inside the light volume. Both A and C are outside of it and there are many pixels in the G buffer that don't contain any data at all because there is no object along the way.

The way we are going to use the stencil buffer to limit the lighting calculations only to the pixels covered by object B is based on the same concept used in a shadowing technique known as Stencil Shadow Volumes (which will be covered by dedicated tutorial sometime in the future...). Our technique is based on the following interesting property which is evident in the picture above: when we look at the sphere from the camera point of view both its front and back face polygons are behind object A, the same polygons are infront of object C but in the case of object B the front face polygons are infront of it but the back face polygons are behind it. Let's see how we can take advantage of it in the context of the stencil test.

The techique works as follows:

  1. Render the objects as usual into the G buffer so that the depth buffer will be properly populated.
  2. Disable writing into the depth buffer. From now on we want it to be read-only
  3. Disable back face culling. We want the rasterizer to process all polygons of the sphere.
  4. Set the stencil test to always succeed. What we really care about is the stencil operation.
  5. Configure the stencil operation for the back facing polygons to increment the value in the stencil buffer when the depth test fails but to keep it unchanged when either depth test or stencil test succeed.
  6. Configure the stencil operation for the front facing polygons to decrement the value in the stencil buffer when the depth test fails but to keep it unchanged when either depth test or stencil test succeed.
  7. Render the light sphere.

Let's see the effect of the above scheme on the picture above:

The picture shows three example vectors from the camera to the screen that cross both the sphere and one of the objects. Each vector is representative for all pixels covered by that particular object. Since the geometry was already rendered and the depth buffer is populated we can check what happens to the depth test when the vector goes through the front and back pixels of the sphere and update the stencil buffer accordingly. In the case of object A both the front and the back pixels fail the depth test. The back face pixel increments the stencil value but this is nullified by the front pixel which decrements it. In the case of object C both the front and back pixels win the depth test so the stencil value remains unchanged. Now pay attention to what happens to object B - the front face pixel wins the depth test but the back face pixel fails it. This means that we increment the value by one.

This is the core of the technique. We render the geometry into the G buffer, setup the stencil test/operation according to the above and then render the bounding sphere of each light into the stencil buffer. The peculiar stencil setup that we saw guarantees that only the pixels in the stencil buffer covered by objects inside the bounding sphere will have a value greater than zero. We call this step the Stencil Pass and since we are only interested in writing into the stencil buffer we use a null fragment shader. Next we render the sphere again using the lighting fragment shader but this time we configure the stencil test to pass only when the stencil value of the pixel is different from zero. All the pixels of objects outside the light volume will fail the stencil test and we will calculate lighting on a very small subset of the pixels that are actually covered by the light sphere.

Let's see another example, this time with more light sources:

As you can see, the logic still works (the case when the camera is inside the light source is left as an exercise for the reader).

One last note about the stencil buffer - it is not a separate buffer but actually part of the depth buffer. You can have depth/stencil buffer with 24 or 32 bits for depth and 8 bits for stencil in each pixel.

Source walkthru

(tutorial37.cpp:149)

virtual void RenderSceneCB()
{
    CalcFPS();

    m_scale += 0.05f;

    m_pGameCamera->OnRender();

    m_gbuffer.StartFrame();

    DSGeometryPass();

    // We need stencil to be enabled in the stencil pass to get the stencil buffer
    // updated and we also need it in the light pass because we render the light
    // only if the stencil passes.
    glEnable(GL_STENCIL_TEST);

    for (unsigned int i = 0 ; i < ARRAY_SIZE_IN_ELEMENTS(m_pointLight); i++) {
        DSStencilPass(i);
        DSPointLightPass(i);
    }

    // The directional light does not need a stencil test because its volume
    // is unlimited and the final pass simply copies the texture.
    glDisable(GL_STENCIL_TEST);

    DSDirectionalLightPass();

    DSFinalPass();

    RenderFPS();

    glutSwapBuffers();
}

The piece of code above is the main render function with changes from the previous tutorial marked in bold. The first change is the call to the StartFrame() API of the GBuffer class. The GBuffer becomes quite complex in this tutorial and needs to be informed about the start of a new frame (changes to this class will be reviewed later but for now we'll just mention that we are not rendering directly to the screen but to an intermediate buffer which will be copied to the main FBO). Next we enable the stencil test because we need it for the two upcoming passes. Now comes the most important change - for each light we do a stencil pass (which marks the relevant pixels) followed by a point light pass which depends on the stencil value. The reason why we need to handle each light source separately is because once a stencil value becomes greater than zero due to one of the lights we cannot tell whether another light source which also overlaps the same pixel is relevant or not.

After we finish with all the point lights we disable the stencil test because for a directional light we need to process all pixels anyway. The last change in the function is the final pass which is also a new pass required due to the complexity of the GBuffer class.

(tutorial37.cpp:185)

void DSGeometryPass()
{
    m_DSGeomPassTech.Enable();

    m_gbuffer.BindForGeomPass();

    // Only the geometry pass updates the depth buffer
    glDepthMask(GL_TRUE);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glEnable(GL_DEPTH_TEST);

    Pipeline p;
    p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());
    p.SetPerspectiveProj(m_persProjInfo);
    p.Rotate(0.0f, m_scale, 0.0f);

    for (unsigned int i = 0 ; i < ARRAY_SIZE_IN_ELEMENTS(m_boxPositions) ; i++) {
        p.WorldPos(m_boxPositions[i]);
        m_DSGeomPassTech.SetWVP(p.GetWVPTrans());
        m_DSGeomPassTech.SetWorldMatrix(p.GetWorldTrans());
        m_box.Render();
    }

    // When we get here the depth buffer is already populated and the stencil pass
    // depends on it, but it does not write to it.
    glDepthMask(GL_FALSE);
}

There are minor changes in the geometry pass. The function GBuffer::BindForWriting() has been renamed GBuffer::BindForGeomPass(). In addition, those of you with a sharp eye will notice that we no longer disabling blending and the depth test. Both of these items are now manipulated elsewhere.

(tutorial37.cpp:215)

void DSStencilPass(unsigned int PointLightIndex)
{
    m_nullTech.Enable();

    // Disable color/depth write and enable stencil
    m_gbuffer.BindForStencilPass();
    glEnable(GL_DEPTH_TEST);

    glDisable(GL_CULL_FACE);

    glClear(GL_STENCIL_BUFFER_BIT);

    // We need the stencil test to be enabled but we want it
    // to succeed always. Only the depth test matters.
    glStencilFunc(GL_ALWAYS, 0, 0);

    glStencilOpSeparate(GL_BACK, GL_KEEP, GL_INCR_WRAP, GL_KEEP);
    glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_DECR_WRAP, GL_KEEP);

    Pipeline p;
    p.WorldPos(m_pointLight[PointLightIndex].Position);
    float BBoxScale = CalcPointLightBSphere(m_pointLight[PointLightIndex].Color,
        m_pointLight[PointLightIndex].DiffuseIntensity);
    p.Scale(BBoxScale, BBoxScale, BBoxScale);
    p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());
    p.SetPerspectiveProj(m_persProjInfo);

    m_nullTech.SetWVP(p.GetWVPTrans());
    m_bsphere.Render();
}

Now comes the real meat - the stencil pass. Let's review it step by step. We start by enabling the null technique. This is an extremely simple technique. The VS includes only a transformation of the position vector by the WVP matrix and the FS is empty. We don't need anything in the FS because we are not updating the color buffer at all. Only the stencil buffer is updated so we just need something to drive rasterization. We bind the GBuffer for this pass and enable the depth test. We will later see that the point light pass disable the depth test but we need it here because the stencil operation depends on it. Next we disable culling because we want to process both the front and back faces of each polygon. After that we clear the stencil buffer and setup the stencil test to always pass and the stencil operation according to the description in the background section. Everything after that is as usual - we render the bounding sphere based on the light params. When we are done the stencil buffer contains positive values only in the pixels of objects inside the light volume. We can now do lighting calculations.

(tutorial37.cpp:246)

void DSPointLightPass(unsigned int PointLightIndex)
{
    m_gbuffer.BindForLightPass();

    m_DSPointLightPassTech.Enable();
    m_DSPointLightPassTech.SetEyeWorldPos(m_pGameCamera->GetPos());

    glStencilFunc(GL_NOTEQUAL, 0, 0xFF);

    glDisable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    glBlendEquation(GL_FUNC_ADD);
    glBlendFunc(GL_ONE, GL_ONE);

    glEnable(GL_CULL_FACE);
    glCullFace(GL_FRONT);

    Pipeline p;
    p.WorldPos(m_pointLight[PointLightIndex].Position);
    float BBoxScale = CalcPointLightBSphere(m_pointLight[PointLightIndex].Color,
                                            m_pointLight[PointLightIndex].DiffuseIntensity);
    p.Scale(BBoxScale, BBoxScale, BBoxScale);
    p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());
    p.SetPerspectiveProj(m_persProjInfo);
    m_DSPointLightPassTech.SetWVP(p.GetWVPTrans());
    m_DSPointLightPassTech.SetPointLight(m_pointLight[PointLightIndex]);
    m_bsphere.Render();
    glCullFace(GL_BACK);

    glDisable(GL_BLEND);
}

Same as the other passes the point light pass starts by setting up the G buffer for what it needs (by calling GBuffer::BindForLightPass()). It sets up the stencil test to pass when the stencil value is not equal to zero. After that it disables the depth test (because we don't need it and on some GPUs we may get some performance by disabling it) and enable blending as usual. The next step is very important - we enable culling of the front face polygons. The reason why we do that is because the camera may be inside the light volume and if we do back face culling as we normally do we will not see the light until we exit its volume. After that we render the bounding sphere as usual.

The directional light pass is pretty much the same as before so we won't review it.

(tutorial37.cpp:296)

void DSFinalPass()
{
    m_gbuffer.BindForFinalPass();
    glBlitFramebuffer(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT,
                      0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, GL_COLOR_BUFFER_BIT, GL_LINEAR);
}

In the final pass we blit from the color buffer inside the G Buffer into the screen. This is a good place to discuss why we added an intermediate color buffer in the G Buffer instead of rendering directly to the screen. The thing is that our G Buffer combines as a target the buffers for the attributes with the depth/stencil buffer. When we run the point light pass we setup the stencil stuff and we need to use the values from the depth buffer. Here we have a problem - if we render into the default FBO we won't have access to the depth buffer from the G Buffer. But the G Buffer must have its own depth buffer because when we render into its FBO we don't have access to the depth buffer from the default FBO. Therefore, the solution is to add to the G Buffer FBO a color buffer to render into and in the final pass blit it to the default FBO color buffer. This is the final pass above.

(gbuffer.h:23)

class GBuffer
{
public:

    enum GBUFFER_TEXTURE_TYPE {
        GBUFFER_TEXTURE_TYPE_POSITION,
        GBUFFER_TEXTURE_TYPE_DIFFUSE,
        GBUFFER_TEXTURE_TYPE_NORMAL,
        GBUFFER_NUM_TEXTURES
    };

    GBuffer();

    ~GBuffer();

    bool Init(unsigned int WindowWidth, unsigned int WindowHeight);

    void StartFrame();
    void BindForGeomPass();
    void BindForStencilPass();
    void BindForLightPass();
    void BindForFinalPass();

private:

    GLuint m_fbo;
    GLuint m_textures[GBUFFER_NUM_TEXTURES];
    GLuint m_depthTexture;
    GLuint m_finalTexture;
};

We've added a final texture to the GBuffer class for the color and reshuffled the API quite a bit since the last tutorial. Let's review the changes.

(gbuffer.cpp:52)

bool GBuffer::Init(unsigned int WindowWidth, unsigned int WindowHeight)
{
    ...

    glGenTextures(1, &m_finalTexture);

    ...

    // depth
    glBindTexture(GL_TEXTURE_2D, m_depthTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH32F_STENCIL8, WindowWidth, WindowHeight, 0, GL_DEPTH_STENCIL,
                  GL_FLOAT_32_UNSIGNED_INT_24_8_REV, NULL);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_depthTexture, 0);

    // final
    glBindTexture(GL_TEXTURE_2D, m_finalTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, WindowWidth, WindowHeight, 0, GL_RGB, GL_FLOAT, NULL);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT4, GL_TEXTURE_2D, m_finalTexture, 0);

    ...
}

When we initialize the G Buffer we need to allocate one more texture for the final texture. The final texture is attached to attachment point number 4. The depth texture is no longer created with type GL_DEPTH_COMPONENT32F. Instead we create it as GL_DEPTH32F_STENCIL8. This leave a full byte for the stencil value in each pixel. This depth buffer is attached to GL_DEPTH_STENCIL_ATTACHMENT instead of GL_DEPTH_COMPONENT.

(gbuffer.cpp:97)

void GBuffer::StartFrame()
{
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo);
    glDrawBuffer(GL_COLOR_ATTACHMENT4);
    glClear(GL_COLOR_BUFFER_BIT);
}

At the start of each frame we need to clear the final texture which is attached to attachment point number 4.

(gbuffer.cpp:105)

void GBuffer::BindForGeomPass()
{
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo);

    GLenum DrawBuffers[] = { GL_COLOR_ATTACHMENT0,
                            GL_COLOR_ATTACHMENT1,
                            GL_COLOR_ATTACHMENT2 };

    glDrawBuffers(ARRAY_SIZE_IN_ELEMENTS(DrawBuffers), DrawBuffers);
}

Previously the FBO in the G Buffer was static (in terms of its configuration) and was set up in advance so we just had to bind it for writing when the geometry pass started. Now we keep changing the FBO to we need to config the draw buffers for the attributes each time.

(gbuffer.cpp:117)

void GBuffer::BindForStencilPass()
{
    // must disable the draw buffers
    glDrawBuffer(GL_NONE);
}

As mentioned earlier, in the stencil test we are not writing to the color buffer, only the stencil buffer. Indeed, even our FS is empty. However, in that case the default output color from the FS is black. In order to avoid garbaging the final buffer with a black image of the bounding sphere we disable the draw buffers here.

(gbuffer.cpp:125)

void GBuffer::BindForLightPass()
{
    glDrawBuffer(GL_COLOR_ATTACHMENT4);

    for (unsigned int i = 0 ; i < ARRAY_SIZE_IN_ELEMENTS(m_textures); i++) {
        glActiveTexture(GL_TEXTURE0 + i);
        glBindTexture(GL_TEXTURE_2D, m_textures[GBUFFER_TEXTURE_TYPE_POSITION + i]);
    }
}

The light pass is straightforward. We set the target to be the final buffer and bind the attribute buffers as a source.

(gbuffer.cpp:136)

void GBuffer::BindForFinalPass()
{
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo);
    glReadBuffer(GL_COLOR_ATTACHMENT4);
}

When we get to the final pass our final buffer is populated with the final image. Here we set things up for the blitting that takes place in the main application code. The default FBO is the target and the G Buffer FBO is the source.

This tutorial completes our introduction to deferred shading. It is definitely not the only "right way" to do it and you can find alternatives on the web but the core concepts are probably common. Like everything is life, it has its advantages and disadvantages. In future tutorials we will spend time on both forward and deferred rendering and improve their frameworks with new features.

Next tutorial


Tutorial 38 - Skeletal Animation With Assimp

Tutorial 38:

Skeletal Animation With Assimp

Background

Finally, it is here. The tutorial that millions of my readers (I may be exaggerating here, but definitely a few ;-) ) have been asking for. Skeletal animation, also known as Skinning, using the Assimp library.

Skeletal animation is actually a two part process. The first one is executed by the artist and the second by you, the programmer (or rather, the engine that you wrote). The first part takes place inside the modeling software and is called Rigging. What happens here is that the artist defines a skeleton of bones underneath the mesh. The mesh represents the skin of the object (be it a human, monster or whatever) and the bones are used to move the mesh in a way that would mimic actual movement in the real world. This is done by assigning each vertex to one or more bones. When a vertex is assigned to a bone a weight is defined that determines the amount of influence that bone has on the vertex when it moves. The common practice is to make the sum of all weights 1 (per vertex). For example, if a vertex is located exactly between two bones we would probably want to assign each bone a weight of 0.5 because we expect the bones to be equal in their influence on the vertex. However, if a vertex is entirely within the influence of a single bone then the weight would be 1 (which means that bone autonomously controls the movement of the vertex).

Here's an example of a bone structure created in blender:

What we see above is actually an important part of the animation. The artist riggs together the bone structure and defines a set of key frames for each animation type ("walk", "run", "die", etc). The key frames contain the transformations of all bones in critical points along the animation path. The graphics engine interpolates between the transformations of the keyframes and creates a smooth motion between them.

The bone structure used for skeletal animation is often heirarchical. This means that the bones have a child/parent relationships so a tree of bones is created. Every bone has one parent except for the root bone. In the case of the human body, for example, you may assign the back bone as the root with child bones such as arms and legs and finger bones on the next level done. When a parent bone moves it also moves all of its children, but when a child bone moves it does not move it parent (our fingers can move without moving the hand, but when the hand moves it moves all of its fingers). From a practical point of view this means that when we process the transformations of a bone we need to combine it with the transformations of all the parent bones that lead from it to the root.

We are not going to discuss rigging any further. It is a complex subject and outside the domain of graphics programmers. Modeling software has advanced tools to help the artist do this job and you need to be a good artist to create a good looking mesh and skeleton. Let's see what the graphics engine needs to do in order to make skeletal animation.

The first stage is to augument the vertex buffer with per vertex bone information. There are several options available but what we are going to do is pretty straightforward. For each vertex we are going to add an array of slots where each slot contains a bone ID and a weight. To make our life simpler we will use an array with four slots which means no vertex can be influenced by more than four bones. If you are going to load models with more bones you will need to adjust the array size but for the Doom 3 model that is part of this tutorial demo four bones are enough. So our new vertex structure is going to look like this:

The bone IDs are indices into an array of bone transformations. These tranformations will be applied on the position and normal before the WVP matrix (i.e. they transform the vertex from a "bone space" into local space). The weight will be used to combine the transformations of several bones into a single transformation and in any case the total weight must be exactly 1 (responsibility of the modeling software). Usually, we would interpolate between animation key frames and update the array of bone transformations in every frame.

The way the array of bone transformations is created is usually the tricky part. The transformations are set in a heirarchical structure (i.e. tree) and a common practice is to have a scaling vector, a rotation quaternion and a translation vector in every node in the tree. In fact, each node contains an array of these items. Every entry in the array must have a time stamp. The case where the application time will exactly match one of the time stamps is probably rare so our code must be able to interpolate the scaling/rotation/translation to get the correct transformation for the point in time of the application. We do the same process for each node from the current bone to the root and multiply this chain of transformations together to get the final result. We do that for each bone and then update the shader.

Everything that we talked about so far has been pretty generic. But this is a tutorial about skeletal animation with Assimp, so we need to dive into that library again and see how to do skinning with it. The good thing about Assimp is that it supports loading bone information from several formats. The bad thing is that you still need to do quite a bit of work on the data structures that it creates to generate the bone transformations that you need for the shaders.

Let's start at the bone information at the vertex level. Here's the relevant pieces in Assimp data structures:

As you probably recall from the tutorial on Assimp, everything is contained in the aiScene class (an object of which we get when we import the mesh file). The aiScene contains an array of aiMesh objects. An aiMesh is a part of the model and contains stuff at the vertex level such as position, normal, texture coordinates, etc. Now we see that aiMesh also contains an array of aiBone objects. Unsuprisingly, an aiBone represents one bone in the skeleton of the mesh. Each bone has a name by which it can be found in the bone heirarchy (see below), an array of vertex weights and a 4x4 offset matrix. The reason why we need this matrix is because the vertices are stored in the usual local space. This means that even without skeletal animation support our existing code base can load the model and render it correctly. But the bone transformations in the heirarchy work in a bone space (and every bone has its own space which is why we need to multiply the transformations together). So the job of the offset matrix it to move the vertex position from the local space of the mesh into the bone space of that particular bone.

The vertex weight array is where things start to become interesting. Each entry in this array contains an index into the array of vertices in the aiMesh (remember that the vertex is spread across several arrays with the same length) and a weight. The sum of all vertex weights must be 1 but to find them you need to walk through all the bones and accumulate the weights into a kind of list for each particular vertex.

After we build the bone information at the vertex level we need to process the bone transformation heirarchy and generate the final transformations that we will load into the shader. The following picture displays the relevant data structures:

Again, we start at the aiScene. The aiScene object contains a pointer to an object of the aiNode class which is the root of the a node heirarchy (in other words - a tree). Each node in the tree has a pointer back to its parent and an array of pointers to its children. This allows us to conveniently traverse the tree back and forth. In addition, the node carries a transformation matrix that transforms from the node space into the space of its parent. Finally, the node may or may not have a name. If a node represents a bone in the heirarchy then the node name must match the bone name. But sometimes nodes have no name (which means there is not corresponding bone) and their job is simply to help the modeller decompose the model and place some intermediate transformation along the way.

The last piece of the puzzle is the aiAnimation array which is also stored in the aiScene object. A single aiAnimation object represents a sequence of animation frames such as "walk", "run", "shoot", etc. By interpolating between the frames we get the desired visual effect which matches the name of the animation. An animation has a duration in ticks and the number of ticks per second (e.g 100 ticks and 25 ticks per second represent a 4 second animation) which help us time the progression so that the animation will look the same on every hardware. In addition, the animation has an array of aiNodeAnim objects called channels. Each channel is actually the bone with all its transformations. The channel contains a name which must match one of the nodes in the heirarchy and three transformation arrays.

In order to calculate the final bone transformation in a particular point in time we need to find the two entries in each of these three arrays that matches the time and interpolate between them. Then we need to combine the transformations into a single matrix. Having done that we need to find the corresponding node in the heirarchy and travel to its parent. Then we need the corresponding channel for the parent and do the same interpolation process. We multiply the two transformations together and continue until we reach the root of the heirarchy.

Source walkthru

(mesh.cpp:75)

bool Mesh::LoadMesh(const string& Filename)
{
    // Release the previously loaded mesh (if it exists)
    Clear();

    // Create the VAO
    glGenVertexArrays(1, &m_VAO);
    glBindVertexArray(m_VAO);

    // Create the buffers for the vertices attributes
    glGenBuffers(ARRAY_SIZE_IN_ELEMENTS(m_Buffers), m_Buffers);

    bool Ret = false;

    m_pScene = m_Importer.ReadFile(Filename.c_str(), aiProcess_Triangulate | aiProcess_GenSmoothNormals |
                                    aiProcess_FlipUVs);

    if (m_pScene) {
       m_GlobalInverseTransform = m_pScene->mRootNode->mTransformation;
       m_GlobalInverseTransform.Inverse();
        Ret = InitFromScene(m_pScene, Filename);
    }
    else {
       printf("Error parsing '%s': '%s'\n", Filename.c_str(), m_Importer.GetErrorString());
    }

    // Make sure the VAO is not changed from the outside
    glBindVertexArray(0);

    return Ret;
}

Here's the updated entry point to the Mesh class with changes marked in bold face. There are a couple of changes that we need to note. One is that the importer and aiScene object are now class members rather then stack variables. The reason is that during runtime we are going to go back to the aiScene object again and again and for that we need to extend the scope of both the importer and the scene. In a real game you may want to copy the stuff that you need and store it at a more optimized format but for educational purposes this is enough.

The second change is that the transformation matrix of the root of the heirarchy is extracted, inversed and stored. We are going to use that further down the road. Note that the matrix inverse code has been copied from the Assimp library into our Matrix4f class.

(mesh.h:69)

struct VertexBoneData
{
    uint IDs[NUM_BONES_PER_VEREX];
    float Weights[NUM_BONES_PER_VEREX];
}

(mesh.cpp:107)

bool Mesh::InitFromScene(const aiScene* pScene, const string& Filename)
{
    ...
    vector<VertexBoneData> Bones;
    ...
    Bones.resize(NumVertices);
    ...
    glBindBuffer(GL_ARRAY_BUFFER, m_Buffers[BONE_VB]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(Bones[0]) * Bones.size(), &Bones[0], GL_STATIC_DRAW);
    glEnableVertexAttribArray(BONE_ID_LOCATION);
    glVertexAttribIPointer(BONE_ID_LOCATION, 4, GL_INT, sizeof(VertexBoneData), (const GLvoid*)0);
    glEnableVertexAttribArray(BONE_WEIGHT_LOCATION);
    glVertexAttribPointer(BONE_WEIGHT_LOCATION, 4, GL_FLOAT, GL_FALSE, sizeof(VertexBoneData), (const GLvoid*)16);
    ...
}

The structure above contains everything we need at the vertex level. By default, we have enough storage for four bones (ID and weight per bone). VertexBoneData was structured like that to make it simple to pass it on to the shader. We already got position, texture coordinates and normal bound at locations 0, 1 and 2, respectively. Therefore, we configure our VAO to bind the bone IDs at location 3 and the weights at location 4. It is very important to note that we use glVertexAttribIPointer rather than glVertexAttribPointer to bind the IDs. The reason is that the IDs are integer and not floating point. Pay attention to this or you will get corrupted data in the shader.

(mesh.cpp:213)

void Mesh::LoadBones(uint MeshIndex, const aiMesh* pMesh, vector& Bones)
{
    for (uint i = 0 ; i < pMesh->mNumBones ; i++) {
        uint BoneIndex = 0;
        string BoneName(pMesh->mBones[i]->mName.data);

        if (m_BoneMapping.find(BoneName) == m_BoneMapping.end()) {
            BoneIndex = m_NumBones;
            m_NumBones++;
            BoneInfo bi;
            m_BoneInfo.push_back(bi);
        }
        else {
            BoneIndex = m_BoneMapping[BoneName];
        }

        m_BoneMapping[BoneName] = BoneIndex;
        m_BoneInfo[BoneIndex].BoneOffset = pMesh->mBones[i]->mOffsetMatrix;

        for (uint j = 0 ; j < pMesh->mBones[i]->mNumWeights ; j++) {
            uint VertexID = m_Entries[MeshIndex].BaseVertex + pMesh->mBones[i]->mWeights[j].mVertexId;
            float Weight = pMesh->mBones[i]->mWeights[j].mWeight;
            Bones[VertexID].AddBoneData(BoneIndex, Weight);
        }
    }
}

The function above loads the vertex bone information for a single aiMesh object. It is called from Mesh::InitMesh(). In addition to populating the VertexBoneData structure this function also updates a map between bone names and bone IDs (a running index managed by this function) and stores the offset matrix in a vector based on the bone ID. Note how the vertex ID is calculated. Since vertex IDs are relevant to a single mesh and we store all meshes in a single vector we add the base vertex ID of the current aiMesh to vertex ID from the mWeights array to get the absolute vertex ID.

(mesh.cpp:29)

void Mesh::VertexBoneData::AddBoneData(uint BoneID, float Weight)
{
    for (uint i = 0 ; i < ARRAY_SIZE_IN_ELEMENTS(IDs) ; i++) {
        if (Weights[i] == 0.0) {
            IDs[i] = BoneID;
            Weights[i] = Weight;
            return;
        }
    }

    // should never get here - more bones than we have space for
    assert(0);
}

This utility function finds a free slot in the VertexBoneData structure and places the bone ID and weight in it. Some vertices will be influenced by less than four bones but since the weight of a non existing bone remains zero (see the constructor of VertexBoneData) it means that we can use the same weight calculation for any number of bones.

(mesh.cpp:473)

Matrix4f Mesh::BoneTransform(float TimeInSeconds, vector<Matrix4f>& Transforms)
{
    Matrix4f Identity;
    Identity.InitIdentity();

    float TicksPerSecond = m_pScene->mAnimations[0]->mTicksPerSecond != 0 ?
                            m_pScene->mAnimations[0]->mTicksPerSecond : 25.0f;
    float TimeInTicks = TimeInSeconds * TicksPerSecond;
    float AnimationTime = fmod(TimeInTicks, m_pScene->mAnimations[0]->mDuration);

    ReadNodeHeirarchy(AnimationTime, m_pScene->mRootNode, Identity);

    Transforms.resize(m_NumBones);

    for (uint i = 0 ; i < m_NumBones ; i++) {
        Transforms[i] = m_BoneInfo[i].FinalTransformation;
    }
}

Loading of the bone information at the vertex level that we saw earlier is done only once when the mesh is loading during startup. Now we come to the second part which is calculating the bone transformations that go into the shader every frame. The function above is the entry point to this activity. The caller reports the current time in seconds (which can be a fraction) and provides a vector of matrices which we must update. We find the relative time inside the animation cycle and process the node heirarchy. The result is an array of transformations which is returned to the caller.

(mesh.cpp:428)

void Mesh::ReadNodeHeirarchy(float AnimationTime, const aiNode* pNode, const Matrix4f& ParentTransform)
{
    string NodeName(pNode->mName.data);

    const aiAnimation* pAnimation = m_pScene->mAnimations[0];

    Matrix4f NodeTransformation(pNode->mTransformation);

    const aiNodeAnim* pNodeAnim = FindNodeAnim(pAnimation, NodeName);

    if (pNodeAnim) {
        // Interpolate scaling and generate scaling transformation matrix
        aiVector3D Scaling;
        CalcInterpolatedScaling(Scaling, AnimationTime, pNodeAnim);
        Matrix4f ScalingM;
        ScalingM.InitScaleTransform(Scaling.x, Scaling.y, Scaling.z);

        // Interpolate rotation and generate rotation transformation matrix
        aiQuaternion RotationQ;
        CalcInterpolatedRotation(RotationQ, AnimationTime, pNodeAnim);
        Matrix4f RotationM = Matrix4f(RotationQ.GetMatrix());

        // Interpolate translation and generate translation transformation matrix
        aiVector3D Translation;
        CalcInterpolatedPosition(Translation, AnimationTime, pNodeAnim);
        Matrix4f TranslationM;
        TranslationM.InitTranslationTransform(Translation.x, Translation.y, Translation.z);

        // Combine the above transformations
        NodeTransformation = TranslationM * RotationM * ScalingM;
    }

    Matrix4f GlobalTransformation = ParentTransform * NodeTransformation;

    if (m_BoneMapping.find(NodeName) != m_BoneMapping.end()) {
        uint BoneIndex = m_BoneMapping[NodeName];
        m_BoneInfo[BoneIndex].FinalTransformation = m_GlobalInverseTransform * GlobalTransformation *
                                                    m_BoneInfo[BoneIndex].BoneOffset;
    }

    for (uint i = 0 ; i < pNode->mNumChildren ; i++) {
        ReadNodeHeirarchy(AnimationTime, pNode->mChildren[i], GlobalTransformation);
    }
}

This function traverses the node tree and generates the final transformation for each node/bone according to the specified animation time. It is limited in the sense that it assumes that the mesh has only a single animation sequence. If you want to support multiple animations you will need to tell it the animation name and search for it in the m_pScene->mAnimations[] array. The code above is good enough for the demo mesh that we use.

The node transformation is initialized from the mTransformation member in the node. If the node does not correspond to a bone then that is its final transformation. If it does we overwrite it with a matrix that we generate. This is done as follows: first we search for the node name in the channel array of the animation. Then we interpolate the scaling vector, rotation quaternion and translation vector based on the animation time. We combine them into a single matrix and multiply with the matrix we got as a parameter (named GlobablTransformation). This function is recursive and is called for the root node with the GlobalTransformation param being the identity matrix. Each node recursively calls this function for all of its children and passes its own transformation as GlobalTransformation. Since we start at the top and work our way down, we get the combined transformation chain at every node.

The m_BoneMapping array maps a node name to the index that we generate and we use that index to as an entry into the m_BoneInfo array where the final transformations are stored. The final transformation is calculated as follows: we start with the node offset matrix which brings the vertices from their local space position into there node space. We then multiple with the combined transformations of all of the nodes parents plus the specific transformation that we calculated for the node according to the animation time.

Note that we use Assimp code here to handle the math stuff. I saw no point in duplicating it into our own code base so I simply used Assimp.

(mesh.cpp:387)

void Mesh::CalcInterpolatedRotation(aiQuaternion& Out, float AnimationTime, const aiNodeAnim* pNodeAnim)
{
    // we need at least two values to interpolate...
    if (pNodeAnim->mNumRotationKeys == 1) {
        Out = pNodeAnim->mRotationKeys[0].mValue;
        return;
    }

    uint RotationIndex = FindRotation(AnimationTime, pNodeAnim);
    uint NextRotationIndex = (RotationIndex + 1);
    assert(NextRotationIndex < pNodeAnim->mNumRotationKeys);
    float DeltaTime = pNodeAnim->mRotationKeys[NextRotationIndex].mTime - pNodeAnim->mRotationKeys[RotationIndex].mTime;
    float Factor = (AnimationTime - (float)pNodeAnim->mRotationKeys[RotationIndex].mTime) / DeltaTime;
    assert(Factor >= 0.0f && Factor <= 1.0f);
    const aiQuaternion& StartRotationQ = pNodeAnim->mRotationKeys[RotationIndex].mValue;
    const aiQuaternion& EndRotationQ = pNodeAnim->mRotationKeys[NextRotationIndex].mValue;
    aiQuaternion::Interpolate(Out, StartRotationQ, EndRotationQ, Factor);
    Out = Out.Normalize();
}

This method interpolates the rotation quaternion of the specified channel based on the animation time (remember that the channel contains an array of key quaternions). First we find the index of the key quaternion which is just before the required animation time. We calculate the ratio between the distance from the animation time to the key before it and the distance between that key and the next. We need to interpolate between these two keys using that factor. We use an Assimp code to do the interpolation and normalize the result. The corresponding methods for position and scaling are very similar so they are not quoted here.

(mesh.cpp:335)

uint Mesh::FindRotation(float AnimationTime, const aiNodeAnim* pNodeAnim)
{
    assert(pNodeAnim->mNumRotationKeys > 0);

    for (uint i = 0 ; i < pNodeAnim->mNumRotationKeys - 1 ; i++) {
        if (AnimationTime < (float)pNodeAnim->mRotationKeys[i + 1].mTime) {
            return i;
        }
    }

    assert(0);
}

This utility method finds the key rotation which is immediately before the animation time. If we have N key rotations the result can be 0 to N-2. The animation time is always contained inside the duration of the channel so the last key (N-1) can never be a valid result.

(skinning.vs)

#version 330

layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
layout (location = 2) in vec3 Normal;
layout (location = 3) in ivec4 BoneIDs;
layout (location = 4) in vec4 Weights;

out vec2 TexCoord0;
out vec3 Normal0;
out vec3 WorldPos0;

const int MAX_BONES = 100;

uniform mat4 gWVP;
uniform mat4 gWorld;
uniform mat4 gBones[MAX_BONES];

void main()
{
    mat4 BoneTransform = gBones[BoneIDs[0]] * Weights[0];
    BoneTransform += gBones[BoneIDs[1]] * Weights[1];
    BoneTransform += gBones[BoneIDs[2]] * Weights[2];
    BoneTransform += gBones[BoneIDs[3]] * Weights[3];

    vec4 PosL = BoneTransform * vec4(Position, 1.0);
    gl_Position = gWVP * PosL;
    TexCoord0 = TexCoord;
    vec4 NormalL = BoneTransform * vec4(Normal, 0.0);
    Normal0 = (gWorld * NormalL).xyz;
    WorldPos0 = (gWorld * PosL).xyz;
}

Now that we have finished with the changes in the mesh class let's see what we need to do at the shader level. First, we've added the bone IDs and weights array to the VSInput structure. Next, there is a new uniform array that contains the bone transformations. In the shader itself we calculate the final bone transformation as a combination of the bone transformation matrices of the vertex and their weights. This final matrix is used to transform the position and normal from their bone space into the local space. From here on everything is the same.

(tutorial38.cpp:140)

float RunningTime = (float)((double)GetCurrentTimeMillis() - (double)m_startTime) / 1000.0f;

m_mesh.BoneTransform(RunningTime, Transforms);

for (uint i = 0 ; i < Transforms.size() ; i++) {
    m_pEffect->SetBoneTransform(i, Transforms[i]);
}

The last thing we need to do is to integrate all this stuff into the application code. This is done in the above simple code. The function GetCurrentTimeMillis() returns the time in milliseconds since the application startup (note the floating point to accomodate fractions).

If you've done everything correctly then the final result should look similar to this.

Next tutorial


Tutorial 39 - __

Tutorial 39:

Silhouette Detection

Background

Today we are going to discuss one way in which the silhouette of an object can be detected. To make things clearer, I'm referring to the silhouette of a 3D object which is created when light falls upon it from an arbitrary direction. Moving the light source will likely change the silhouette accordingly. This is entirely different from silhouette detection in image space that deals with finding the boundaries of an object in a 2D picture (which is usually not dependant on the location of the light source). While the subject of silhouette detection may be interesting by itself, for me its main goal is as a first step in the implementation of a Stencil Shadow Volume. This is a technique for rendering shadows which is particularly useful when dealing with point lights. We will study this technique in the next tutorial (so you may refer to this tutorial as "Stencil Shadow Volume - Part 1"...).

The following image demonstrates the silhouette that we want to detect:

In the image above the silhouette is the ellipsis which is touched by the light rays.

Let us now move to a more traditional 3D language. A model is basically composed of triangles so the silhouette must be created by triangle edges. How do we decide whether an edge is part of the silhouette or not? The trick is based on the diffuse light model. According to that model the light strength is based on the dot product between the triangle normal and the light vector. If the triangle faces away from the light source the result of this dot product operation will be less than or equal to zero. In that case the light doesn't affect the triangle at all. In order to decide whether a triangle edge is part of the silhouette or not we need to find the adjacent triangle that shares the same edge and calculate the dot product between the light direction and the normals of both the original triangle and its neighbor. An edge is considered a silhouette edge if one triangle faces the light but its neighbor does not.

The following picture shows a 2D object for simplicity:

The red arrow represents the light ray that hits the three edges (in 3D these would be triangles) whose normals are 1, 2 and 3 (dot product between these normals and the reverse of the light vector is obviously greater than zero). The edges whose normals are 4, 5 and 6 are facing away from the light (here the same dot product would be less than or equal to zero). The two blue circles mark the silhouette of the object and the reason is that edge 1 is facing the light but its neighbor edge 6 does not. The point between them is therefore a silhoette. Same goes for the other silhouette point. Edges (or points in this example) that face the light as well as their neighbors are not silhoette (between 1 and 2 and between 2 and 3).

As you can see, the algorithm for finding the silhouette is very simple. However, it does require us to have knowledge of the three neighbors of each triangle. This is known as the Adjacencies of the triangles. Unfortunately, Assimp does not support automatic adjacencies calculation for us so we need to implement such an algorithm ourselves. In the coding section we will review a simple algorithm that will satisfy our needs.

What is the best place in the pipeline for the silhouette algorithm itself? remember that we need to do a dot product between the light vector and the triangle normal as well as the normals of the three adjacent triangles. This requires us to have access to the entire primitive information. Therefore, the VS is not enough. Looks like the GS is more appropriate since it allows access to all the vertices of a primitive. But what about the adjacencies? luckily for us, the designers of OpenGL have already given it much thought and created a topology type known as 'triangle with adjacencies'. If you provide a vertex buffer with adjacency information it will correctly load it and provide the GS with six vertices per triangle instead of three. The additional three vertices belong to the adjacent triangles and are not shared with the current triangle. The following image should make this much clearer:

The red vertices in the above picture belong to the original triangle and the blue ones are the adjacent vertices (ignore the edges e1-e6 for now - they are referenced later in the code section). When we supply a vertex buffer in the above format the VS is executed for every vertex (adjacent and non adjacent) and the GS (if it exists) is executed on a group of six vertices that include the triangle and its adjacent vertices. When the GS is present it is up to the developer to supply an output topology but if there is no GS the rasterizer knows how to deal with such a scheme and it rasterizes only the actual triangles (ignoring the adjacent triangles). One of the readers informed me that such a setup has produced an error on his Macbook with Intel HD 3000 so if you run into a similar problem simply use a pass thru GS, or change the topology type.

Note that the adjacent vertices in the vertex buffer have the same format and attributes as regular vertices. What makes them adjacent is simply their relative location within each group of six vertices. In the case of a model whose triangles are continuous the same vertices will sometimes be regular and sometimes adjacent, depending on the current triangle. This makes indexed draws even more attractive due to the saving of space in the vertex buffer.

Source walkthru

(mesh.cpp:204)

void Mesh::FindAdjacencies(const aiMesh* paiMesh, vector& Indices)
{
    for (uint i = 0 ; i < paiMesh->mNumFaces ; i++) {
        const aiFace& face = paiMesh->mFaces[i];

        Face Unique;

        // If a position vector is duplicated in the VB we fetch the
        // index of the first occurrence.
        for (uint j = 0 ; j < 3 ; j++) {
            uint Index = face.mIndices[j];
            aiVector3D& v = paiMesh->mVertices[Index];

            if (m_posMap.find(v) == m_posMap.end()) {
                m_posMap[v] = Index;
            }
            else {
                Index = m_posMap[v];
            }

            Unique.Indices[j] = Index;
        }

        m_uniqueFaces.push_back(Unique);

        Edge e1(Unique.Indices[0], Unique.Indices[1]);
        Edge e2(Unique.Indices[1], Unique.Indices[2]);
        Edge e3(Unique.Indices[2], Unique.Indices[0]);

        m_indexMap[e1].AddNeigbor(i);
        m_indexMap[e2].AddNeigbor(i);
        m_indexMap[e3].AddNeigbor(i);
    }

Most of the adjacency logic is contained in the above function and a few helper structures. The algorithm is composed of two stages. In the first stage we create a map between each edge and the two triangles that share it. This happens in the above for loop. In the first half of this loop we generate a map between each vertex position and the first index that refers to it. The reason why different indices may point to vertices that share the same position is that sometimes other attributes force Assimp to split the same vertex into two vertices. e.g. the same vertex may have different texture attributes for two neighboring triangles that share it. This creates a problem for our adjacency algorithm and we prefer to have each vertex appear only once. Therefore, we create this mapping between a position and first index and use only this index from now on.

(mesh.cpp:240)

    for (uint i = 0 ; i < paiMesh->mNumFaces ; i++) {
        const Face& face = m_uniqueFaces[i];

        for (uint j = 0 ; j < 3 ; j++) {
            Edge e(face.Indices[j], face.Indices[(j + 1) % 3]);
            assert(m_indexMap.find(e) != m_indexMap.end());
            Neighbors n = m_indexMap[e];
            uint OtherTri = n.GetOther(i);

            assert(OtherTri != -1)

            const Face& OtherFace = m_uniqueFaces[OtherTri];
            uint OppositeIndex = OtherFace.GetOppositeIndex(e);

            Indices.push_back(face.Indices[j]);
            Indices.push_back(OppositeIndex);
        }
    }
}

In the second stage we populate the index vector with sets of six vertices each that match the topology of the triangle list with adjacency that we saw earlier. The map that we created in the first stage helps us here because for each edge in the triangle it is very easy to find the neighboring triangle that shares it and then the vertex in that triangle which is opposite to this edge. The last two lines in the loop alternate the content of the index buffer between vertices from the current triangle and vertices from the adjacent triangles that are opposite to edges of the current triangle.

There are a few additional minor changes to the Mesh class. I suggest you compare it to the version from the previous tutorial to make sure you capture all differences. One of the notable changes is that we use GL_TRIANGLES_ADJACENCY instead of GL_TRIANGLES as the topology when calling glDrawElementsBaseVertex(). If you forget that the GL will feed incorrectly sized primitives into the GS.

(silhouette.vs)

#version 330

layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
layout (location = 2) in vec3 Normal;

out vec3 WorldPos0;

uniform mat4 gWVP;
uniform mat4 gWorld;

void main()
{
    vec4 PosL = vec4(Position, 1.0);
    gl_Position = gWVP * PosL;
    WorldPos0 = (gWorld * PosL).xyz;
}

In today's demo we are going to detect the silhouette of an object and mark it by a thick red line. The object itself will be drawn using our standard forward rendering lighting shader and the silhouette will be drawn using a dedicated shader. The code above belongs to the VS of that shader. There is nothing special about it. We just need to transform the position into clip space using the WVP matrix and provide the GS with the vertices in world space (since the silhouette algorithm takes place in world space).

(silhouette.gs)

#version 330

layout (triangles_adjacency) in;
layout (line_strip, max_vertices = 6) out;

in vec3 WorldPos0[];

void EmitLine(int StartIndex, int EndIndex)
{
    gl_Position = gl_in[StartIndex].gl_Position;
    EmitVertex();

    gl_Position = gl_in[EndIndex].gl_Position;
    EmitVertex();

    EndPrimitive();
}

uniform vec3 gLightPos;

void main()
{
    vec3 e1 = WorldPos0[2] - WorldPos0[0];
    vec3 e2 = WorldPos0[4] - WorldPos0[0];
    vec3 e3 = WorldPos0[1] - WorldPos0[0];
    vec3 e4 = WorldPos0[3] - WorldPos0[2];
    vec3 e5 = WorldPos0[4] - WorldPos0[2];
    vec3 e6 = WorldPos0[5] - WorldPos0[0];

    vec3 Normal = cross(e1,e2);
    vec3 LightDir = gLightPos - WorldPos0[0];

    if (dot(Normal, LightDir) > 0.00001) {

        Normal = cross(e3,e1);

        if (dot(Normal, LightDir) <= 0) {
            EmitLine(0, 2);
        }

        Normal = cross(e4,e5);
        LightDir = gLightPos - WorldPos0[2];

        if (dot(Normal, LightDir) <=0) {
            EmitLine(2, 4);
        }

        Normal = cross(e2,e6);
        LightDir = gLightPos - WorldPos0[4];

        if (dot(Normal, LightDir) <= 0) {
            EmitLine(4, 0);
        }
    }
}

All the silhouette logic is contained within the GS. When using the triangle list with adjacencies topology the GS receives an array of six vertices. We start by calculating a few selected edges that will help us calculate the normal of the current triangle as well as the three adjacent triangles. Use the picture above to understand how to map e1-e6 to actual edges. Then we check whether the triangle faces the light by calculating a dot product between its normal and the light direction (with the light vector going towards the light). If the result of the dot product is positive the answer is yes (we use a small epsilon due to floating point inaccuracies). If the triangle does not face the light then this is the end of the way for it, but if it is light facing, we do the same dot product operation between the light vector and every one of the three adjacent triangles. If we hit an adjacent triangle that doesn't face the light we call the EmitLine() function which (unsurprisingly) emits the shared edge between the triangle (which faces the light) and its neighbor (which does not). The FS simply draws that edge in red.

(tutorial39.cpp:183)

void RenderScene()
{
    // Render the object as-is
    m_LightingTech.Enable();

    Pipeline p;
    p.SetPerspectiveProj(m_persProjInfo);
    p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());
    p.WorldPos(m_boxPos);
    m_LightingTech.SetWorldMatrix(p.GetWorldTrans());
    m_LightingTech.SetWVP(p.GetWVPTrans());
    m_mesh.Render();

    // Render the object's silhouette
    m_silhouetteTech.Enable();

    m_silhouetteTech.SetWorldMatrix(p.GetWorldTrans());
    m_silhouetteTech.SetWVP(p.GetWVPTrans());
    m_silhouetteTech.SetLightPos(Vector3f(0.0f, 10.0f, 0.0f));

    glLineWidth(5.0f);

    m_mesh.Render();
}

This is how we use the silhouette technique. The same object is rendered twice. First with the standard lighting shader. Then with the silhouette shader. Note how the function glLightWidth() is used to make the silhouette thicker and thus more noticeable.

If you use the code above as-is to create the demo, you might notice a minor corruption around the silhouette lines. The reason is that the second render generates a line with roughly the same depth as the original mesh edge. This causes a phenomenon known as Z fighting as pixels from the silhouette and the original mesh cover each other in an inconsistent way (again, due to floating point accuracies). To fix this we call glDepthFunc(GL_LEQUAL) which relaxes the depth test a bit. It means that if a second pixel is rendered on top of a previous pixel with the same depth the last pixel always take precedence.

Next tutorial


Tutorial 40 - Stencil Shadow Volume

Tutorial 40:

Stencil Shadow Volume

Background

In tutorials 23 & 24 we studied the shadow map technique which is a relatively simple way to get shadows into your 3D world. Shadow maps are in a disadvantage when trying to generate a shadow for a point light source. You need a direction vector in order to generate the shadow map and since a point light casts its light all over the place it is difficult to get such a vector. While there are methods to overcome this, they are a bit complex and make the shadow map technique more suitable for spot lights. The Stencil Shadow Volume is an interesting technique that provides a straightforward solution to the problem of point lights. This technique was discovered by William Bilodeau and Michael Songy in 1998 and was popularized by John Carmack in his Doom 3 engine (2002).

If you've followed the tutorials thus far you've actually seen a variation of this technique in our mini series of tutorials on Deferred Shading. With deferred shading we needed a way to block the light influence and we've used a light volume for that purpose. We processed lighting only on stuff within the light volume. Now we are going to do the opposite. We will create a shadow volume and process lighting only on stuff outside of it. Same as in light volume we will use the stencil buffer as a key component of the algorithm. Hence the name - Stencil Shadow Volume.

The idea behind the shadow volume algorithm is to extend the silhouette of an object which is created when light falls upon it into a volume and then render that volume into the stencil buffer using a couple of simple stencil operations. The key idea is that when an object is inside the volume (and therefore in shadow) the front polygons of the volume win the depth test against the polygons of the object and the back polygons of the volume fail the same test.

We are going to setup the stencil operation according to a method known as Depth Fail. People often start the description of the shadow volume technique using a more straighforward method called Depth Pass, however, that method has a known problem when the viewer itself is inside the shadow volume and Depth Fail fixes that problem. Therefore, I've skipped Depth Pass altogether and went directly to Depth Fail. Take a look at the following picture:

We have a light bulb at the bottom left corner and a green object (called an occluder) which casts shadow due to that light. Three round objects are rendered in this scene as well. Object B is shadowed while A & C are not. The red arrows bound the area of the shadow volume (the dashed part of the line is not part of it).

Let's see how we can utilize the stencil buffer to get shadows working here. We start by rendering the actual objects (A, B, C and the green box) into the depth buffer. When we are done we have the depth of the closest pixels available to us. Then we go over the objects in the scene one by one and create a shadow volume for each one. The example here shows only the shadow volume of the green box but in a complete application we would also create volumes for the round objects because they cast shadows of their own. The shadow volume is created by detecting its silhouette (make sure you fully understand tutorial 39 before starting this one) and extending it into infinity. We render that volume into the stencil buffer using the following simple rules:

  • If the depth test fails when rendering the back facing polygons of the shadow volume we increment the value in the stencil buffer.
  • If the depth test fails when rendering the front facing polygons of the shadow volume we decrement the value in the stencil buffer.
  • We do nothing in the following cases: depth test pass, stencil test fails.

Let's see what happens to the stencil buffer using the above scheme. The front and back facing triangles of the volume that are covered by object A fail the depth test. We increment and decrement the values of the pixels covered by object A in the stencil buffer which means they are left at zero. In the case of object B the front facing triangles of the volume win the depth test while the back facing ones fails. Therefore, we only increment the stencil value. The volume triangles (front and back facing) that cover object C win the depth test. Therefore, the stencil value is not updated and remains at zero.

Note that up till now we haven't touched the color buffer. When we complete all of the above we render all objects once again using the standard lighting shader but this time we set the stencil test such that only pixels whose stencil value is zero will be rendered. This means that only objects A & C will make it to the screen.

Here's a more complex scene that includes two occluders:

To make it simpler to detect the shadow volume of the second occluder it is marked by thinner red arrows. You can follow the changes to the stencil buffer (marked by +1 and -1) and see that the algorithm works fine in this case as well. The change from the previous picture is that now A is also in shadow.

Let's see how to put that knowledge into practice. As we said earlier, we need to render a volume which is created when we extend the silhouette of an occluder. We can start with the code from the previous tutorial which detects the silhouette. All we need to do is to extend the silhouette edges into a volume. This is done by emitting a quad (or actually, four vertices in triangle strip topology) from the GS for each silhouette edge. The first two vertices come from the silhouette edge and the other two vertices are generated when we extend the edge vertices into infinity along the vector from the light position to the vertices. By extending into infinity we make sure the volume captures everything which lies in the path of the shadow. This quad is depicted in the following picture:

When we repeat this process of emitting quads from all silhouette edges a volume is created. Is that enough? definitely not. The problem is that this volume looks kind of like a truncated cone without its caps. Since our algorithm depends on checking the depth test of the front and back triangles of the volume we might end up with a case where the vector from the eye to the pixel goes through only either the front or back of the volume:

The solution to this problem is to generate a volume which is closed on both sides. This is done by creating a front and a back cap to the volume (the dotted lines in the picture above). Creating the front cap is very easy. Every triangle which faces the light becomes part of the front cap. While this may not be the most efficient solution and you could probably create a front cap using fewer triangles it is definitely the simplest solution. The back cap is almost as simple. We just need to extend the vertices of light facing triangle to infinity (along the vector from the light to each vertex) and reverse their order (else the resulting triangle will point inside the volume).

The word 'infinity' has been mentioned here a few times and we now need to define exactly what this means. Take a look at the following picture:

What we see is a picture of the frustum taken from above. The light bulb emits a ray which goes through point 'p' and continues to infinity. In other words, 'p' is extended to infinity. Obviously, at infinity the position of point p is simply (infinity, infinity, infinity), but we don't care about that. We need to find a way to rasterize the triangles of the shadow volume which means we must project its vertices on the projection plane. This projection plane is in fact the near plane. While 'p' is extended to infinity along the light vector we can still project it back on the near plane. This is done by the dotted line that goes from the origin and crosses the light vector somewhere. We want to find 'Xp' which is the X value of the point where that vector crosses the near plane.

Let's describe any point on the light vector as 'p + vt' where 'v' is the vector from the light source to point 'p' and 't' is a scalar which goes from 0 towards infinity. From the above picture and due to triangle similarities we can say that:

Where 'n' is the Z value of the near plane. As 't' goes to infinity we are left with:

So this is how we find the projection of 'p' at infinity on the near plane. Now here's a bit of magic - turns out that to calculate Xp and Yp according to the above we just need to multiply the vector (Vx, Vy, Vz, 0) (where 'V' is the vector from the light source to point 'p') by the view/projection matrix and apply perspective divide on it. We are not going to prove it here by you can try this yourself and see the result. So the bottom line is that whenever we need to rasterize a triangle that contains a vertex which was extended to infinity along some vector we simply multiply that vector by the view/projection matrix while adding a 'w' component with the value of zero to it. We will use that technique extensively in the GS below.

Source walkthru

(glut_backend.cpp:171)

glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH|GLUT_STENCIL);

Before you start working on this tutorial make sure you initialize FreeGLUT per the code in bold face above. Without it the framebuffer will be created without a stencil buffer and nothing will work. I wasted some time before realizing this was missing so make sure you add this.

(tutorial40.cpp:139)

virtual void RenderSceneCB()
{
    CalcFPS();

    m_scale += 0.1f;

    m_pGameCamera->OnRender();

    glDepthMask(GL_TRUE);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    RenderSceneIntoDepth();

    glEnable(GL_STENCIL_TEST);

    RenderShadowVolIntoStencil();

    RenderShadowedScene();

    glDisable(GL_STENCIL_TEST);

    RenderAmbientLight();

    RenderFPS();

    glutSwapBuffers();
}

The main render loop function executes the three stages of the algorithm. First we render the entire scene into the depth buffer (without touching the color buffer). Then we render the shadow volume into the stencil buffer while setting up the stencil test as described in the background session. And finally the scene itself is rendered while taking into account the values in the stencil buffer (i.e. only those pixels whose stencil value is zero are rendered).

An important difference between this method and shadow map is that shadowed pixels in the stencil shadow volume method never reach the fragment shader. When we were using shadow map we had the opportunity to calculate ambient lighting on shadowed pixels. We don't have that opportunity here. Therefore, we add an ambient pass outside the stencil test.

Note that we enable writing to the depth buffer before the call to glClear. Without it the depth buffer will not be cleared (because we play with the mask later on).

(tutorial40.cpp:198)

void RenderSceneIntoDepth()
{
    glDrawBuffer(GL_NONE);

    m_nullTech.Enable();

    Pipeline p;

    p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());
    p.SetPerspectiveProj(m_persProjInfo);

    m_boxOrientation.m_rotation = Vector3f(0, m_scale, 0);
    p.Orient(m_boxOrientation);
    m_nullTech.SetWVP(p.GetWVPTrans());
    m_box.Render();

    p.Orient(m_quadOrientation);
    m_nullTech.SetWVP(p.GetWVPTrans());
    m_quad.Render();
}

Here we render the entire scene into the depth buffer, while disabling writes to the color buffer. We have to do this because in the next step we render the shadow volume and we need the depth fail algorithm to be performed correctly. If the depth buffer is only partially updated we will get incorrect results.

(tutorial40.cpp:219)

void RenderShadowVolIntoStencil()
{
    glDepthMask(GL_FALSE);
    glEnable(GL_DEPTH_CLAMP);
    glDisable(GL_CULL_FACE);

    // We need the stencil test to be enabled but we want it
    // to succeed always. Only the depth test matters.
    glStencilFunc(GL_ALWAYS, 0, 0xff);

    // Set the stencil test per the depth fail algorithm
    glStencilOpSeparate(GL_BACK, GL_KEEP, GL_INCR_WRAP, GL_KEEP);
    glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_DECR_WRAP, GL_KEEP);

    m_ShadowVolTech.Enable();

    m_ShadowVolTech.SetLightPos(m_pointLight.Position);

    // Render the occluder
    Pipeline p;
    p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());
    p.SetPerspectiveProj(m_persProjInfo);
    m_boxOrientation.m_rotation = Vector3f(0, m_scale, 0);
    p.Orient(m_boxOrientation);
    m_ShadowVolTech.SetVP(p.GetVPTrans());
    m_ShadowVolTech.SetWorldMatrix(p.GetWorldTrans());
    m_box.Render();

    // Restore local stuff
    glDisable(GL_DEPTH_CLAMP);
    glEnable(GL_CULL_FACE);
}

This is where things become interesting. We use a special technique which is based on the silhouette technique from the previous tutorial. It generates the volume (and its caps) from the silhouette of the occluder. First we disable writes to the depth buffer (writes to the color are already disabled from the previous step). We are only going to update the stencil buffer. We enable depth clamp which will cause our projected-to-infinity-vertices (from the far cap) to be clamped to the maximum depth value. Otherwise, the far cap will simply be clipped away. We also disable back face culling because our algorithm depends on rendering all the triangles of the volume. Then we set the stencil test (which has been enabled in the main render function) to always succeed and we set the stencil operations for the front and back faces according to the depth fail algorithm. After that we simply set everything the shader needs and render the occluder.

(tutorial40.cpp:250)

void RenderShadowedScene()
{
    glDrawBuffer(GL_BACK);

    // Draw only if the corresponding stencil value is zero
    glStencilFunc(GL_EQUAL, 0x0, 0xFF);

    // prevent update to the stencil buffer
    glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_KEEP);

    m_LightingTech.Enable();

    m_pointLight.AmbientIntensity = 0.0f;
    m_pointLight.DiffuseIntensity = 0.8f;

    m_LightingTech.SetPointLights(1, &m_pointLight);

    Pipeline p;
    p.SetPerspectiveProj(m_persProjInfo);
    p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());

    m_boxOrientation.m_rotation = Vector3f(0, m_scale, 0);
    p.Orient(m_boxOrientation);
    m_LightingTech.SetWVP(p.GetWVPTrans());
    m_LightingTech.SetWorldMatrix(p.GetWorldTrans());
    m_box.Render();

    p.Orient(m_quadOrientation);
    m_LightingTech.SetWVP(p.GetWVPTrans());
    m_LightingTech.SetWorldMatrix(p.GetWorldTrans());
    m_pGroundTex->Bind(COLOR_TEXTURE_UNIT);
    m_quad.Render();
}

We can now put the updated stencil buffer into use. Based on our algorithm we set rendering to succeed only when the stencil value of the pixel is exactly zero. In addition, we also prevent updates to the stencil buffer by setting the stencil test action to GL_KEEP. And that's it! We can now use the standard lighting shader to render the scene. Just remember to enable writing into the color buffer before you start...

(tutorial40.cpp:285)

void RenderAmbientLight()
{
    glEnable(GL_BLEND);
    glBlendEquation(GL_FUNC_ADD);
    glBlendFunc(GL_ONE, GL_ONE);

    m_LightingTech.Enable();

    m_pointLight.AmbientIntensity = 0.2f;
    m_pointLight.DiffuseIntensity = 0.0f;

    m_LightingTech.SetPointLights(1, &m_pointLight);

    m_pGroundTex->Bind(GL_TEXTURE0);

    Pipeline p;
    p.SetPerspectiveProj(m_persProjInfo);
    p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());

    m_boxOrientation.m_rotation = Vector3f(0, m_scale, 0);
    p.Orient(m_boxOrientation);
    m_LightingTech.SetWVP(p.GetWVPTrans());
    m_LightingTech.SetWorldMatrix(p.GetWorldTrans());
    m_box.Render();

    p.Orient(m_quadOrientation);
    m_LightingTech.SetWVP(p.GetWVPTrans());
    m_LightingTech.SetWorldMatrix(p.GetWorldTrans());
    m_pGroundTex->Bind(COLOR_TEXTURE_UNIT);
    m_quad.Render();

    glDisable(GL_BLEND);
}

The ambient pass helps us avoid completely black pixels that were dropped by the stencil test. In real life we usually don't see such extreme shadows so we add a bit of ambient light to all pixels. This is done by simply doing another lighting pass outside the boundaries of the stencil test. Couple of things to note here: we zero out the diffuse intensity (because that one is affected by the shadow) and we enable blending (to merge the results of the previous pass with this one). Now let's take a look at the shaders of the shadow volume technique.

(shadow_volume.vs)

#version 330

layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
layout (location = 2) in vec3 Normal;

out vec3 PosL;

void main()
{
    PosL = Position;
}

In the VS we simply forward the position as-is (in local space). The entire algorithm is implemented in the GS.

(shadow_volume.gs)

#version 330

layout (triangles_adjacency) in; // six vertices in
layout (triangle_strip, max_vertices = 18) out;

in vec3 PosL[]; // an array of 6 vertices (triangle with adjacency)

uniform vec3 gLightPos;
uniform mat4 gWVP;

float EPSILON = 0.0001;

// Emit a quad using a triangle strip
void EmitQuad(vec3 StartVertex, vec3 EndVertex)
{
    // Vertex #1: the starting vertex (just a tiny bit below the original edge)
    vec3 LightDir = normalize(StartVertex - gLightPos);
    gl_Position = gWVP * vec4((StartVertex + LightDir * EPSILON), 1.0);
    EmitVertex();

    // Vertex #2: the starting vertex projected to infinity
    gl_Position = gWVP * vec4(LightDir, 0.0);
    EmitVertex();

    // Vertex #3: the ending vertex (just a tiny bit below the original edge)
    LightDir = normalize(EndVertex - gLightPos);
    gl_Position = gWVP * vec4((EndVertex + LightDir * EPSILON), 1.0);
    EmitVertex();

    // Vertex #4: the ending vertex projected to infinity
    gl_Position = gWVP * vec4(LightDir , 0.0);
    EmitVertex();

    EndPrimitive();
}

void main()
{
    vec3 e1 = WorldPos[2] - WorldPos[0];
    vec3 e2 = WorldPos[4] - WorldPos[0];
    vec3 e3 = WorldPos[1] - WorldPos[0];
    vec3 e4 = WorldPos[3] - WorldPos[2];
    vec3 e5 = WorldPos[4] - WorldPos[2];
    vec3 e6 = WorldPos[5] - WorldPos[0];

    vec3 Normal = cross(e1,e2);
    vec3 LightDir = gLightPos - WorldPos[0];

    // Handle only light facing triangles
    if (dot(Normal, LightDir) > 0) {

        Normal = cross(e3,e1);

        if (dot(Normal, LightDir) <= 0) {
            vec3 StartVertex = WorldPos[0];
            vec3 EndVertex = WorldPos[2];
            EmitQuad(StartVertex, EndVertex);
        }

        Normal = cross(e4,e5);
        LightDir = gLightPos - WorldPos[2];

        if (dot(Normal, LightDir) <= 0) {
            vec3 StartVertex = WorldPos[2];
            vec3 EndVertex = WorldPos[4];
            EmitQuad(StartVertex, EndVertex);
        }

        Normal = cross(e2,e6);
        LightDir = gLightPos - WorldPos[4];

        if (dot(Normal, LightDir) <= 0) {
            vec3 StartVertex = WorldPos[4];
            vec3 EndVertex = WorldPos[0];
            EmitQuad(StartVertex, EndVertex);
        }

        // render the front cap
        LightDir = (normalize(PosL[0] - gLightPos));
        gl_Position = gWVP * vec4((PosL[0] + LightDir * EPSILON), 1.0);
        EmitVertex();

        LightDir = (normalize(PosL[2] - gLightPos));
        gl_Position = gWVP * vec4((PosL[2] + LightDir * EPSILON), 1.0);
        EmitVertex();

        LightDir = (normalize(PosL[4] - gLightPos));
        gl_Position = gWVP * vec4((PosL[4] + LightDir * EPSILON), 1.0);
        EmitVertex();
        EndPrimitive();

        // render the back cap
        LightDir = PosL[0] - gLightPos;
        gl_Position = gWVP * vec4(LightDir, 0.0);
        EmitVertex();

        LightDir = PosL[4] - gLightPos;
        gl_Position = gWVP * vec4(LightDir, 0.0);
        EmitVertex();

        LightDir = PosL[2] - gLightPos;
        gl_Position = gWVP * vec4(LightDir, 0.0);
        EmitVertex();
    }
}

The GS starts in pretty much the same way as the silhouette shader in the sense that we only care about triangles that are light facing. When we detect a silhouette edge we extend a quad from it towards infinity (see below). Remember that the indices of the vertices of the original triangles are 0, 2 and 4 and the adjacent vertices are 1, 3, 5 (see picture in the previous tutorial). After we take care of the quads we emit the front and back caps. Note that for the front cap we don't use the original triangle as-is. Instead, we move it along the light vector by a very small amount (we do it by normalizing the light vector and multiplying it by a small epsilon). The reason is that due to floating point errors we might encounter bizarre corruptions where the volume hides the front cap. Moving the cap away from the volume by just a bit works around this problem.

For the back cap we simply project the original vertices into infinity along the light vector and emit them in reversed order.

In order to emit a quad from an edge we project both vertices to infinity along the light direction and generate a triangle strip. Note that the original vertices are moved along the light vector by a very small amount, to match the front cap.

It is critical that we set the maximum output vertices from the GS correctly (see 'max_vertices' above). We have 3 vertices for the front cap, 3 for the back cap and 4 for each silhouette edge. When I was working on this tutorial I accidently set this value to 10 and got very strange corruptions. Make sure you don't make the same mistake...

Next tutorial


Tutorial 41 - Object Motion Blur

Tutorial 41:

Object Motion Blur

Background

Motion Blur is a very popular technique in fast pace 3D games whose purpose is to add a blurring effect to moving objects. This enhances the sense of realism experienced by the player. Motion Blur can be accomplished in various ways. There is a camera based Motion Blur which focuses on camera movement and there is an object based Motion Blur. In this tutorial we will study one of the options to accomplish the later.

The principle behind Motion Blur is that we can calculate the vector of movement (a.k.a Motion Vector) for each rendered pixel between two frames. By sampling along that vector from the current color buffer and averaging the result we get pixels that represent the movement of the underlying objects. This is really all there is to it. Let's take it to the next level of details. The following is a summary of the required steps and after that we will review the actual code.

  1. The technique is split into two passes - a render pass and then a motion blur pass.
  2. In the render pass we render into two buffers - the regular color buffer and a motion vector buffer. The color buffer contains the original image as if it was rendered without motion blur. The motion vector blur contains a vector for each pixel which represents its movement along the screen between the previous frame and the current.
  3. The motion vector is calculated by supplying the WVP matrix of the previous frame to the VS. We transform the local space position of each vertex using the current WVP and the previous one to clip space and pass both results to the FS. We get the interpolated clip space positions in the FS and transform them to NDC by dividing them by their respective W coordinate. This completes their projection to the screen so now we can substract the previous position from the current and get a motion vector. The motion vector is written out to a texture.
  4. The motion blur pass is implemented by rendering a full screen quad. We sample the motion vector for each pixel in the FS and then we sample from the color buffer along that vector (starting from the current pixel).
  5. We sum up the results of each sample operation while giving the highest weight to the current pixel and the lowest weight to the one which is the most distant on the motion vector (this is what we do in this tutorial, but there are many other options here).
  6. This averaging of sample results along the motion vector creates the sense of bluriness. Obviously, pixels that didn't move between two frames will look the same, which is fine.

This tutorial is based on the Skeletal Animation tutorial (#38). We will review here the changes that add the motion blur to that tutorial.

Source walkthru

(tutorial41.cpp:157)

virtual void RenderSceneCB()
{
    CalcFPS();

    m_pGameCamera->OnRender();

    RenderPass();

    MotionBlurPass();

    RenderFPS();

    glutSwapBuffers();
}

This is the main render function and it is very simple. We have a render pass for all the objects in the scene and then a post processing pass for the motion blur.

(tutorial41.cpp:172)

void RenderPass()
{
    m_intermediateBuffer.BindForWriting();

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    m_pSkinningTech->Enable();

    vector Transforms;

    float RunningTime = (float)((double)GetCurrentTimeMillis() - (double)m_startTime) / 1000.0f;

    m_mesh.BoneTransform(RunningTime, Transforms);

    for (uint i = 0 ; i < Transforms.size() ; i++) {
        m_pSkinningTech->SetBoneTransform(i, Transforms[i]);
        m_pSkinningTech->SetPrevBoneTransform(i, m_prevTransforms[i]);
    }

    m_pSkinningTech->SetEyeWorldPos(m_pGameCamera->GetPos());

    m_pipeline.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());
    m_pipeline.SetPerspectiveProj(m_persProjInfo);
    m_pipeline.Scale(0.1f, 0.1f, 0.1f);

    Vector3f Pos(m_position);
    m_pipeline.WorldPos(Pos);
    m_pipeline.Rotate(270.0f, 180.0f, 0.0f);
    m_pSkinningTech->SetWVP(m_pipeline.GetWVPTrans());
    m_pSkinningTech->SetWorldMatrix(m_pipeline.GetWorldTrans());

    m_mesh.Render();

    m_prevTransforms = Transforms;
}

This is our render pass. It is almost identical to the one from the Skeletal Animation tutorial with changes marked in bold face. The intermediate buffer is a simple class that combines the color, depth, and motion vector buffers under a single frame buffer object. We've seen this already when we studied deferred rendering (tutorials 35-37) so I'm not going to review it here. Check the attached sources. The basic idea is to render into a FBO and not directly to the screen. In the motion blur pass we will read from the intermediate buffer.

Other than that you can see that we've added a class member to the 'Tutorial41' class that keeps the vector of bone transformations from the previous frame. We feed it into the skinning technique along with the current bone transformations. We will see how this is used when we review the GLSL code of the technique.

(tutorial41.cpp:209)

void MotionBlurPass()
{
    m_intermediateBuffer.BindForReading();

    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    m_pMotionBlurTech->Enable();

    m_quad.Render();
}

In the motion blur pass we bind the intermediate buffer for reading (which means that the rendering output goes to the screen) and render a full screen quad. Each screen pixel will be processed once and the effect of motion blur will be calculated.

(skinning.vs)

#version 330

layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
layout (location = 2) in vec3 Normal;
layout (location = 3) in ivec4 BoneIDs;
layout (location = 4) in vec4 Weights;

out vec2 TexCoord0;
out vec3 Normal0;
out vec3 WorldPos0;
out vec4 ClipSpacePos0;
out vec4 PrevClipSpacePos0;

const int MAX_BONES = 100;

uniform mat4 gWVP;
uniform mat4 gWorld;
uniform mat4 gBones[MAX_BONES];
uniform mat4 gPrevBones[MAX_BONES];

void main()
{
    mat4 BoneTransform = gBones[BoneIDs[0]] * Weights[0];
    BoneTransform += gBones[BoneIDs[1]] * Weights[1];
    BoneTransform += gBones[BoneIDs[2]] * Weights[2];
    BoneTransform += gBones[BoneIDs[3]] * Weights[3];

    vec4 PosL = BoneTransform * vec4(Position, 1.0);
    vec4 ClipSpacePos = gWVP * PosL;
    gl_Position = ClipSpacePos;
    TexCoord0 = TexCoord;
    vec4 NormalL = BoneTransform * vec4(Normal, 0.0);
    Normal0 = (gWorld * NormalL).xyz;
    WorldPos0 = (gWorld * PosL).xyz;

    mat4 PrevBoneTransform = gPrevBones[BoneIDs[0]] * Weights[0];
    PrevBoneTransform += gPrevBones[BoneIDs[1]] * Weights[1];
    PrevBoneTransform += gPrevBones[BoneIDs[2]] * Weights[2];
    PrevBoneTransform += gPrevBones[BoneIDs[3]] * Weights[3];

    ClipSpacePos0 = ClipSpacePos;
    vec4 PrevPosL = PrevBoneTransform * vec4(Position, 1.0);
    PrevClipSpacePos0 = gWVP * PrevPosL;
}

Above we see the changes to the VS of the skinning technique. We've added a uniform array which contains the bone transformations from the previous frame and we use it to calculate the clip space position of the current vertex in the previous frame. This position, along with the clip space position of the current vertex in the current frame are forwarded to the FS.

(skinning.fs:123)

layout (location = 0) out vec3 FragColor;
layout (location = 1) out vec2 MotionVector;

void main()
{
    VSOutput In;
    In.TexCoord = TexCoord0;
    In.Normal = normalize(Normal0);
    In.WorldPos = WorldPos0;

    vec4 TotalLight = CalcDirectionalLight(In);

    for (int i = 0 ; i < gNumPointLights ; i++) {
        TotalLight += CalcPointLight(gPointLights[i], In);
    }

    for (int i = 0 ; i < gNumSpotLights ; i++) {
        TotalLight += CalcSpotLight(gSpotLights[i], In);
    }

    vec4 Color = texture(gColorMap, TexCoord0) * TotalLight;
    FragColor = Color.xyz;
    vec3 NDCPos = (ClipSpacePos0 / ClipSpacePos0.w).xyz;
    vec3 PrevNDCPos = (PrevClipSpacePos0 / PrevClipSpacePos0.w).xyz;
    MotionVector = (NDCPos - PrevNDCPos).xy;
}

The FS of the skinning technique has been updated to output two vectors into two separate buffers (the color and the motion vector buffers). The color is calculated as usual. To calculate the motion vector we project the clip space positions of the current and previous frame by doing perspective divide on both and substract one from the other.

Note that the motion vector is just a 2D vector. This is because it "lives" only on the screen. The corresponding motion buffer is created with the type GL_RG to match.

(motion_blur.vs)

#version 330

layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;

out vec2 TexCoord0;

void main()
{
    gl_Position = vec4(Position, 1.0);
    TexCoord0 = TexCoord;
}

This is the VS of the motion blur technique. We simply pass along the position and texture coordinate of each vertex of the full screen quad.

(motion_blur.fs)

#version 330

in vec2 TexCoord0;

uniform sampler2D gColorTexture;
uniform sampler2D gMotionTexture;

out vec4 FragColor;

void main()
{
    vec2 MotionVector = texture(gMotionTexture, TexCoord0).xy / 2.0;

    vec4 Color = vec4(0.0);

    vec2 TexCoord = TexCoord0;

    Color += texture(gColorTexture, TexCoord) * 0.4;
    TexCoord -= MotionVector;
    Color += texture(gColorTexture, TexCoord) * 0.3;
    TexCoord -= MotionVector;
    Color += texture(gColorTexture, TexCoord) * 0.2;
    TexCoord -= MotionVector;
    Color += texture(gColorTexture, TexCoord) * 0.1;

    FragColor = Color;
}

This is where all the motion blur fun takes place. We sample the motion vector of the current pixel and use it to sample four texels from the color buffer. The color of the current pixel is sampled using the original texture coordinates and we give it the highest weight (0.4). We then move the texture coordinate backward along the motion vector and sample three more color texels. We combine them together while giving smaller and smaller weights as we move along.

You can see that I divided the original motion vector by two. You will probably need some fine tuning here as well as with the weights to get the best result for your scene. Have fun.

Here's an example of a possible output:

Next tutorial


Tutorial 42 - Percentage Closer Filtering

Tutorial 42:

Percentage Closer Filtering

Background

In tutorial 24 we saw how to implement shadows using a technique called Shadow Mapping. The shadows that result from Shadow Mapping aren't that great and there is quite a lot of aliasing there, as you can see in the following picture:

This tutorial describes a method (one of many) to reduce that problem. It is called Percentage Closer Filtering, or PCF. The idea is to sample from the shadow map around the current pixel and compare its depth to all the samples. By averaging out the results we get a smoother line between light and shadow. For example, take a look at the following shadow map:

Each cell contains the depth value for each pixel (when viewed from the light source). To make life simple, let's say that the depth of all the pixels above is 0.5 (when viewed from the camera point of view). According to the method from tutorial 24 all the pixels whose shadow map value is small than 0.5 will be in shadow while the ones whose shadow map value is greater than or equal to 0.5 will be in light. This will create a hard aliased line between light and shadow.

Now consider the following - the pixels that are nearest the border between light and shadow are surrounded by pixels who shadow map value is smaller than 0.5 as well as pixels whose shadow map value is greater than or equal to 0.5. If we sample these neighboring pixels and average out the results we will get a factor level that can help us smooth out the border between light and shadow. Ofcourse we don't know in advance what pixels are closest to that border so we simply do this sampling work for each pixel. This is basically the entire system. In this tutorial we will sample 9 pixels in a 3 by 3 kernel around each pixel and average out the result. This will be our shadow factor instead of the 0.5 or 1.0 which we have used as a factor in tutorial 24.

Let us now review the source code that implements PCF. We will do this by going over the changes made to the implementation of tutorial 24. You may want to do a short refresh on that tutorial to make things clearer here.

Source walkthru

(lighting.fs:64)

uniform sampler2DShadow gShadowMap;

#define EPSILON 0.00001

float CalcShadowFactor(vec4 LightSpacePos)
{
    vec3 ProjCoords = LightSpacePos.xyz / LightSpacePos.w;
    vec2 UVCoords;
    UVCoords.x = 0.5 * ProjCoords.x + 0.5;
    UVCoords.y = 0.5 * ProjCoords.y + 0.5;
    float z = 0.5 * ProjCoords.z + 0.5;

    float xOffset = 1.0/gMapSize.x;
    float yOffset = 1.0/gMapSize.y;

    float Factor = 0.0;

    for (int y = -1 ; y <= 1 ; y++) {
        for (int x = -1 ; x <= 1 ; x++) {
            vec2 Offsets = vec2(x * xOffset, y * yOffset);
            vec3 UVC = vec3(UVCoords + Offsets, z + EPSILON);
            Factor += texture(gShadowMap, UVC);
        }
    }

    return (0.5 + (Factor / 18.0));
}

This is the updated shadow factor calculation function. It starts out the same where we manually perform perspective divide on clip space coordinates from the light source point of view, followed by a transformation from the (-1,+1) range to (0,1). We now have coordinates that we can use to sample from the shadow map and a Z value to compare against the sample result. From here on things are going to roll a bit differently. We are going to sample a 3 by 3 kernel so we need 9 texture coordinates altogether. The coordinates must result in sampling texels that are on one texel intervals on the X and/or Y axis. Since UV texture coordinates run from 0 to 1 and map into the texel ranges (0, Width-1) and (0, Height-1), respectively, we divide 1 by the width and height of the texture. These values are stored in the gMapSize uniform vector (see sources for more details). This gives us the offset in the texture coordinates space between two neighboring texels.

Next we perform a nested for loop and calculate the offset vector for each of the 9 texels we are going to sample. The last couple of lines inside the loop may seem a bit odd. We sample from the shadow map using a vector with 3 components (UVC) instead of just 2. The last component contains the value which we used in tutorial 24 to manually compare against the value from the shadow map (the light source Z plus a small epsilon to avoid Z-fighting). The change here is that we are using a sampler2DShadow as the type of 'gShadowMap' instead of a sampler2D. When sampling from a shadow typed sampler (sampler1DShadow, sampler2DShadow, etc) the GPU performs a comparison between the texel value and a value that we supply as the last component of the texture coordinate vector (the second component for 1D, the third component for 2D, etc). We get a zero result if the comparison fails and one if the comparison succeeds. The type of comparison is configured using a GL API and not through GLSL. We will see this change later on. For now, just assume that we get a zero result for shadow and one for light. We accumulate the 9 results and divide them by 18. Thus we get a value between 0 and 0.5. We add it to a base of 0.5 and this is our shadow factor.

(shadow_map_fbo.cpp:39)

bool ShadowMapFBO::Init(unsigned int WindowWidth, unsigned int WindowHeight)
{
    // Create the FBO
    glGenFramebuffers(1, &m_fbo);

    // Create the depth buffer
    glGenTextures(1, &m_shadowMap);
    glBindTexture(GL_TEXTURE_2D, m_shadowMap);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, WindowWidth, WindowHeight, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_shadowMap, 0);

    // Disable writes to the color buffer
    glDrawBuffer(GL_NONE);

    // Disable reads from the color buffer
    glReadBuffer(GL_NONE);

    GLenum Status = glCheckFramebufferStatus(GL_FRAMEBUFFER);

    if (Status != GL_FRAMEBUFFER_COMPLETE) {
        printf("FB error, status: 0x%x\n", Status);
        return false;
    }

    return true;
}

This is how we configure our shadow map texture to work with the shadow sampler in the shader instead of the regular sampler. There are two new lines here and they are marked in bold face. First we set the texture compare mode to 'compare ref to texture'. The only other possible value for the third parameter here is GL_NONE which is the default and makes the sampler behave in the regular, non-shadow, form. The second call to glTexParameteri sets the comparison function to 'less than or equal'. This means that the result of the sample operation will be 1.0 if the reference value is less than or equal to the value in the texture and zero otherwise. You can also use GL_GEQUAL, GL_LESS, GL_GREATER, GL_EQUAL, GL_NOTEQUAL for similar types of comparisons. You get the idea. There are also GL_ALWAYS which always return 1.0 and GL_NEVER which always return 0.0.

(tutorial42.cpp:174)

void ShadowMapPass()
{
    glCullFace(GL_FRONT);
    ...
}

void RenderPass()
{
    glCullFace(GL_BACK);
    ...
}

The last point that I want to discuss is a minor change intended to avoid self shadowing. Self shadowing is a big problem when dealing with almost any shadowing technique and the reason is that the precision of the depth buffer is quite limited (even at 32 bits). The problem is specific to the polygons that are facing the light and are not in shadow. In the shadow map pass we render their depth into the shadow map and in the render pass we compare their depth against the value stored in the shadow map. Due to the depth precision problem we often get Z fighting which leads to some pixels being in shadow while others are in light. To reduce this problem we reverse culling so that we cull front facing polygons in the shadow map pass (and render only the back facing polygons into the shadow map). In the render pass we are back to the usual culling. Since real world occluders are generally closed volumes it is ok to use the back facing polygons for depth comparison and not the front facing ones. You should try to disable the code above and see the results for yourself.

After applying all the changes that we discussed the shadow looks like this:

Next tutorial


Tutorial 43 - Multipass Shadow Mapping With Point Lights

Tutorial 43:

Multipass Shadow Mapping With Point Lights

Background

In tutorial 24 we learned the basics of Shadow Mapping - first a rendering pass from the light point of view using the light direction as the viewing vector and then a second pass from the camera point of view using the data from the first pass for shadow calculation. At this point most programmers will ask themselves: this is fine for directional/spot light but what if I want to generate shadows from a point light? There is no specific light direction in this case. Solving this problem is the topic of this tutorial.

The solution to this problem is to recognize that a point light basically casts its light in all directions, so rather than place a rectangular shadow map texture somewhere that will only receive a small portion of that light, we can place the light source in a middle of a texture cube. We now have six rectangular shadow maps and the light has no where to escape. Every light "beam" has to land on one of these six shadow maps and we can sample from it to do our standard shadow calculations. We have already seen the cube map in action in the skybox tutorial so we are already familiar with it.

In practice, in order to simulate the notion of spreading light all over we will do six shadow map rendering passes from the location of the light source but each rendering pass will target a different direction. We are going to make this very simple and target the following axis aligned directions: positive/negative X, positive/negative Y and positive/negative Z. Eventually the cubemap faces will contain the distance of all pixels in the scene that are closest to the light source. By comparing this value to the distance of each pixel to the light during the lighting pass we can tell whether that pixel is in light or shadow.

Take a look at the following picture:

Our scene contains a blue sphere and a point light (the yellow light bulb) is stationed nearby. In the first rendering pass we use a texture cube as the framebuffer. Remember that at this stage we don't care about the original camera location or direction. We place the camera at the position of the point light so it always looks like it is located at the middle of the texture cube. In the example above we see that the current rendering direction is the positive Z axis (into the yellow face). At this point we are back to the standard shadow mapping process so using the depth values in the yellow face we can generate the proper shadow for the blue sphere (these depth values are located in the black circle but the actual shadow will be rendered in the second pass).

The following picture demonstrates the six camera directions that we will use in the first rendering pass:

Since the same scene is rendered six times in the first rendering pass we call this Multipass Shadow Mapping.

Source walkthru

(shadow_map_fbo.h)

class ShadowMapFBO
{
public:
    ShadowMapFBO();

    ~ShadowMapFBO();

    bool Init(unsigned int WindowWidth, unsigned int WindowHeight);

    void BindForWriting(GLenum CubeFace);

    void BindForReading(GLenum TextureUnit);

private:
    GLuint m_fbo;
    GLuint m_shadowMap;
    GLuint m_depth;
};

Let's start the code walkthru by reviewing the changes in our shadow map FBO. The FBO is mostly the same with two minor changes: the BindForWriting() method now takes a cube face enumerator. Since we are doing a multi pass rendering into the cubemap this is how we will tell the GL which cube face we are going to render. The second change is the addition of a separate depth buffer. Previously we used the m_shadowMap class member as the shadow map object (which is actually a depth buffer). Now m_shadowMap is going to be used as a cube map and we need a dedicated depth buffer. For each of the six passes into the cube map faces we will use this depth buffer (and naturally we will clear it before each pass).

(shadow_map_fbo.cpp:46)

bool ShadowMapFBO::Init(unsigned int WindowWidth, unsigned int WindowHeight)
{
    // Create the FBO
    glGenFramebuffers(1, &m_fbo);

    // Create the depth buffer
    glGenTextures(1, &m_depth);
    glBindTexture(GL_TEXTURE_2D, m_depth);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, WindowWidth, WindowHeight, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glBindTexture(GL_TEXTURE_2D, 0);

    // Create the cube map
    glGenTextures(1, &m_shadowMap);
    glBindTexture(GL_TEXTURE_CUBE_MAP, m_shadowMap);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

    for (uint i = 0 ; i < 6 ; i++) {
        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_R32F, WindowWidth, WindowHeight, 0, GL_RED, GL_FLOAT, NULL);
    }

    glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_depth, 0);

    // Disable writes to the color buffer
    glDrawBuffer(GL_NONE);

    // Disable reads from the color buffer
    glReadBuffer(GL_NONE);

    GLenum Status = glCheckFramebufferStatus(GL_FRAMEBUFFER);

    if (Status != GL_FRAMEBUFFER_COMPLETE) {
        printf("FB error, status: 0x%x\n", Status);
        return false;
    }

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    return GLCheckError();
}

This is how we initialize the shadow map. First we create and setup the depth buffer. Nothing new here. Next comes the cubemap texture. GL_TEXTURE_CUBE_MAP is used as the target. The interesting part here is the way we initialize the six cube faces. OpenGL provides a macro for each face: GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, etc. They happen to be defines sequentially which makes the loop above possible (see glew.h for the remaining macros; around line 1319 in the version I have). Each face is initialized with a single 32 bit floating point value in each texel.

(tutorial43.cpp:183)

virtual void RenderSceneCB()
{
  CalcFPS();

  m_scale += 0.05f;

  m_pGameCamera->OnRender();

  ShadowMapPass();
  RenderPass();

  RenderFPS();

  glutSwapBuffers();
}

This is the main render scene function and as you can see, there is no change in comparison to previous shadow mapping tutorials. At the high level we have the same two passes of shadow map generation and rendering.

(tutorial43.cpp:200)

void ShadowMapPass()
{
    glCullFace(GL_FRONT);

    m_shadowMapEffect.Enable();

    PersProjInfo ProjInfo;
    ProjInfo.FOV = 90.0f;
    ProjInfo.Height = WINDOW_HEIGHT;
    ProjInfo.Width = WINDOW_WIDTH;
    ProjInfo.zNear = 1.0f;
    ProjInfo.zFar = 100.0f;

    Pipeline p;
    p.SetPerspectiveProj(m_persProjInfo);

    glClearColor(FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX);

    for (uint i = 0 ; i < NUM_OF_LAYERS ; i++) {
        m_shadowMapFBO.BindForWriting(gCameraDirections[i].CubemapFace);
        glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

        p.SetCamera(m_pointLight.Position, gCameraDirections[i].Target, gCameraDirections[i].Up);

        p.Orient(m_mesh1Orientation);
        m_shadowMapEffect.SetWorld(p.GetWorldTrans());
        m_shadowMapEffect.SetWVP(p.GetWVPTrans());
        m_mesh.Render();

        p.Orient(m_mesh2Orientation);
        m_shadowMapEffect.SetWorld(p.GetWorldTrans());
        m_shadowMapEffect.SetWVP(p.GetWVPTrans());
        m_mesh.Render();
    }
}

This is the full shadow map pass. There are a few things we need to notice here that are different from regular shadow mapping. First off is that the field of view is set to 90 degrees. The reason is that we are going to render the entire world into the cube map so to align the camera perfectly into each face we set it to one quarter of a full circle (360 degrees).

Next is that the clear value of the cube map is set to the maximum value of the floating point (FLT_MAX). Every texel which will actually be rendered into will have a much smaller value. The "real" pixels will always have values smaller than the un-rendered texels.

Finally, the loop over the cube map faces uses the gCameraDirections array (see below) in order to set the proper face in the FBO and to orient the camera into that face.

(tutorial43.cpp:45)

struct CameraDirection
{
    GLenum CubemapFace;
    Vector3f Target;
    Vector3f Up;
};

CameraDirection gCameraDirections[NUM_OF_LAYERS] =
{
    { GL_TEXTURE_CUBE_MAP_POSITIVE_X, Vector3f(1.0f, 0.0f, 0.0f), Vector3f(0.0f, -1.0f, 0.0f) },
    { GL_TEXTURE_CUBE_MAP_NEGATIVE_X, Vector3f(-1.0f, 0.0f, 0.0f), Vector3f(0.0f, -1.0f, 0.0f) },
    { GL_TEXTURE_CUBE_MAP_POSITIVE_Y, Vector3f(0.0f, 1.0f, 0.0f), Vector3f(0.0f, 0.0f, -1.0f) },
    { GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, Vector3f(0.0f, -1.0f, 0.0f), Vector3f(0.0f, 0.0f, 1.0f) },
    { GL_TEXTURE_CUBE_MAP_POSITIVE_Z, Vector3f(0.0f, 0.0f, 1.0f), Vector3f(0.0f, -1.0f, 0.0f) },
    { GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, Vector3f(0.0f, 0.0f, -1.0f), Vector3f(0.0f, -1.0f, 0.0f) }
};

This array combines the enums defined by GL to denote each cube face along with the two vectors used to orient the camera toward that face.

(shadow_map_fbo.cpp:96)

void ShadowMapFBO::BindForWriting(GLenum CubeFace)
{
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, CubeFace, m_shadowMap, 0);
    glDrawBuffer(GL_COLOR_ATTACHMENT0);
}

The function above is used by the shadow map pass to setup the face that will be rendered to. First we bind the FBO to make it current. After that we bind the face to the first color attachment and enable writing to it.

(tutorial43.cpp:237)

void RenderPass()
{
    glCullFace(GL_BACK);

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    m_lightingEffect.Enable();
    m_shadowMapFBO.BindForReading(SHADOW_TEXTURE_UNIT);
    m_lightingEffect.SetEyeWorldPos(m_pGameCamera->GetPos());

    Pipeline p;
    p.SetPerspectiveProj(m_persProjInfo);
    p.SetCamera(*m_pGameCamera);

    // Render the quads
    m_pGroundTex->Bind(COLOR_TEXTURE_UNIT);
    p.Orient(m_quad1Orientation);
    m_lightingEffect.SetWorldMatrix(p.GetWorldTrans());
    m_lightingEffect.SetWVP(p.GetWVPTrans());
    m_quad.Render();

    p.Orient(m_quad2Orientation);
    m_lightingEffect.SetWorldMatrix(p.GetWorldTrans());
    m_lightingEffect.SetWVP(p.GetWVPTrans());
    m_quad.Render();

    // Render the meshes
    p.Orient(m_mesh1Orientation);
    m_lightingEffect.SetWorldMatrix(p.GetWorldTrans());
    m_lightingEffect.SetWVP(p.GetWVPTrans());
    m_mesh.Render();

    p.Orient(m_mesh2Orientation);
    m_lightingEffect.SetWorldMatrix(p.GetWorldTrans());
    m_lightingEffect.SetWVP(p.GetWVPTrans());
    m_mesh.Render();
}

This is the full lighting pass. Everything is back to normal - we render into the default framebuffer, we bind the cubemap for reading and reset the camera based on the viewer position. This completes our C++ code review. Now let's take a look at the shaders.

(shadow_map.vs)

#version 330

layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
layout (location = 2) in vec3 Normal;

uniform mat4 gWVP;
uniform mat4 gWorld;

out vec3 WorldPos;

void main()
{
    vec4 Pos4 = vec4(Position, 1.0);
    gl_Position = gWVP * Pos4;
    WorldPos = (gWorld * Pos4).xyz;
}

We are going to render from the position of the point light and the camera is currently aligned on one of the axis. The value that will be written into the cubemap is the distance from the object to the point light. So we need the object world position in the FS where this distance will be calculated.

(shadow_map.fs)

#version 330

in vec3 WorldPos;

uniform vec3 gLightWorldPos;

out float FragColor;

void main()
{
    vec3 LightToVertex = WorldPos - gLightWorldPos;

    float LightToPixelDistance = length(LightToVertex);

    FragColor = LightToPixelDistance;
}

We now have the world space position of the pixel in the FS and the world space position of the point light is provided as a uniform. We calculate the vector from the light to the pixel, take its length and write it out.

(lighting.vs)

#version 330

layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
layout (location = 2) in vec3 Normal;

out vec2 TexCoord0;
out vec3 Normal0;
out vec3 WorldPos0;

uniform mat4 gWVP;
uniform mat4 gWorld;

void main()
{
    gl_Position = gWVP * vec4(Position, 1.0);
    TexCoord0 = TexCoord;
    Normal0 = (gWorld * vec4(Normal, 0.0)).xyz;
    WorldPos0 = (gWorld * vec4(Position, 1.0)).xyz;
}

This is the updated lighting VS and what's interesting here is the missing piece - we no longer need to calculate the light space position of the vertex as we did in the original shadow mapping algorithm. This was required when we needed to place the shadow map along the light vector but now we only need the vector from the light to the pixel in world space in order to sample from the cubmap. Everything we need for this is here so we are good to go.

(lighting.fs)

...

uniform samplerCube gShadowMap;

...

float CalcShadowFactor(vec3 LightDirection)
{
    float SampledDistance = texture(gShadowMap, LightDirection).r;

    float Distance = length(LightDirection);

    if (Distance < SampledDistance + EPSILON)
        return 1.0; // Inside the light
    else
        return 0.5; // Inside the shadow
}

The code excerpt above contains the key changes in the lighting FS. The shadow map uniform that was previously sampler2D (in tutorial 24) or sampler2DShadow (in tutorial 42) is now a samplerCube. In order to sample from it we use the LightDirection vector which was calculated as the vector from the point light to the pixel. Note that all the three coordinates (X, Y and Z) of the light direction vector are used for sampling. Since the cube has three dimension we need a three dimension vector in order to select the proper face and the specific texel in that face. Comparison of the sampled value with the distance from the light to the pixel tells us whether we are in light or shadow.

In this tutorial example I've placed a couple of spheres facing a point light such that the shadow will fall directly on the quad behind each sphere. Here's the result:

Next tutorial


Tutorial 44 - GLFW

Tutorial 44:

GLFW

Background

In the first tutorial we learned that OpenGL doesn't deal directly with windowing and that this responsibility is left to other APIs (GLX, WGL, etc). To make life simpler for ourselves we used GLUT to handle the windowing API. This makes our tutorials portable between different OSs. We've been using GLUT exclusively, until today. We are now going to take a look at another popular library that handles the same stuff as GLUT. This library is called GLFW and is hosted at www.glfw.org. One of the main differences between the two libraries is that GLFW is more modern and is actively being developed while GLUT is, well, older and its development has mostly stopped. GLFW has many features and you can read all about them in its home page.

Since there is no mathematical background for this tutorial we can go right ahead and review the code. What I've done here is to abstract the contents of glut_backend.h and glut_backend.cpp behind a general "backend" API that wraps the details of setting up the window and handling the input from the mouse and keyboard. You can easily switch between a GLUT backend and a GLFW backend and this gives a very nice flexibility for future tutorials.

In order to install GLFW (run as root):

  • On Fedora Core: yum install glfw glfw-devel
  • On Ubuntu: apt-get install libglfw3 libglfw3-dev
  • Other Linux distributions also provide binary packages of GLFW. Alternatively, you can grab the sources directly from GLFW website and build them.

If you're using Windows simply use the GLFW headers and libraries that I provide as part of the source package. This tutorial should build out of the box (please let me know if it doesn't...).

In order to build stuff aginst the GLFW library you must tell the compiler where the headers and libraries are located. On Linux my recommendation is to use the pkg-config utility:

pkg-config --cflags --libs glfw3

The '--cflags' flag tells pkg-config to output the flags GCC needs to compile a file that uses GLFW. The '--libs' flags outputs the flags required for linking. I'm using these flags in the Netbeans project that I provide for Linux and you can use them in your own makefile. If you're using one of the build systems such as autotools, cmake or scons you will need to check that system documentation for details.

Source walkthru

(ogldev_glfw_backend.cpp:24)

#define GLFW_DLL
#include <GLFW/glfw3.h>

This is how you include GLFW in your application. The 'GLFW_DLL' macro is required on Windows for using GLFW as a DLL.

(ogldev_glfw_backend.cpp:168)

void GLFWBackendInit(int argc, char** argv, bool WithDepth, bool WithStencil)
{
    sWithDepth = WithDepth;
    sWithStencil = WithStencil;

    if (glfwInit() != 1) {
        OGLDEV_ERROR("Error initializing GLFW");
        exit(1);
    }

    int Major, Minor, Rev;

    glfwGetVersion(&Major, &Minor, &Rev);

    printf("GLFW %d.%d.%d initialized\n", Major, Minor, Rev);

    glfwSetErrorCallback(GLFWErrorCallback);
}

Initializing GLFW is very simple. Note that the argc/argv parameters are not used but to keep the interface identical with the one we used for FreeGLUT they are still passed to the function. In addition to GLFW initialization we also print the version of the library for informative purposes and set a general error callback. If anything goes wrong we will print the error and exit.

(ogldev_glfw_backend.cpp:195)

bool GLFWBackendCreateWindow(uint Width, uint Height, bool isFullScreen, const char* pTitle)
{
    GLFWmonitor* pMonitor = isFullScreen ? glfwGetPrimaryMonitor() : NULL;

    s_pWindow = glfwCreateWindow(Width, Height, pTitle, pMonitor, NULL);

    if (!s_pWindow) {
        OGLDEV_ERROR("error creating window");
        exit(1);
    }

    glfwMakeContextCurrent(s_pWindow);

    // Must be done after glfw is initialized!
    glewExperimental = GL_TRUE;
    GLenum res = glewInit();
    if (res != GLEW_OK) {
        OGLDEV_ERROR((const char*)glewGetErrorString(res));
        exit(1);
    }

    return (s_pWindow != NULL);
}

In the function above we create a window and perform other important initialization stuff. The first three parameters to glfwCreateWindow are obvious. The fourth parameter specifies the monitor to use. 'GLFWmonitor' is an opaque GLFW object that represents the physical monitor. GLFW support multi-monitor setups and for such cases the function glfwGetMonitors returns a list of all the available monitors. If we pass a NULL monitor pointer we will get a regular window; if we pass a pointer to an actual monitor (we get the default using glfwGetPrimaryMonitor) we get a full screen window. Very simple. The fifth and last parameter is used for context sharing which is out of scope for this tutorial.

Before we start dispatching GL commands we have to make the window current on the calling thread. We accomplish this using glfwMakeContextCurrent. Finally, we initialize GLEW.

(ogldev_glfw_backend.cpp:238)

while (!glfwWindowShouldClose(s_pWindow)) {
    // OpenGL API calls go here...
    glfwSwapBuffers(s_pWindow);
    glfwPollEvents();
}

Unlike GLUT, GLFW doesn't provide its own main loop function. Therefore, we construct it using the above code which is part of wrapper function called GLFWBackendRun(). s_pWindow is a pointer to a GLFW window previously created using glfwCreateWindow(). In order for the application to signal the end of this loop the function glfwSetWindowShouldClose is available to the application via the wrapper function GLFWBackendLeaveMainLoop().

(ogldev_glfw_backend.cpp:122)

static void KeyCallback(GLFWwindow* pWindow, int key, int scancode, int action, int mods)
{
}

static void CursorPosCallback(GLFWwindow* pWindow, double x, double y)
{
}

static void MouseCallback(GLFWwindow* pWindow, int Button, int Action, int Mode)
{
}

static void InitCallbacks()
{
    glfwSetKeyCallback(s_pWindow, KeyCallback);
    glfwSetCursorPosCallback(s_pWindow, CursorPosCallback);
    glfwSetMouseButtonCallback(s_pWindow, MouseCallback);
}

What we see above is the initialization of our keyboard and mouse callbacks. If you are interested in using GLFW exclusively in your application simply review the documentation here for information about the values of Button, Action, Mode, etc. For my tutorials I have created a set of enums to describe the various keyboard and mouse keys and translated GLFW to these enums. I have done the same for GLUT and this provides the commonality which lets the same application code quickly switch from one backend to the other (see the implementation of the above functions in the code for further details).

(ogldev_glfw_backend.cpp:)

void GLFWBackendTerminate()
{
    glfwDestroyWindow(s_pWindow);
    glfwTerminate();
}

This is how we shutdown the GLFW backend. First we destroy the window and after that we terminate the GLFW library and free all of its resources. No call to GLFW can be done after that which is why this has to be the last thing we do in the main function (graphics-wise).

(ogldev_backend.h)

enum OGLDEV_BACKEND_TYPE {
    OGLDEV_BACKEND_TYPE_GLUT,
    OGLDEV_BACKEND_TYPE_GLFW
};

void OgldevBackendInit(OGLDEV_BACKEND_TYPE BackendType, int argc, char** argv, bool WithDepth, bool WithStencil);

void OgldevBackendTerminate();

bool OgldevBackendCreateWindow(uint Width, uint Height, bool isFullScreen, const char* pTitle);

void OgldevBackendRun(ICallbacks* pCallbacks);

void OgldevBackendLeaveMainLoop();

void OgldevBackendSwapBuffers();

I have created a new backend interface which we see in the above header file. These functions replace the GLUT specific code which we have been using. They are implemented in ogldev_backend.cpp in the Common project and are essentially redirections into GLUT or GLFW. You select the backend using OgldevBackendInit() and after that everything is transparent.

Since there isn't nothing new to display in this tutorial I have used the Sponza model which is very common in the 3D community to test new global illumination algorithms.

Next tutorial


Tutorial 45 - Screen Space Ambient Occlusion

Tutorial 45:

Screen Space Ambient Occlusion

Credits: SSAO tutorial by John Chapman

Background

Do you remember how our lighting model began evolving? Back in tutorial 17 we took a first look at the lighting model, starting with the ambient lighting type. The ambient lighting which is supposed to mimic the general feeling of "everything is bright" that you get in a highly lit, mid-day environment, was implemented using a single floating point value that was attached to each lighting source and we multiplied that value by the color of the surface which we sampled from the texture bound to that surface. So you could have a single light source in your scene called "sun" and you could play with the ambient light to control how well the scene was generally lit - values closer to zero produced a darker scene while values closer to 1 produced a lighter scene.

In the following tutorials we implemented diffuse and specular lighting which contributed to the overall quality of the scene but the basic ambient light remained the same. In the recent years we see a rise of what is known as Ambient Occlusion which basically means that instead of going with a fixed ambient light value for each pixel we can calculate how much the pixel is exposed to the ambient light source. A pixel on the floor in the middle of room is much more exposed to the light than, say, a pixel in the corner. This means that the corner will be a bit darker than the rest of the floor. This is the core of ambient occlusion. So in order to implement it we need to find a way to differentiate between those "tightly packed in corners pixels" vs. "out in the open pixels". The product of this calculation is an ambient occlusion term which will control the ambient light in the final lighting stage. Here's a visualization of this ambient occlusion term:

You can see how the edges are the brightest and the corners where we expect to get the smaller amount of lighting are much darker.

There is a lot of research on the subject of ambient occlusion and many algorithms have been developed to approximate it. We are going to study a branch of these algorithms known as Screen Space Ambient Occlusion or SSAO, which was developed by Crytek and became highly popular with their 2007 release of Crysis. Many games have since implemented SSAO and a lot of variations were created on top of it. We are going to study a simplified version of the algorithm based on a SSAO tutorial by John Chapman.

Ambient occlusion can be very compute intensive. Crytek came up with a good compromise where the occlusion term is calculated once per pixel. Hence the prefix 'Screen Space' to the algorithm name. The idea was to go over the window pixel by pixel, extract the view space position in that location, sample a few random points very near that position and check whether they fall inside or outside the real geometry in that area. If many points fall inside the geometry it means the original pixel is cornered by many polygons and receives less light. If many points are outside of any geometry it means the original pixel is "highly exposed" and therefore receives more light. For example, take a look at the following image:

We have a surface with two points on it - P0 and P1. Assume that we are looking at it from somewhere on the upper left corner of the image. We sample a few points around each point and check whether they fall inside or outside the geometry. In the case of P0 there is a greater chance that random points around it will fall inside the geometry. For P1 it is the opposite. Therefore we expect to get a greater ambient occlusion term for P1 which means it will look lighter in the final frame.

Let's take it to the next level of details. We are going to plug in an ambient occlusion pass somewhere before our standard lighting pass (we will need the ambient term for the lighting). This ambient occlusion pass will be a standard full screen quad pass where the calculation is done once per pixel. For every pixel we will need its view space position and we want to generate a few random points in close vicinity to that position. The easiest way will be to have a texture ready at the point fully populated with the view space positions of the entire scene geometry (obviously - only of the closest pixels). For this we will need a geometry pass before the ambient pass where something very similar to the gbuffer that we saw in deferred rendering will be filled with view space position information (and that's it - we don't need normals, color, etc). So now getting the view space position for the current pixel in the ambient pass is just one sample operation away.

So now we are in a fragment shader holding the view space position for the current pixel. Generating random points around it is very easy. We will pass into the shader an array of random vectors (as uniform variables) and add each one to the view space position. For every generated point we want to check whether it lies inside or outside the geometry. Remember that these points are virtual, so no match to the actual surface is expected. We are going to do something very similar to what we did in shadow mapping. Compare the Z value of the random point to the Z value of the closest point in the actual geometry. Naturally, that actual geometry point must lie on the ray that goes from the camera to the virtual point. Take a look at the following diagram:

Point P lies on the red surface and the red and green points were generated randomly around it. The green point lies outside (before) the geometry and the red is inside (thus contributes to the ambient occlusion). The circle represents the radius in which random points are generated (we don't want them to be too far off point P). R1 and R2 are the rays from the camera (at 0,0,0) to the red and green points. They intersect the geometry somewhere. In order to calculate the ambient occlusion we must compare the Z values of the red and green points vs the Z value of the corresponding geometry points that are formed by the intersection of R1/R2 and the surface. We already have the Z value of the red and green points (in view space; after all - this is how we created them). But where's the Z value of the points formed by the above intersection?

Well, there's more than one solution to that question but since we already have a texture ready with the view space position of the entire scene the simplest way will be to find it somehow in it. To do that we will need the two texture coordinates that will sample the view space position for the R1 and R2 rays. Remember that the original texture coordinates that were used to find the view space position of P are not what we need. These coordinates were formed based on the interpolation of the full screen quad that we are scanning in that pass. But R1 and R2 don't intersect P. They intersect the surface somewhere else.

Now we need to do a quick refresher on the way the texture with the view space positions was originally created. After transforming the object space coordinates to view space the resulting vectors were multiplied by the projection matrix (in fact - all these transformation were performed by a single matrix). All this happened in the vertex shader and on the way to the fragment shader the GPU automatically performed perspective divide to complete the projection. This projection placed the view space position on the near clipping plane and the points inside the frustum have a (-1,1) range for their XYZ components. As the view space position was written out to the texture in the fragment shader (the above calculation is performed only on gl_Position; the data written to the texture is forwarded in a different variable) the XY were transformed to the (0,1) range and the results are the texture coordinates where the view space position is going to be written.

So can we use the same procedure in order to calculate the texture coordinates for the red and green points? Well, why not? The math is the same. All we need to do is provide the shader with the projection matrix and use it to project the red and green points on the near clipping plane. We will need to perform the perspective divide manually but that's a no-brainer. Next we will need to transform the result to the (0,1) and here's our texture coordinate! We are now just a sample away from getting the missing Z value and checking whether the virtual point that we generated is located inside or outside the geometry. Now let's see the code.

Source walkthru

(tutorial45.cpp:156)

virtual void RenderSceneCB()
{
    m_pGameCamera->OnRender();

    m_pipeline.SetCamera(*m_pGameCamera);

    GeometryPass();

    SSAOPass();

    BlurPass();

    LightingPass();

    RenderFPS();

    CalcFPS();

    OgldevBackendSwapBuffers();
}

We will start the source walkthru from the top level and work our way down. This is the main render loop and in addition to the three passes that we discussed in the background section there's also a blur pass whose job is to apply a blur kernel on the ambient occlusion map formed by the SSAO pass. This helps smooth things up a bit and is not part of the core algorithm. It's up to you to decide whether to include it or not in your engine.

(tutorial45.cpp:177)

void GeometryPass()
{
    m_geomPassTech.Enable();

    m_gBuffer.BindForWriting();

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    m_pipeline.Orient(m_mesh.GetOrientation());
    m_geomPassTech.SetWVP(m_pipeline.GetWVPTrans());
    m_geomPassTech.SetWVMatrix(m_pipeline.GetWVTrans());
    m_mesh.Render();
}

In the geometry pass we render the entire scene into a texture. In this example there's only one mesh. In the real world there will probably be many meshes.

(geometry_pass.vs)

#version 330

layout (location = 0) in vec3 Position;

uniform mat4 gWVP;
uniform mat4 gWV;

out vec3 ViewPos;

void main()
{
    gl_Position = gWVP * vec4(Position, 1.0);
    ViewPos = (gWV * vec4(Position, 1.0)).xyz;
}

(geometry_pass.fs)

#version 330

in vec3 ViewPos;

layout (location = 0) out vec3 PosOut;

void main()
{
    PosOut = ViewPos;
}

These are the vertex and fragment shaders of the geometry pass. In the vertex shader we calculate the gl_position as usual and we pass the view space position to the fragment shader in a separate variable. Remember that there is no perspective divide for this variable but it is a subject to the regular interpolations performed during rasterization.

In the fragment shader we write the interpolated view space position to the texture. That's it.

(tutorial45.cpp:192)

void SSAOPass()
{
    m_SSAOTech.Enable();

    m_SSAOTech.BindPositionBuffer(m_gBuffer);

    m_aoBuffer.BindForWriting();

    glClear(GL_COLOR_BUFFER_BIT);

    m_quad.Render();
}

This is the application code of the SSAO pass and it is very simple. On the input side we have the view space position from the previous pass and we write the output to an AO buffer. For the rendering we use a full screen quad. This will generate the AO term for every pixel. The real meat is in the shaders.

(ssao.vs)

#version 330

layout (location = 0) in vec3 Position;

out vec2 TexCoord;

void main()
{
    gl_Position = vec4(Position, 1.0);
    TexCoord = (Position.xy + vec2(1.0)) / 2.0;
}

As in many screen space based techniques in the vertex shader we just need to pass-thru the position of the full screen quad. gl_Position will be consumed by the GPU for the purposes of rasterization but we use it's XY components for the texture coordinates. Remember that the full screen quad coordinates range from (-1,-1) to (1,1) so everything in the fragment shader will be interpolated in that range. We want our texture coordinates to be in the (0,1) so we transform it here before sending it out to the fragment shader.

(ssao.fs)

#version 330

in vec2 TexCoord;

out vec4 FragColor;

uniform sampler2D gPositionMap;
uniform float gSampleRad;
uniform mat4 gProj;

const int MAX_KERNEL_SIZE = 128;
uniform vec3 gKernel[MAX_KERNEL_SIZE];

void main()
{
    vec3 Pos = texture(gPositionMap, TexCoord).xyz;

    float AO = 0.0;

    for (int i = 0 ; i < MAX_KERNEL_SIZE ; i++) {
        vec3 samplePos = Pos + gKernel[i]; // generate a random point
        vec4 offset = vec4(samplePos, 1.0); // make it a 4-vector
        offset = gProj * offset; // project on the near clipping plane
        offset.xy /= offset.w; // perform perspective divide
        offset.xy = offset.xy * 0.5 + vec2(0.5); // transform to (0,1) range

        float sampleDepth = texture(gPositionMap, offset.xy).b;

        if (abs(Pos.z - sampleDepth) < gSampleRad) {
            AO += step(sampleDepth,samplePos.z);
        }
    }

    AO = 1.0 - AO/128.0;

    FragColor = vec4(pow(AO, 2.0));
}

Here's the core of the SSAO algorithm. We take the texture coordinates we got from the vertex shader and sample the position map to fetch our view space position. Next we enter a loop and start generating random points. This is done using an array of uniform vectors (gKernel). This array is populated by random vectors in the (-1,1) range in the ssao_technique.cpp file (which I haven't included here because it's pretty standard; check the code for more details). We now need to find the texture coordinates that will fetch the Z value for the geometry point that matches the current random point. We project the random point from view space on the near clipping plane using the projection matrix, perform perspective divide on it and transform it to the (0,1) range. We can now use it to sample the view space position of the actual geometry and compare its Z value to the random point. But before we do that we make sure that the distance between the origin point and the one whose Z value we just fetched is not too far off. This helps us avoid all kinds of nasty artifacts. You can play with the gSampleRad variable for that.

Next we compare the depth of the virtual point with the one from the actual geometry. The GLSL step(x,y) function returns 0 if y < x and 1 otherwise. This means that the local variable AO increases as more points end up behind the geometry. We plan to multiply the result by the color of the lighted pixel so we do a 'AO = 1.0 - AO/128.0' to kind-of reverse it. The result is written to the output buffer. Note that we take the AO to the power of 2 before writing it out. This simply makes it look a bit better in my opinion. This is another artist variable you may want to play with in your engine.

(tutorial45.cpp:205)

void BlurPass()
{
    m_blurTech.Enable();

    m_blurTech.BindInputBuffer(m_aoBuffer);

    m_blurBuffer.BindForWriting();

    glClear(GL_COLOR_BUFFER_BIT);

    m_quad.Render();
}

The application code of the blur pass is identical to the SSAO pass. Here the input is the ambient occlusionn term we just calculated and the output is a buffer containing the blurred results.

(blur.vs)

#version 330

layout (location = 0) in vec3 Position;

out vec2 TexCoord;

void main()
{
    gl_Position = vec4(Position, 1.0);
    TexCoord = (Position.xy + vec2(1.0)) / 2.0;
}

(blur.fs)

#version 330

in vec2 TexCoord;

out vec4 FragColor;

uniform sampler2D gColorMap;

float Offsets[4] = float[]( -1.5, -0.5, 0.5, 1.5 );

void main()
{
    vec3 Color = vec3(0.0, 0.0, 0.0);

    for (int i = 0 ; i < 4 ; i++) {
        for (int j = 0 ; j < 4 ; j++) {
            vec2 tc = TexCoord;
            tc.x = TexCoord.x + Offsets[j] / textureSize(gColorMap, 0).x;
            tc.y = TexCoord.y + Offsets[i] / textureSize(gColorMap, 0).y;
            Color += texture(gColorMap, tc).xyz;
        }
    }

    Color /= 16.0;

    FragColor = vec4(Color, 1.0);
}

This is an example of a very simple blur technique. The VS is actually identical to the one from the SSAO. In the fragment shader we sample 16 points around the origin and average them out.

(tutorial45.cpp:219)

void LightingPass()
{
    m_lightingTech.Enable();
    m_lightingTech.SetShaderType(m_shaderType);
    m_lightingTech.BindAOBuffer(m_blurBuffer);

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    m_pipeline.Orient(m_mesh.GetOrientation());
    m_lightingTech.SetWVP(m_pipeline.GetWVPTrans());
    m_lightingTech.SetWorldMatrix(m_pipeline.GetWorldTrans());
    m_mesh.Render();
}

We have a pretty standard application code for the lighting pass. The only addition here is the blurred AO term buffer which is bound as input.

(lighting.fs)

vec2 CalcScreenTexCoord()
{
    return gl_FragCoord.xy / gScreenSize;
}

vec4 CalcLightInternal(BaseLight Light, vec3 LightDirection, vec3 Normal)
{
    vec4 AmbientColor = vec4(Light.Color * Light.AmbientIntensity, 1.0f);

    if (gShaderType == SHADER_TYPE_SSAO) {
        AmbientColor *= texture(gAOMap, CalcScreenTexCoord()).r;
    }

    ...

I haven't included the entire lighting shader since the change is very minor. The ambient color is modulated by the ambient occlusion term sampled from the AO map for the current pixel. Since we are rendering the actual geometry here and not a full screen quad we have to calculate the texture coordinates using the system maintained gl_FragCoord. gShaderType is a user controlled variable that helps us switch from SSAO to no-SSAO and only-ambient-occlusion-term display. Play with the 'a' key to see how it goes.

Next tutorial


Tutorial 46 - SSAO With Depth Reconstruction

Tutorial 46:

SSAO With Depth Reconstruction

Background

In the previous tutorial we studied the Screen Space Ambient Occlusion algorithm. We used a geometry buffer which contained the view space position of all the pixels as a first step in our calculations. In this tutorial we are going to challenge ourselves by calculating the view space position directly from the depth buffer. The advantage of this approach is that much less memory is required because we will only need one floating point value per pixel instead of three. This tutorial relies heavily on the previous tutorial so make sure you fully understand it before going on. The code here will be presented only as required changes over the original algorithm.

In the SSAO algorithm we scan the entire window pixel by pixel, generate random points around each pixel in view space, project them on the near clipping plane and compare their Z value with the actual pixel at that location. The view space position is generated in a geometry pass at the start of the render loop. In order to populate correctly the geometry buffer with the view space position we also need a depth buffer (else pixels will be updated based on draw order rather than depth). We can use that depth buffer alone to reconstruct the entire view space position vector, thus reducing the space required for it (though some more per-pixel math will be required).

Let's do a short recap on the stages required to populate the depth buffer (if you need a more in-depth review please see tutorial 12). We begin with the object space position of a vertex and multiply it with the WVP matrix which is a combined transformations of local-to-world, world-to-view and projection from view on the near clipping plane. The result is a 4D vector with the view space Z value in the fourth component. We say that this vector is in clip space at this point. The clip space vector goes into the gl_Position output vector from the vertex shader and the GPU clips its first three components between -W and W (W is the fourth component with the view space Z value). Next the GPU performs perspective divide which means that the vector is divided by W. Now the first three components are between -1 and 1 and the last component is simply 1. We say that at this point the vector is in NDC space (Normalized Device Coordinates).

Usually the vertex is just one out of three vertices comprising a triangle so the GPU interpolates between the three NDC vectors across the triangle face and executes the fragment shader on each pixel. On the way out of the fragment shader the GPU updates the depth buffer with the Z component of the NDC vector (based on several state nobs that must be configured correctly such as depth testing, depth write, etc). An important point to remember is that before writing the Z value to the depth buffer the GPU transforms it from (-1,1) to (0,1). We must handle this correctly or else we will get visual anomalies.

So this is basically all the math relevant to the Z buffer handling. Now let's say that we have a Z value that we sampled for the pixel and we want to reconstruct the entire view space vector from it. Everything we need in order to retrace our steps is in the above description but before we dive any further let's see that math again only this time with numbers and matrices rather than words. Since we are only interested in the view space position we can look at the projection matrix rather than the combined WVP (because projection works on the view space position):

What we see above is the projection of the view space vector to clip space (the result on the right). Few notations:

  • ar = Aspect Ratio (width/height)
  • FOV = Field of View
  • n = near clipping plane
  • f = far clipping plane

In order to simplify the next steps let's call the value in location (3,3) of the projection matrix 'S' and the value in location (3,4) 'T'. This means that the value of the Z in NDC is (remember perspective divide):

And since we need to transform the NDC value from (-1,1) to (0,1) the actual value written to the depth buffer is:

It is now easy to see that we can extract the view space Z from the above formula. I haven't specified all the intermediate steps because you should be able to do them yourself. The final result is:

So we have the view space Z. Let's see how we can recover X and Y. Remember that after transforming X and Y to clip space we perform clipping to (-W,W) and divide by W (which is actually Z in view space). X and Y are now in the (-1,1) range and so are all the X and Y values of the to-be-interpolated pixels of the triangle. In fact, -1 and 1 mapped to the left, right, top and bottom of the screen. This means that for every pixel on the screen the following equation applies (showing for X only; same applies to Y just without 'ar'):

We can write the same as:

Note that the left and right hand side of the inequality are basically constants and can be calculated by the application before the draw call. This means that we can draw a full screen quad and prepare a 2D vector with those values for X and Y and have the GPU interpolate them all over the screen. When we get to the pixel we can use the interpolated value along with Z in order to calculate both X and Y.

Source walkthru

(tutorial46.cpp:101)

float AspectRatio = m_persProjInfo.Width / m_persProjInfo.Height;
m_SSAOTech.SetAspectRatio(AspectRatio);
float TanHalfFOV = tanf(ToRadian(m_persProjInfo.FOV / 2.0f));
m_SSAOTech.SetTanHalfFOV(TanHalfFOV);

As I said earlier, we are only going to review the specific code changes to the previous tutorial in order to implement depth reconstruction. The first change that we need to make is to provide the aspect ratio and the tangent of half the field of view angle to the SSAO technique. We see above how to calculate them.

(tutorial46.cpp:134)

if (!m_depthBuffer.Init(WINDOW_WIDTH, WINDOW_HEIGHT, true, GL_NONE)) {
    return false;
}

Next we need to initialize the geometry buffer (whose class attribute was renamed from m_gBuffer to m_depthBuffer) with GL_NONE as the internal format type. This will cause only the depth buffer to be created. Review io_buffer.cpp in the Common project for further details on the internal workings of the IOBuffer class.

(tutorial46.cpp:181)

void GeometryPass()
{
    m_geomPassTech.Enable();

    m_depthBuffer.BindForWriting();

    glClear(GL_DEPTH_BUFFER_BIT);

    m_pipeline.Orient(m_mesh.GetOrientation());
    m_geomPassTech.SetWVP(m_pipeline.GetWVPTrans());
    m_mesh.Render();
}

void SSAOPass()
{
    m_SSAOTech.Enable();
    m_SSAOTech.BindDepthBuffer(m_depthBuffer);

    m_aoBuffer.BindForWriting();

    glClear(GL_COLOR_BUFFER_BIT);

    m_quad.Render();
}

We can see the change from m_gBuffer to m_depthBuffer in the geometry and SSAO passses. Also, we no longer need to call glClear with the color buffer bit because m_depthBuffer does not contain a color buffer. This completes the changes in the main application code and you can see that they are fairly minimal. Most of the juice is in the shaders. Let's review them.

(geometry_pass.vs/fs)

#version 330

layout (location = 0) in vec3 Position;

uniform mat4 gWVP;
// uniform mat4 gWV;

// out vec3 ViewPos;

void main()
{
    gl_Position = gWVP * vec4(Position, 1.0);
    // ViewPos = (gWV * vec4(Position, 1.0)).xyz;
}

#version 330

// in vec3 ViewPos;

// layout (location = 0) out vec3 PosOut;

void main()
{
    // PosOut = ViewPos;
}

Above we see the revised geometry pass vertex and fragment shaders with the stuff that we no longer need commented out. Since we are only writing out the depth everything related to view space position was thrown out. In fact, the fragment shader is now empty.

(ssao.vs)

#version 330

layout (location = 0) in vec3 Position;

uniform float gAspectRatio;
uniform float gTanHalfFOV;

out vec2 TexCoord;
out vec2 ViewRay;

void main()
{
    gl_Position = vec4(Position, 1.0);
    TexCoord = (Position.xy + vec2(1.0)) / 2.0;
    ViewRay.x = Position.x * gAspectRatio * gTanHalfFOV;
    ViewRay.y = Position.y * gTanHalfFOV;
}

Based on the math reviewed above (see the very end of the background section) we need to generate something that we call a view ray in the vertex shader of the SSAO technique. Combined with the view space Z calculated in the fragment shader it will help us extract the view space X and Y. Note how we use the fact that the incoming geometry is a full screen quad that goes from -1 to 1 on the X and Y axis in order to generate the end points of '-1/+1 * ar * tan(FOV/2)' for X and '-1/+1 * tan(FOV/2)' and 'tan(FOV/2)' for Y.

(ssao.fs)

#version 330

in vec2 TexCoord;
in vec2 ViewRay;

out vec4 FragColor;

uniform sampler2D gDepthMap;
uniform float gSampleRad;
uniform mat4 gProj;

const int MAX_KERNEL_SIZE = 64;
uniform vec3 gKernel[MAX_KERNEL_SIZE];

float CalcViewZ(vec2 Coords)
{
    float Depth = texture(gDepthMap, Coords).x;
    float ViewZ = gProj[3][2] / (2 * Depth -1 - gProj[2][2]);
    return ViewZ;
}

void main()
{
    float ViewZ = CalcViewZ(TexCoord);

    float ViewX = ViewRay.x * ViewZ;
    float ViewY = ViewRay.y * ViewZ;

    vec3 Pos = vec3(ViewX, ViewY, ViewZ);

    float AO = 0.0;

    for (int i = 0 ; i < MAX_KERNEL_SIZE ; i++) {
        vec3 samplePos = Pos + gKernel[i];
        vec4 offset = vec4(samplePos, 1.0);
        offset = gProj * offset;
        offset.xy /= offset.w;
        offset.xy = offset.xy * 0.5 + vec2(0.5);

        float sampleDepth = CalcViewZ(offset.xy);

        if (abs(Pos.z - sampleDepth) < gSampleRad) {
            AO += step(sampleDepth,samplePos.z);
        }
    }

    AO = 1.0 - AO/64.0;

    FragColor = vec4(pow(AO, 2.0));
}

The first thing we do in the fragment shader is to calculate the view space Z. We do this with the exact same formula we saw in the background section. The projection matrix was already here in the previous tutorial and we just need to be careful when accessing the 'S' and 'T' items in the (3,3) and (3,4) locations. Remember that the index goes from 0 to 3 (vs. 1 to 4 in standard matrix semantics) and that the matrix is transposed so we we need to reverse the column/row for the 'T'.

Once the Z is ready we multiply it by the view ray in order to retrieve the X and Y. We continue as usual by generating the random points and projecting them on the screen. We use the same trick to calculate the depth of the projected point.

If you have done everything correctly you should end up with pretty much the same results as in the previous tutorial... ;-)


Tutorial 47 - Shadow Mapping with Directional Lights

Tutorial 47:

Shadow Mapping with Directional Lights

Credits: some of the images were take from Clipart Panda and ClipArtHut

Background

The Shadow Mapping algorithm that we explored in tutorial 23 and tutorial 24 used a spot light as the light source. The algorithm itself is based on the idea of rendering into a shadow map from the light point of view. This is simple with spot lights because they behave in the same way as our standard camera. The spot light has a location and a direction vector and the area covered by the light grows as we move further away from its source:

The fact that the spotlight behaves like a frustum makes it easier to implement shadow mapping because we can use the same perspective projection matrix as the camera in order to render into the shadow map. Implementing Shadow Mapping with Point Lights was a bit of a challenge but we were able to overcome it by rendering into a cubemap. Projection, though, was still perspective.

Now let's think about directional lights. A directional light has a direction but not a position. It is usually used to mimic the behavior of the sun which due to its size and distance seems to cast parallel lights rays:

In this case, we can no longer use Perspective Projection. Enter Orthographic Projection. The idea here is that of converging all light rays in one spot (the camera), the light rays remain parallel so no 3D effect is created.

In the following image we see the same box using perspective projection on the left hand side and orthographic projection on the right hand side:

The left box looks real, just as you would expect it to be and delivers the correct sense of depth. The right one doesn't look real since the front and back rectangles are exactly the same. We know that their dimensions are the same but when looking at a picture we expect the front one to look larger. So how does Orthographic Projection helps us with directional lights? Well, remember that Perspective Projection takes something that looks like a frustum and maps it to a normalized cube (a cube that goes from [-1,-1,-1] to [1,1,1]). After mapping, the XY coordinates are used to find the location in the texture (in our case the shadow map) and the Z is the value which is written there. An Orthographic projection takes a general box and maps it to the normalized cube (l,r,b,t,n,f stands for left, right, bottom, top, near, far, respectively):

Now think about the rays of the directional light as if they are originating from the front face of the box and going parallel to each other until they hit the back face. If we do the mapping between the general box and the normalized box (remember - we call this NDC space) properly the rest of the generation of the shadow map remains the same.

Let's see how this mapping is done. We have three ranges along the XYZ axes that we need to map to (-1,1). This is a simple linear mapping without divide-by-zero after that (since it is orthographic and not perspective). The general form of an equation that maps range (a,b) to (c,d) is:

Where a<=X<=b. Let's do the mapping on the X-axis. Plug the ranges (l,r) to (-1,1) in the above equation and we get:

Following the same logic we do the mapping on the Y-axis from (b,t) to (-1,1):

In the case of the Z-axis we need to map (n,f) to (-1,1):

Now that we have the three mapping equations let's create a matrix to wrap them together nicely:

Compare this matrix with the one we created for perpective projection in tutorial 12. An important difference is that in location [3,2] (count starts at zero) we have 0 instead of 1. For perspective projection the 1 was required in order to copy the Z into the W location of the result. This allows the GPU to perform perspective divide when everything is divided automatically by W (and you cannot disable this). In the case of orthographic projection the W will remain as 1, effectively disabling this operation.

When working on shadow mapping with directional lights you need to be careful about how you define the dimensions of orthographic projection. With perspective projection life is a bit simpler. The field-of-view defines how wide the camera is and due to the nature of the frustum we capture more and more as we move further away from the viewer (same as how our eye functions). We also need to define a near and far plane to control clipping based on distance. In many cases the same values of field-of-view, near and far plane will work just fine. But in the case of orthographic projection we have a box rather than a frustum and if we are not careful we may "miss" the objects and not render anything. Let's see an example. In the scene below left and bottom are -10, right and top are 10, the near plane is -10 and the far plane is 100:

The problem is that the objects are placed at distance of 30 from each other so the projection was not wide enough in order to capture everything (remember that the light direction is orthogonal to the viewer so the objects are scattered on a wide field relative to the light). Now let's multiply left/right/bottom/top by 10 (near/far planes unchanged):

Now all the objects have a shadow. However, we have a new problem. The shadows don't look as good as when only one object had a shadow. This problem is called Perspective Aliasing and the reason is that many pixels in view space (when rendering from the camera point of view) are mapped to the same pixel in the shadow map. This makes the shadows look kind of blocky. When we increased the dimensions of the orthographic box we increased that ratio because the shadow map remains the same but a larger part of the world is now rendered to it. Perspective Aliasing can be mitigated somewhat by increasing the size of the shadow map but you cannot go too far with that as there is a negative impact on memory footprint. In future tutorials we will explore advanced techniques to handle this problem.

Source walkthru

The main difference between shadow mapping with directional and spot lights is the orthographic vs. perspective projection. This is why I'm only going to review the changes required for shadows with directional light. Make sure you are highly familiar with tutorial 23 and tutorial 24 before proceeding because most of the code is the same. If you have a working version of shadows with spot lights you will only need to make a few minor changes to get directional lights shadows working.

(matrix_3d.cpp:165)

void Matrix4f::InitOrthoProjTransform(const OrthoProjInfo& p)
{
    float l = p.l;
    float r = p.r;
    float b = p.b;
    float t = p.t;
    float n = p.n;
    float f = p.f;

    m[0][0] = 2.0f/(r - l); m[0][1] = 0.0f;         m[0][2] = 0.0f;         m[0][3] = -(r + l)/(r - l);
    m[1][0] = 0.0f;         m[1][1] = 2.0f/(t - b); m[1][2] = 0.0f;         m[1][3] = -(t + b)/(t - b);
    m[2][0] = 0.0f;         m[2][1] = 0.0f;         m[2][2] = 2.0f/(f - n); m[2][3] = -(f + n)/(f - n);
    m[3][0] = 0.0f;         m[3][1] = 0.0f;         m[3][2] = 0.0f;         m[3][3] = 1.0;
}

I've added the above function to the Matrix4f class in order to initialize the orthographic projection matrix. This function is called from Pipeline::GetWVOrthoPTrans().

(tutorial47.cpp:163)

void ShadowMapPass()
{
    m_shadowMapFBO.BindForWriting();
    glClear(GL_DEPTH_BUFFER_BIT);

    m_ShadowMapEffect.Enable();

    Pipeline p;
    p.SetCamera(Vector3f(0.0f, 0.0f, 0.0f), m_dirLight.Direction, Vector3f(0.0f, 1.0f, 0.0f));
    p.SetOrthographicProj(m_shadowOrthoProjInfo);

    for (int i = 0; i < NUM_MESHES ; i++) {
        p.Orient(m_meshOrientation[i]);
        m_ShadowMapEffect.SetWVP(p.GetWVOrthoPTrans());
        m_mesh.Render();
    }

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

void RenderPass()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    m_LightingTech.Enable();

    m_LightingTech.SetEyeWorldPos(m_pGameCamera->GetPos());

    m_shadowMapFBO.BindForReading(SHADOW_TEXTURE_UNIT);

    Pipeline p;
    p.SetOrthographicProj(m_shadowOrthoProjInfo);
    p.Orient(m_quad.GetOrientation());
    p.SetCamera(Vector3f(0.0f, 0.0f, 0.0f), m_dirLight.Direction, Vector3f(0.0f, 1.0f, 0.0f));
    m_LightingTech.SetLightWVP(p.GetWVOrthoPTrans());
    p.SetPerspectiveProj(m_persProjInfo);
    p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());
    m_LightingTech.SetWVP(p.GetWVPTrans());
    m_LightingTech.SetWorldMatrix(p.GetWorldTrans());
    m_pGroundTex->Bind(COLOR_TEXTURE_UNIT);
    m_quad.Render();

    for (int i = 0; i < NUM_MESHES ; i++) {
        p.Orient(m_meshOrientation[i]);
        m_LightingTech.SetWVP(p.GetWVPTrans());
        m_LightingTech.SetWorldMatrix(p.GetWorldTrans());
        m_mesh.Render();
    }
}

These are the complete shadow and render passes and they are practically the same as for spot lights so we don't have to review them fully. Just a couple of differences that must be noted here. First is that I've added a member called m_shadowOrthoProjInfo in order to keep the orthographic projection variables separate from the existing perspective projection variables that are used for rendering. m_shadowOrthoProjInfo is used to configure the WVP for the light point of view and it is initialized with the values of -100,+100,-100,+100,-10,+100 for left, right, bottom, top, near, far, respectively.

The second change is that when we configure the camera for that light WVP matrix we use the origin as the location of the light. Since a directional light only has a direction and no position we don't care about that variable in the view matrix. We just need to rotate the world so that the light points toward the positive Z-axis.

(lighting.fs:96)

vec4 CalcDirectionalLight(vec3 Normal, vec4 LightSpacePos)
{
    float ShadowFactor = CalcShadowFactor(LightSpacePos);
    return CalcLightInternal(gDirectionalLight.Base, gDirectionalLight.Direction, Normal, ShadowFactor);
}

void main()
{
...
  vec4 TotalLight = CalcDirectionalLight(Normal, LightSpacePos);
...
}

The shaders are almost exactly the same - we just need to calculate a shadow factor for the directional light as well.

Next tutorial
comments powered by Disqus


Tutorial 48:

Tutorial 48 - User Interface with Ant Tweak Bar

Background

In this tutorial we are going to leave 3D for a while and focus on adding something practical and useful to our programs. We will learn how to integrate a user interface library which will help in configuring the various values that interest us in the tutorials. The library that we will use is called Ant Tweak Bar (a.k.a ATB) which is hosted at anttweakbar.sourceforge.net. There are many options available and if you do some research on the subject you will find a lot of discussions and opinions on the matter. In addition to OpenGL, ATB also supports DirectX 9/10/11 so if you want your UI to be portable this is a good advantage. I found it to be very useful and easy to learn. I hope you will too. So let's jump right in.

Disclaimer: as I was putting the finishing touches on this tutorial I noticed that ATB is no longer supported. The official website is alive but the author states that he is no longer actively maintaining it. After some thought I decided to publish this tutorial regardless. The library has proved to be very useful to me and I plan to keep using it. If you are looking for something like that and having the library being actively maintained is a requirement for you then you may need to find an alternative but I think many people can use it as it is. Since this is open source there is always a chance someone will pick up maintenance.

Installation

The first thing we need to do is to install ATB. You can grab the zip file from the ATB website (version 1.16 when this tutorial was published) which contains almost everything you need or use a copy of the files that I provide along with the tutorials source package. If you decide to go with the official package simply grab it from the link above, unzip it somewhere and grab AntTweakBar.h from the include directory and copy it where your project sees it. On Linux I recommend putting it in /usr/local/include (requires root access). In the tutorials source package this file is available under Include/ATB.

Now for the libraries. If you are using Windows this is very easy. The official zip file contains a lib directory with AntTweakBar.dll and AntTweakBar.lib (there is also a matching couple for 64 bits). You will need to link your project to the lib file and when you run the executable have the dll in the local directory or in Windows\System32. On Linux you will need to go into the src directory and type 'make' to build the libraries. The result will be libAntTweakBar.a, libAntTweakBar.so and libAntTweakBar.so.1. I recommend you copy these into /usr/local/lib to make them available from everywhere. The tutorials source package contains the Windows libraries in Windows/ogldev/Debug and the Linux binaries in Lib (so you don't need to build them).

Integration

In order to use ATB you will need to include the header AntTweakBar.h in your source code:
#include <AntTweakBar.h> If you are using the Netbeans project that I provide then the Include/ATB directory which contains this header is already in the include path. If not then make sure your build system sees it.

To link against the libraries:

  • Windows: add the AntTweakBar.lib to your Visual Studio project
  • Linux: add '-lAntTweakBar' to the build command and make sure the Linux binaries are in /usr/local/lib

Again, if you are using my Visual Studio or Netbeans projects then all of this is already set up for you so you don't need to worry about anything.

Initialization

In order to initialize ATB you need to call:
TwInit(TW_OPENGL, NULL); or in case you want to initialize the GL context for core profile:
TwInit(TW_OPENGL_CORE, NULL); For the tutorials series I created a class called ATB which encapsulates some of the functionality of the library and adds some stuff to make it easier for integration (that class is part of the Common project). You can initialize ATB via that class using a code similar to this:
ATB m_atb;

if (!m_atb.Init()) {
    // error
    return false;
}

Processing Events

ATB provides widgets that allow you to modify their values in different ways. In some widgets you can simply type in a new value. Others are more graphical in nature and allows the use of the mouse in order to modify the value. This means that ATB must be notified on mouse and keyboard events in the system. This is done using a set of callback functions that ATB provides for each of the underlying windowing libraries it supports (glut, glfw, SDL, etc). If your framework is based on just one of these libraries you can simply hook ATB's callbacks inside your callbacks. See ATB website for an example. Since OGLDEV supports both glut and glfw I'm going to show you how I integrated the callbacks into my framework so that these two libraries are supported in a unified manner. Take a look at the following three functions from the ATB class:

bool ATB::KeyboardCB(OGLDEV_KEY OgldevKey)
{
    int ATBKey = OgldevKeyToATBKey(OgldevKey);

    if (ATBKey == TW_KEY_LAST) {
        return false;
    }

    return (TwKeyPressed(ATBKey, TW_KMOD_NONE) == 1);
}

bool ATB::PassiveMouseCB(int x, int y)
{
    return (TwMouseMotion(x, y) == 1);
}

bool ATB::MouseCB(OGLDEV_MOUSE Button, OGLDEV_KEY_STATE State, int x, int y)
{
    TwMouseButtonID btn = (Button == OGLDEV_MOUSE_BUTTON_LEFT) ? TW_MOUSE_LEFT : TW_MOUSE_RIGHT;
    TwMouseAction ma = (State == OGLDEV_KEY_STATE_PRESS) ? TW_MOUSE_PRESSED : TW_MOUSE_RELEASED;

    return (TwMouseButton(ma, btn) == 1);
}

These functions are basically wrappers around the native ATB callback functions. They translate OGLDEV types to ATB types and then pass the call down to ATB. They return true if ATB processed the event (in which case you can simply discard it) and false if not (so you should take a look at the event and see if it interests you). Here's how I hooked these functions into the callbacks of the tutorial:

virtual void KeyboardCB(OGLDEV_KEY OgldevKey, OGLDEV_KEY_STATE OgldevKeyState)
{
    if (OgldevKeyState == OGLDEV_KEY_STATE_PRESS) {
        if (m_atb.KeyboardCB(OgldevKey)) {
            return;
        }
    }

    switch (OgldevKey) {
        .
        .
        .
    default:
        m_pGameCamera->OnKeyboard(OgldevKey);
    }
}

virtual void PassiveMouseCB(int x, int y)
{
    if (!m_atb.PassiveMouseCB(x, y)) {
        m_pGameCamera->OnMouse(x, y);
    }
}

virtual void MouseCB(OGLDEV_MOUSE Button, OGLDEV_KEY_STATE State, int x, int y)
{
    m_atb.MouseCB(Button, State, x, y);
}

If you are not familiar with OGLDEV framework then the above may not make much sense to you so make sure you spend some time with the tutorials first and get to know how things are done. Every tutorial is just a class that inherits from ICallbacks and OgldevApp. ICallbacks provides the (not surprisingly) callback functions that are called from the backend (by glut or glfw). We first let ATB know about the events and if it didn't process them we let the app handle them (e.g passing them on to the camera object).

Create a tweak bar

You need to create at least one tweak bar which is basically a window with widgets that ATB provides to tweak your application:
TwBar *bar = TwNewBar("OGLDEV"); The string in the parenthesis is just a way to name the tweak bar.

Draw the tweak bar

    In order for the tweak bar to appear in your OpenGL window there must be a call present
    to the TwDraw() function in the render loop. The ATB website provides the following
    generic render loop as an example:
// main loop
while( ... )
{
// clear the frame buffer
// update view and camera
// update your scene
// draw your scene

TwDraw();  // draw the tweak bar(s)

// present/swap the frame buffer
} // end of main loop
I placed a call to TwDraw() in the beginning of OgldevBackendSwapBuffers() (ogldev_backend.cpp:97).
    This function is called at the end of every main render function and is a good place
    to integrate TwDraw() into the framework.

Adding widgets

The above is everything you need to basically have ATB up and running in your application. Your ATB bar should now look like this:

From now on what we need to do is to add widgets and link them to our application so that they can be used to tweak parameters of our code. Let's add a drop down box. In this tutorial I will use it to select the mesh to be displayed. We need to use the TwEnumVal structure provided by ATB in order to create a list of available items in the drop down box. That structure is made of pairs of integer and a char array. The integer is an identifier for the drop down item and the char array is the name to be displayed. Once the item list is created as an array of TwEnumVal structs we create a TwType object using the TwDefineEnum function. TwType is an enum of a few parameter types that ATB understands (color, vectors, etc) but we can add user defined types to support our specific needs. Once our TwType is ready we can use TwAddVarRW to link it to the tweak bar. TwAddVarRW() also takes an address of an integer where ATB will place the current selection in the drop down box. We can then use that integer to change stuff in our application (the mesh to be displayed in our case).
// Create an internal enum to name the meshes
typedef enum { BUDDHA, BUNNY, DRAGON } MESH_TYPE;

// A variable for the current selection - will be updated by ATB
MESH_TYPE m_currentMesh = BUDDHA;

// Array of drop down items
TwEnumVal Meshes[] = { {BUDDHA, "Buddha"}, {BUNNY, "Bunny"}, {DRAGON, "Dragon"}};

// ATB identifier for the array
TwType MeshTwType = TwDefineEnum("MeshType", Meshes, 3);

// Link it to the tweak bar
TwAddVarRW(bar, "Mesh", MeshTwType, &m_currentMesh, NULL);

 The result should look like this:

We can add a seperator using the following line:
// The second parameter is an optional name
TwAddSeparator(bar, "", NULL); Now we have:

Let's see how we can link our camera so that its position and direction will always be displayed. Until now you are probably already used to printing the current camera parameters so that they can be reused later but displaying them in the UI is much nicer. To make the code reusable I've added the function AddToATB() to the camera class. It contains three calls to ATB functions. The first call just uses TwAddButton() in order to add a string to the tweak bar. TwAddButton() can do much more and we will see an example later on. Then we have TwAddVarRW() that adds a read/write variable and TwAddVarRO() that adds a read-only variable. The read/write variable we use here is simply the position of the camera and the UI can be used to modify this and have it reflected in the actual application. Surprisingly, ATB does no provide an internal TwType for an array of three floats so I created one to be used by the framework:

(ogldev_atb.cpp:38)
TwStructMember Vector3fMembers[] = {
    { "x", TW_TYPE_FLOAT, offsetof(Vector3f, x), "" },
    { "y", TW_TYPE_FLOAT, offsetof(Vector3f, y), "" },
    { "z", TW_TYPE_FLOAT, offsetof(Vector3f, z), "" }
};

TW_TYPE_OGLDEV_VECTOR3F = TwDefineStruct("Vector3f", Vector3fMembers, 3, sizeof(Vector3f), NULL, NULL); We can now use TW_TYPE_OGLDEV_VECTOR3F whenever we want to add a widget to tweak a vector of 3 floats. Here's the complete AddToATB() function:
void Camera::AddToATB(TwBar* bar)
{
    TwAddButton(bar, "Camera", NULL, NULL, "");
    TwAddVarRW(bar, "Position", TW_TYPE_OGLDEV_VECTOR3F, (void*)&m_pos, NULL);
    TwAddVarRO(bar, "Direction", TW_TYPE_DIR3F, &m_target, " axisz=-z ");
} We have used the provided TW_TYPE_DIR3F as the parameter type that displays an array of 3 floats using an arrow. Note the addition of 'axisz=-z' as the last parameter of TwAddVarRO(). Many ATB functions take a string of options in the last parameter. This allows modifying the internal behavior of the function. axisz is used to change from right handed system (ATB default) to left handed system (OGLDEV default). There's a lot of additional options available that I simply cannot cover. You can find them here.

Here's how the tweak bar looks with the camera added:

You are probably spending a lot of time playing with the orientation of your meshes. Let's add something to the tweak bar to simplify that. The solution is a visual quaternion that can be used to set the rotation of a mesh. We start by adding a local Quaternion variable (see ogldev_math_3d.h for the definition of that struct):
Quaternion g_Rotation = Quaternion(0.0f, 0.0f, 0.0f, 0.0f); We then link the quaternion variable to the tweak bar using the parameter type TW_TYPE_QUAT4F:
TwAddVarRW(bar, "ObjRotation", TW_TYPE_QUAT4F, &g_Rotation, " axisz=-z "); Again, we need to change from right handed to left handed system. Finally the quaternion is converted to degrees:
m_mesh[m_currentMesh].GetOrientation().m_rotation = g_Rotation.ToDegrees(); The rotation vector can now be used to orient the mesh and generate the WVP matrix for it:
m_pipeline.Orient(m_mesh[m_currentMesh].GetOrientation()); Our tweak bar now looks like this:

Now let's add a check box. We will use the check box to toggle between automatic rotation of the mesh around the Y-axis and manual rotation (using the quaternion we saw earlier). First we make an ATB call to add a button:
TwAddButton(bar, "AutoRotate", AutoRotateCB, NULL, " label='Auto rotate' "); The third parameter is a callback function which is triggered when the check box is clicked and the fourth parameter is a value to be transfered as a parameter to the callback. I don't need it here so I've used NULL.
bool gAutoRotate = false;

void TW_CALL AutoRotateCB(void *p)
{
    gAutoRotate = !gAutoRotate;
} You can now use gAutoRotate to toggle between automatic and manual rotations.
Here's how the tweak bar looks like:

Another useful widget that we can add is a read/write widget for controlling the speed of rotation (when auto rotation is enabled). This widget provides multiple ways to control its value:
TwAddVarRW(bar, "Rot Speed", TW_TYPE_FLOAT, &m_rotationSpeed,
            " min=0 max=5 step=0.1 keyIncr=s keyDecr=S help='Rotation speed (turns/second)' "); The first four parameters are obvious. We have the pointer to the tweak bar, the string to display, the type of the parameter and the address where ATB will place the updated value. The interesting stuff comes in the option string at the end. First we limit the value to be between 0 and 5 and we set the increment/decrement step to 0.1. We set the keys 's' and 'd' to be shortcuts to increment or decrement the value, respectively. When you hover over the widget you can see the shortcuts in the bottom of the tweak bar. You can either type in the value directly, use the shortcut keys, click on the '+' or '-' icons on the right or use the lever to modify the value (click on the circle to bring up the rotation lever). Here's the bar with this widget:

In all of the tutorials there is usually at least one light source so it makes sense to add some code that will allow us to easily hook it up to the tweak bar so we can play with it parameters. So I went ahead and added the following methods to the various light source classes:
void BaseLight::AddToATB(TwBar *bar)
{
    std::string s = Name + ".Color";
    TwAddVarRW(bar, s.c_str(), TW_TYPE_COLOR3F, &Color, NULL);
    s = Name + ".Ambient Intensity";
    TwAddVarRW(bar, s.c_str(), TW_TYPE_FLOAT, &AmbientIntensity, "min=0.0 max=1.0 step=0.005");
    s = Name + ".Diffuse Intensity";
    TwAddVarRW(bar, s.c_str(), TW_TYPE_FLOAT, &DiffuseIntensity, "min=0.0 max=1.0 step=0.005");
}

void DirectionalLight::AddToATB(TwBar *bar)
{
    BaseLight::AddToATB(bar);
    std::string s = Name + ".Direction";
    TwAddVarRW(bar, s.c_str(), TW_TYPE_DIR3F, &Direction, "axisz=-z");
}

void PointLight::AddToATB(TwBar *bar)
{
    BaseLight::AddToATB(bar);
    std::string s = Name + ".Position";
    TwAddVarRW(bar, s.c_str(), TW_TYPE_OGLDEV_VECTOR3F, &Position, "axisz=-z");
    s = Name + ".Attenuation";
    TwAddVarRW(bar, s.c_str(), TW_TYPE_OGLDEV_ATTENUATION, &Attenuation, "");
}

void SpotLight::AddToATB(TwBar *bar)
{
    PointLight::AddToATB(bar);
    std::string s = Name + ".Direction";
    TwAddVarRW(bar, s.c_str(), TW_TYPE_DIR3F, &Direction, "axisz=-z");
    s = Name + ".Cutoff";
    TwAddVarRW(bar, s.c_str(), TW_TYPE_FLOAT, &Cutoff, "");
}
 Note that 'Name' is a new string memeber of the BaseLight class that must be set before AddToATB() function is called on the light object. It represents the string that will be displayed in the tweak bar for that light. If you plan on adding multiple lights you must make sure to pick up unique names for them. AddToATB() is a virtual function so the correct instance according to the concrete class is always called. Here's the bar with a directional light source:

The last thing that I want to demonstrate is the ability to get and set various parameters that control the behaviour of the tweak bar. Here's an example of setting the refresh rate of the bar to one tenth of a second:
float refresh = 0.1f;
TwSetParam(bar, NULL, "refresh", TW_PARAM_FLOAT, 1, &refresh);
 Since moving the mouse to the tweak bar means that the camera also moves I made the key 'a' automatically move the mouse to the center of the tweak bar without touching the camera. I had to read the location and size of the tweak bar in order to accomplish that so I used TwGetParam() in order to do that:
virtual void KeyboardCB(OGLDEV_KEY OgldevKey)
{
    if (!m_atb.KeyboardCB(OgldevKey)) {
        switch (OgldevKey) {
            case OGLDEV_KEY_A:
            {
                int Pos[2], Size[2];
                TwGetParam(bar, NULL, "position", TW_PARAM_INT32, 2, Pos);
                TwGetParam(bar, NULL, "size", TW_PARAM_INT32, 2, Size);
                OgldevBackendSetMousePos(Pos[0] + Size[0]/2, Pos[1] + Size[1]/2);
                break;
            }

Next tutorial
comments powered by Disqus


Tutorial 49 - Cascaded Shadow Mapping

Tutorial 49:

Cascaded Shadow Mapping

Background

Let's take a close up look of the shadow from tutorial 47:

As you can see, the qaulity of the shadow is not high. It's too blocky. We've touched on the reason for that blockiness at the end of tutorial 47 and referred to it as Perspective Aliasing which means a large number of pixels in view space being mapped to the same pixel in the shadow map. This means that all these pixels will either be in shadow or in light, contributing to the sense of blockiness. In other words, since the resolution of the shadow map is not high enough it cannot cover the view space adequately. One obvious way to deal with this is to increase the resolution of the shadow map but that will increase the memory footprint of our app so it may not be the best course of action.

Another way to deal with this problem is to notice that shadows closer to the camera a far more important in terms of quality than shadow of objects that are far away. Distant objects are smaller anyway and usually the eye focuses on what happens close by, leaving the rest as a "background". If we can find a way to use a dedicated shadow map for closer objects and a different shadow map for distant objects then the first shadow map will only need to cover the a smaller region, thus decreasing the ratio that we discusses above. This, in a nutshell, is what Cascaded Shadow Mapping (a.k.a CSM) is all about. At the time of writing this tutorial CSM is considered one of the best ways to deal with Perspective Aliasing. Let's see how we can implement it.

From a high level view we are going to split the view frustum into several cascades (since it doesn't need to be just two as in the previous example). For the purpose of this tutorial we will use three cascades: near, middle and far. The algorithm itself is pretty generic so you can use more cascades if you feel like it. Every cascade will be rendered into its own private shadow map. The shadow algorithm itself will remain the same but when sampling the depth from the shadow map we will need to select the appropriate map based on the distance from the viewer. Let's take a look at a generic view frustum:

As usual, we have a small near plane and a larger far plane. Now let's take a look at the same fustum from above:

The next step is to split the range from the near plane to the far plane into three parts. We will call this near, middle and far. In addition, let's add the light direction (the arrow on the right hand side):

So how are we going to render each cascade into its own private shadow map? Let's think about the shadow phase in the shadow mapping algorithm. We set up things to render the scene from the light point of view. This means creating a WVP matrix with the world transform of the object, the view transform based on the light and a projection matrix. Since this tutorial is based on tutorial 47 which dealt with shadows of directional lights the projection matrix will be orthographic. In general CSMs make more sense in outdoor scenes where the main light source is usually the sun so using a directional light here is natural. If you look at the WVP matrix above you will notice that the first two parts (world and view) are the same for all cascades. After all, the position of the object in the world and the orientation of the camera based on the light source are not related to the splitting of the frustum into cascades. What matters here is only the projection matrix because it defines the extent of the region which will eventually be rendered. And since orthographic projections are defined using a box we need to define three different boxes which will be translated into three different orthographic projection matrices. These projection matrices will be used to create the three WVP matrices to render each cascade into its own shadow map.

The most logical thing to do will be to make these boxes as small as posible in order to keep the ratio of view pixels to shadow map pixels as low as possible. This means creating a bounding box for each cascade which is oriented along the light direction vector. Let's create such a bounding box for the first cascade:

Now let's create a bounding box for the second cascade:

And finally a bouding box for the last cascade:

As you can see, there is some overlap of the bounding boxes due to the orientationn of the light which means some pixels will be rendered into more than one shadow map. There is no problem with that as long as all the pixels of a single cascade are entirely inside a single shadow map. The selection of the shadow map to use in the shader for shadow calculations will be based on the distance of the pixel from the actual viewer.

Calculations of the bounding boxes that serve as the basis for the orthographic projection in the shadow phase is the most complicated part of the algorithm. These boxes must be described in light space because the projections come after world and view transforms (at which point the light "originates" from the origin and points along the positive Z axis). Since the boxes will be calculated as min/max values on all three axis they will be aligned on the light direction, which is what we need for projection. To calculate the bounding box we need to know how each cascade looks like in light space. To do that we need to follow these steps:

  1. Calculate the eight corners of each cascade in view space. This is easy and requires simple trigonometry:

    2. The above image represents an arbitrary cascade (since each cascade on its own is basically a frustum and shares the same field-of-view angle with the other cascades). Note that we are looking from the top down to the XZ plane. We need to calculate X1 and X2:

    This gives us the X and Z components of the eight coordinates of the cascade in view space. Using similar math with the vertical field-of-view angle we can get the Y component and finalize the coordinates.

  2. Now we need to transform the cascade coordinates from view space back to world space. Let's say that the viewer is oriented such that in world space the frustum looks like that (the red arrow is the light direction but ignore it for now):

    3. In order to transform from world space to view space we multiply the world position vector by the view matrix (which is based on the camera location and rotation). This means that if we already have the coordinates of the cascade in view space we must multiply them by the inverse of the view matrix in order to transform them to world space:

  3. With the frustum coordinates in world space we can now transform them to light space as any other object. Remember that the light space is exactly like view space but instead of the camera we use the light source. Since we are dealing with a directional light that has no origin we just need to rotate the world so that the light direction becomes aligned with the positive Z axis. The origin of light can simply be the origin of the light space coordinate system (which means we don't need any translation). If we do that using the previous diagram (with the red arrow being the light direction) the cascade frustum in light space should look like:

  4. With the cascade coordinates finally in light space we just need to generate a bounding box for it by taking the min/max values of the X/Y/Z components of the eight coordinates. This bounding box provides the values for the orthographic projection for rendering this cascade into its shadow map. By generating an orthographic projection for each cascade separately we can now render each cascade into different shadow map. During the light phase we will calculate the shadow factor by selecting a shadow map based on the distance from the viewer.

Source walkthru

(ogldev_shadow_map_fbo.cpp:104)

bool CascadedShadowMapFBO::Init(unsigned int WindowWidth, unsigned int WindowHeight)
{
    // Create the FBO
    glGenFramebuffers(1, &m_fbo);

    // Create the depth buffer
    glGenTextures(ARRAY_SIZE_IN_ELEMENTS(m_shadowMap), m_shadowMap);

    for (uint i = 0 ; i < ARRAY_SIZE_IN_ELEMENTS(m_shadowMap) ; i++) {
        glBindTexture(GL_TEXTURE_2D, m_shadowMap[i]);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, WindowWidth, WindowHeight, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    }

    glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_shadowMap[0], 0);

    // Disable writes to the color buffer
    glDrawBuffer(GL_NONE);
    glReadBuffer(GL_NONE);

    GLenum Status = glCheckFramebufferStatus(GL_FRAMEBUFFER);

    if (Status != GL_FRAMEBUFFER_COMPLETE) {
    printf("FB error, status: 0x%x\n", Status);
    return false;
    }

    return true;
}

void CascadedShadowMapFBO::BindForWriting(uint CascadeIndex)
{
    assert(CascadeIndex < ARRAY_SIZE_IN_ELEMENTS(m_shadowMap));
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_shadowMap[CascadeIndex], 0);
}

void CascadedShadowMapFBO::BindForReading()
{
    glActiveTexture(CASCACDE_SHADOW_TEXTURE_UNIT0);
    glBindTexture(GL_TEXTURE_2D, m_shadowMap[0]);

    glActiveTexture(CASCACDE_SHADOW_TEXTURE_UNIT1);
    glBindTexture(GL_TEXTURE_2D, m_shadowMap[1]);

    glActiveTexture(CASCACDE_SHADOW_TEXTURE_UNIT2);
    glBindTexture(GL_TEXTURE_2D, m_shadowMap[2]);
}

The CascadedShadowMapFBO class we see above is a modification of the ShadowMapFBO class that we have previously used for shadow mapping. The main change is that the m_shadowMap array has space for three shadow map objects which is the number of cascades we are going to use for this example. Here we have the three main functions of the class used to initialize it, bind it for writing in the shadow map phase and for reading in the lighting phase.

(tutorial49.cpp:197)

virtual void RenderSceneCB()
{
    for (int i = 0; i < NUM_MESHES ; i++) {
        m_meshOrientation[i].m_rotation.y += 0.5f;
    }

    m_pGameCamera->OnRender();

    ShadowMapPass();
    RenderPass();
    OgldevBackendSwapBuffers();
}

The main render function in the CCM algorithm is the same as in the standard shadow mapping algorithm - first render into the shadow maps and then use them for the actual lighting.

(tutorial49.cpp:211)

void ShadowMapPass()
{
    CalcOrthoProjs();

    m_ShadowMapEffect.Enable();

    Pipeline p;

    // The camera is set as the light source - doesn't change in this phase
    p.SetCamera(Vector3f(0.0f, 0.0f, 0.0f), m_dirLight.Direction, Vector3f(0.0f, 1.0f, 0.0f));

    for (uint i = 0 ; i < NUM_CASCADES ; i++) {
        // Bind and clear the current cascade
        m_csmFBO.BindForWriting(i);
        glClear(GL_DEPTH_BUFFER_BIT);

        p.SetOrthographicProj(m_shadowOrthoProjInfo[i]);

        for (int i = 0; i < NUM_MESHES ; i++) {
            p.Orient(m_meshOrientation[i]);
            m_ShadowMapEffect.SetWVP(p.GetWVOrthoPTrans());
            m_mesh.Render();
        }
    }

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

There are a few changes in the shadow mapping phase worth noting. The first is the call to CalOrthoProjs() at the start of the phase. This function is responsible for calculating the bounding boxes used for orthographic projections. The next change is the loop over the cascades. Each cascade must be bound for writing, cleared and rendered to separately. Each cascade has its own projection set up in the m_shadowOrthoProjInfo array (done by CalcOrthoProjs). Since we don't know which mesh goes to which cascade (and it can be more than one) we have to render the entire scene into all the cascades.

(tutorial49.cpp:238)

void RenderPass()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    m_LightingTech.Enable();

    m_LightingTech.SetEyeWorldPos(m_pGameCamera->GetPos());

    m_csmFBO.BindForReading();

    Pipeline p;
    p.Orient(m_quad.GetOrientation());
    p.SetCamera(Vector3f(0.0f, 0.0f, 0.0f), m_dirLight.Direction, Vector3f(0.0f, 1.0f, 0.0f));

    for (uint i = 0 ; i < NUM_CASCADES ; i++) {
        p.SetOrthographicProj(m_shadowOrthoProjInfo[i]);
        m_LightingTech.SetLightWVP(i, p.GetWVOrthoPTrans());
    }

    p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());
    p.SetPerspectiveProj(m_persProjInfo);
    m_LightingTech.SetWVP(p.GetWVPTrans());
    m_LightingTech.SetWorldMatrix(p.GetWorldTrans());
    m_pGroundTex->Bind(COLOR_TEXTURE_UNIT);

    m_quad.Render();

    for (int i = 0; i < NUM_MESHES ; i++) {
        p.Orient(m_meshOrientation[i]);
        m_LightingTech.SetWVP(p.GetWVPTrans());
        m_LightingTech.SetWorldMatrix(p.GetWorldTrans());
        m_mesh.Render();
    }
}

The only change in the lighting phase is that instead of a single light WVP matrix we have three. They are identical except for the projection part. We set them up accordingly in the loop at the middle of the phase.

(tutorial49.cpp:80)

    m_cascadeEnd[0] = m_persProjInfo.zNear;
    m_cascadeEnd[1] = 25.0f,
    m_cascadeEnd[2] = 90.0f,
    m_cascadeEnd[3] = m_persProjInfo.zFar;

Before we study how to calculate the orthographic projections we need to take a look at the m_cascadeEnd array (which is set up as part of the constructor). This array defines the cascades by placing the near Z and far Z in the first and last slots, respectively, and the ends of the cascades in between. So the first cascade ends in the value of slot one, the second in slot two and the last cascade ends with the far Z in the last slot. We need the near Z in the first slot to simplify the calculations later.

(tutorial49.cpp:317)

void CalcOrthoProjs()
{
    Pipeline p;

    // Get the inverse of the view transform
    p.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp());
    Matrix4f Cam = p.GetViewTrans();
    Matrix4f CamInv = Cam.Inverse();

    // Get the light space tranform
    p.SetCamera(Vector3f(0.0f, 0.0f, 0.0f), m_dirLight.Direction, Vector3f(0.0f, 1.0f, 0.0f));
    Matrix4f LightM = p.GetViewTrans();

    float ar = m_persProjInfo.Height / m_persProjInfo.Width;
    float tanHalfHFOV = tanf(ToRadian(m_persProjInfo.FOV / 2.0f));
    float tanHalfVFOV = tanf(ToRadian((m_persProjInfo.FOV * ar) / 2.0f));

    for (uint i = 0 ; i < NUM_CASCADES ; i++) {
        float xn = m_cascadeEnd[i] * tanHalfHFOV;
        float xf = m_cascadeEnd[i + 1] * tanHalfHFOV;
        float yn = m_cascadeEnd[i] * tanHalfVFOV;
        float yf = m_cascadeEnd[i + 1] * tanHalfVFOV;

        Vector4f frustumCorners[NUM_FRUSTUM_CORNERS] = {
            // near face
            Vector4f(xn, yn, m_cascadeEnd[i], 1.0),
            Vector4f(-xn, yn, m_cascadeEnd[i], 1.0),
            Vector4f(xn, -yn, m_cascadeEnd[i], 1.0),
            Vector4f(-xn, -yn, m_cascadeEnd[i], 1.0),

            // far face
            Vector4f(xf, yf, m_cascadeEnd[i + 1], 1.0),
            Vector4f(-xf, yf, m_cascadeEnd[i + 1], 1.0),
            Vector4f(xf, -yf, m_cascadeEnd[i + 1], 1.0),
            Vector4f(-xf, -yf, m_cascadeEnd[i + 1], 1.0)
        };

What we see above matches step #1 of the description in the background section on how to calculate the orthographic projections for the cascades. The frustumCorners array is populated with the eight corners of each cascade in view space. Note that since the field of view is provided only for the horizontal axis we have to extrapolate it for the vertical axis (e.g, if the horizontal field of view is 90 degrees and the window has a width of 1000 and a height of 500 the vertical field of view will be only 45 degrees).

        Vector4f frustumCornersL[NUM_FRUSTUM_CORNERS];

        float minX = std::numeric_limits::max();
        float maxX = std::numeric_limits::min();
        float minY = std::numeric_limits::max();
        float maxY = std::numeric_limits::min();
        float minZ = std::numeric_limits::max();
        float maxZ = std::numeric_limits::min();

        for (uint j = 0 ; j < NUM_FRUSTUM_CORNERS ; j++) {

            // Transform the frustum coordinate from view to world space
            Vector4f vW = CamInv * frustumCorners[j];

            // Transform the frustum coordinate from world to light space
            frustumCornersL[j] = LightM * vW;

            minX = min(minX, frustumCornersL[j].x);
            maxX = max(maxX, frustumCornersL[j].x);
            minY = min(minY, frustumCornersL[j].y);
            maxY = max(maxY, frustumCornersL[j].y);
            minZ = min(minZ, frustumCornersL[j].z);
            maxZ = max(maxZ, frustumCornersL[j].z);
        }

The above code contains step #2 until #4. Each frustum corner coordinate is multiplied by the inverse view transform in order to bring it into world space. It is then multiplied by the light transform in order to move it into light space. We then use a series of min/max functions in order to find the size of the bounding box of the cascade in light space.

        m_shadowOrthoProjInfo[i].r = maxX;
        m_shadowOrthoProjInfo[i].l = minX;
        m_shadowOrthoProjInfo[i].b = minY;
        m_shadowOrthoProjInfo[i].t = maxY;
        m_shadowOrthoProjInfo[i].f = maxZ;
        m_shadowOrthoProjInfo[i].n = minZ;
    }
}

The current entry in the m_shadowOrthoProjInfo array is populated using the values of the bounding box.

(csm.vs)

#version 330

layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
layout (location = 2) in vec3 Normal;

uniform mat4 gWVP;

void main()
{
    gl_Position = gWVP * vec4(Position, 1.0);
}

(csm.fs)

#version 330

void main()
{
}

There is nothing new in the vertex and fragment shaders of the shadow map phase. We just need to render the depth.

(lighting.vs)

#version 330

layout (location = 0) in vec3 Position;
layout (location = 1) in vec2 TexCoord;
layout (location = 2) in vec3 Normal;

const int NUM_CASCADES = 3;

uniform mat4 gWVP;
uniform mat4 gLightWVP[NUM_CASCADES];
uniform mat4 gWorld;

out vec4 LightSpacePos[NUM_CASCADES];
out float ClipSpacePosZ;
out vec2 TexCoord0;
out vec3 Normal0;
out vec3 WorldPos0;

void main()
{
    vec4 Pos = vec4(Position, 1.0);

    gl_Position = gWVP * Pos;

    for (int i = 0 ; i < NUM_CASCADES ; i++) {
        LightSpacePos[i] = gLightWVP[i] * Pos;
    }

    ClipSpacePosZ = gl_Position.z;
    TexCoord0 = TexCoord;
    Normal0 = (gWorld * vec4(Normal, 0.0)).xyz;
    WorldPos0 = (gWorld * vec4(Position, 1.0)).xyz;
}

Let's review the changes in the vertex shader of the lighting phase. Instead of a single position in light space we are going to output one for each cascade and select the proper one for each pixel in the fragment shader. You can optimize this later but for educational purposes I found this to be the simplest way to go. Remember that you cannot select the cascade in the vertex shader anyway because a triangle can be cross cascade. So we have three light space WVP matrices and we output three light space positions. In addition, we also output the Z component of the clip space coordinate. We will use this in the fragment shader to select the cascade. Note that this is calculated in view space and not light space.

(lighting.fs)

const int NUM_CASCADES = 3;

in vec4 LightSpacePos[NUM_CASCADES];
in float ClipSpacePosZ;

uniform sampler2D gShadowMap[NUM_CASCADES];
uniform float gCascadeEndClipSpace[NUM_CASCADES];

The fragment shader of the lighting phase requires some changes/additions in the general section. We get the three light space positions calculated by the vertex shader as input as well as the Z component of the clip space coordinate. Instead of a single shadow map we now have three. In addition, the application must supply the end of each cascade in clip space. We will see later how to calculate this. For now just assume that it is available.

float CalcShadowFactor(int CascadeIndex, vec4 LightSpacePos)
{
    vec3 ProjCoords = LightSpacePos.xyz / LightSpacePos.w;

    vec2 UVCoords;
    UVCoords.x = 0.5 * ProjCoords.x + 0.5;
    UVCoords.y = 0.5 * ProjCoords.y + 0.5;

    float z = 0.5 * ProjCoords.z + 0.5;
    float Depth = texture(gShadowMap[CascadeIndex], UVCoords).x;

    if (Depth < z + 0.00001)
        return 0.5;
    else
        return 1.0;
}

void main()
{
    float ShadowFactor = 0.0;

    for (int i = 0 ; i < NUM_CASCADES ; i++) {
        if (ClipSpacePosZ <= gCascadeEndClipSpace[i]) {
            ShadowFactor = CalcShadowFactor(i, LightSpacePos[i]);
            break;
        }
    }
    ...

In order to find out the proper cascade for the current pixel we traverse the uniform gCascadeEndClipSpace array and compare the Z component of the clip space coordinate to each entry. The array is sorted from the closest cascade to the furthest. We stop as soon as we find an entry whose value is greater than or equal to that Z component. We then call the CalcShadowFactor() function and pass in the index of the cascade we found. The only change to CalcShadowFactor() is that it samples the depth from the shadow map which matches that index. Everything else is the same.

(tutorial49.cpp:134)

for (uint i = 0 ; i < NUM_CASCADES ; i++) {
    Matrix4f Proj;
    Proj.InitPersProjTransform(m_persProjInfo);
    Vector4f vView(0.0f, 0.0f, m_cascadeEnd[i + 1], 1.0f);
    Vector4f vClip = Proj * vView;
    m_LightingTech.SetCascadeEndClipSpace(i, vClip.z);
}

The last piece of the puzzle is to prepare the values for the gCascadeEndClipSpace array. For this we simply take the (0, 0, Z) coordinate where Z is the end of the cascade in view space. We project it using our standard perspective projection transform to move it into clip space. We do this for each cascade in order to calculate the end of every cascade in clip space.

If you study the tutorial sample code you will see that I've added a cascade indicator by adding a red, green or blue color to each cascade to make them stand out. This is very useful for debugging because you can actually see the extent of each cascade. With the CSM algorithm (and the cascade indicator) the scene should now look like this:

comments powered by Disqus